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

1"""Command-line interface.""" 

2 

3import time 

4 

5import click 

6 

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) 

25 

26 

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()) 

30 

31 

32def run_countdown(total_seconds): 

33 """Run the countdown timer for the specified duration. 

34 

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) 

47 

48 # Check for keypress to toggle pause or adjust time 

49 if check_for_keypress(): 

50 key = read_key() # Consume the keypress 

51 

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) 

67 

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="") 

84 

85 

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. 

92 

93 DURATION should be a number followed by m or s for minutes or seconds. 

94 

95 Examples of DURATION: 

96 

97 \\b 

98 - 5m (5 minutes) 

99 - 45s (45 seconds) 

100 - 2m30s (2 minutes and 30 seconds) 

101 

102 Press Space, p, k, or Enter to pause/resume the countdown. 

103 

104 Press +/= to add 30 seconds, - to subtract 30 seconds. 

105 

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 

112 

113 run_countdown(duration) 

114 

115 

116if __name__ == "__main__": 

117 main(prog_name="countdown") # pragma: no cover