Coverage for src/countdown/__main__.py: 100%
54 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 20:45 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 20:45 -0800
1"""Command-line interface."""
3import time
5import click
7from . import timer
8from .display import (
9 DISABLE_ALT_BUFFER,
10 ENABLE_ALT_BUFFER,
11 HIDE_CURSOR,
12 SHOW_CURSOR,
13 enable_ansi_escape_codes,
14 get_chars_for_terminal,
15 print_full_screen,
16)
17from .keys import get_time_adjustment, is_pause_key, is_time_adjust_key
18from .terminal import (
19 check_for_keypress,
20 drain_keypresses,
21 read_key,
22 restore_terminal,
23 setup_terminal,
24)
27def get_number_lines(seconds):
28 """Return list of lines which make large MM:SS glyphs for given seconds."""
29 return timer.get_number_lines(seconds, get_chars_for_terminal())
32def run_countdown(total_seconds):
33 """Run the countdown timer for the specified duration.
35 Args:
36 total_seconds: Duration in seconds to count down from
37 """
38 enable_ansi_escape_codes()
39 old_settings = setup_terminal()
40 print(ENABLE_ALT_BUFFER + HIDE_CURSOR, end="")
41 try:
42 paused = False
43 n = total_seconds
44 while n >= 0:
45 lines = get_number_lines(n)
46 print_full_screen(lines, paused=paused)
48 # Check for keypress to toggle pause or adjust time
49 if check_for_keypress():
50 key = read_key() # Consume the keypress
52 if key == "q":
53 # Quit the timer
54 break
55 elif is_pause_key(key):
56 paused = not paused
57 drain_keypresses() # Ignore any additional rapid keypresses
58 lines = get_number_lines(n)
59 print_full_screen(lines, paused=paused)
60 elif is_time_adjust_key(key):
61 # Adjust the timer by +/- 30 seconds
62 adjustment = get_time_adjustment(key)
63 n = max(0, n + adjustment) # Don't go below 0
64 drain_keypresses() # Ignore any additional rapid keypresses
65 lines = get_number_lines(n)
66 print_full_screen(lines, paused=paused)
68 # Only sleep and decrement if not paused
69 if not paused:
70 # Sleep in small chunks to check for keypresses more frequently
71 for _ in range(20): # 20 x 0.05 = 1 second
72 time.sleep(0.05)
73 if check_for_keypress():
74 break # Exit sleep early if key is pressed
75 n -= 1
76 else:
77 # Short sleep when paused for responsive keypress checking
78 time.sleep(0.05)
79 except KeyboardInterrupt:
80 pass
81 finally:
82 restore_terminal(old_settings)
83 print(SHOW_CURSOR + DISABLE_ALT_BUFFER, end="")
86@click.command()
87@click.version_option(package_name="countdown-cli")
88@click.argument("duration", type=timer.duration, required=False)
89@click.pass_context
90def main(ctx, duration):
91 """Countdown from the given duration to 0.
93 DURATION should be a number followed by m or s for minutes or seconds.
95 Examples of DURATION:
97 \\b
98 - 5m (5 minutes)
99 - 45s (45 seconds)
100 - 2m30s (2 minutes and 30 seconds)
102 Press Space, p, k, or Enter to pause/resume the countdown.
104 Press +/= to add 30 seconds, - to subtract 30 seconds.
106 Press q to quit.
107 """ # noqa: D301
108 # Show help if no duration provided
109 if duration is None:
110 click.echo(ctx.get_help())
111 return
113 run_countdown(duration)
116if __name__ == "__main__":
117 main(prog_name="countdown") # pragma: no cover