Skip to content

Commit 662fce9

Browse files
treyhunnersampsyo
andcommitted
Add support for 3 digit minutes
Includes changes from #210 for the newly refactored code Co-authored-by: Adrian Sampson <adrian@radbox.org>
1 parent 3db24b4 commit 662fce9

5 files changed

Lines changed: 56 additions & 14 deletions

File tree

src/countdown/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
def get_number_lines(seconds):
2828
"""Return list of lines which make large MM:SS glyphs for given seconds."""
29-
return timer.get_number_lines(seconds, get_chars_for_terminal())
29+
return timer.get_number_lines(seconds, get_chars_for_terminal(seconds))
3030

3131

3232
def run_countdown(total_seconds):

src/countdown/display.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,34 @@ def enable_ansi_escape_codes(): # pragma: no cover
3535
)
3636

3737

38-
def get_required_width(chars):
39-
"""Calculate the minimum width required to display MM:SS format."""
40-
# MM:SS format has 4 digits, 1 colon, and 1 space after each char
41-
digit_width = max(len(line) for line in chars["0"].splitlines())
42-
colon_width = max(len(line) for line in chars[":"].splitlines())
43-
# Total: 4 digits + 1 colon + 5 spaces (after each character)
44-
return digit_width * 4 + colon_width + 5
38+
def _format_time_string(seconds):
39+
"""Return the MM:SS string used for display based on seconds."""
40+
seconds = max(0, int(seconds))
41+
minutes, seconds = divmod(seconds, 60)
42+
return f"{minutes:02d}:{seconds:02d}"
4543

4644

47-
def get_chars_for_terminal():
48-
"""Return the largest CHARS dictionary that fits in the current terminal."""
45+
def get_required_width(chars, time_string):
46+
"""Calculate the minimum width required to display the given time string."""
47+
char_widths = {
48+
char: max(len(line) for line in glyph.splitlines())
49+
for char, glyph in chars.items()
50+
}
51+
# Each character in the timer output has a trailing space appended
52+
return sum(char_widths[char] + 1 for char in time_string)
53+
54+
55+
def get_chars_for_terminal(seconds=0):
56+
"""Return the largest CHARS dictionary that fits in the current terminal.
57+
58+
Args:
59+
seconds: Current countdown value, used to account for wide minute values.
60+
"""
4961
width, height = shutil.get_terminal_size()
62+
time_string = _format_time_string(seconds)
5063
for size in DIGIT_SIZES:
5164
chars = CHARS_BY_SIZE[size]
52-
required_width = get_required_width(chars)
65+
required_width = get_required_width(chars, time_string)
5366
# For size 3 (smallest multi-line), allow it without padding
5467
# For larger sizes, require 1 line of padding on top and bottom (2 total)
5568
padding_needed = 0 if size == 3 else 2

src/countdown/timer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
r"""
77
^
88
(?: # Optional minutes
9-
( \d{1,2} ) # D or DD
9+
( \d+ ) # one or more digits
1010
m # "m"
1111
)?
1212
(?: # Optional seconds
13-
( \d{1,2} ) # D or DD
13+
( \d+ ) # one or more digits
1414
s # "s"
1515
)?
1616
$

tests/test_display.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ def test_char_heights_match_size():
138138

139139
def test_get_chars_for_terminal_selects_largest_that_fits(monkeypatch):
140140
"""Test that get_chars_for_terminal selects the largest size that fits both dimensions."""
141-
# Size requirements: 16(93w), 7(57w), 5(33w), 3(20w), 1(10w)
141+
# Size requirements for displaying 00:00:
142+
# 16(93w), 7(57w), 5(33w), 3(20w), 1(10w)
142143

143144
# 80x24 terminal - size 7 fits (57w <= 80, 7h <= 24)
144145
monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24))
@@ -219,3 +220,15 @@ def test_width_constraints_force_smaller_size(monkeypatch):
219220
chars = display.get_chars_for_terminal()
220221
height = len(chars["0"].splitlines())
221222
assert height == 1, "19x5 terminal too narrow for size 3, should select size 1"
223+
224+
225+
def test_three_digit_minutes_force_smaller_chars(monkeypatch):
226+
"""Wide minute values should fall back to smaller glyphs if needed."""
227+
# 60x20 terminal can show size 7 for two-digit minutes
228+
monkeypatch.setattr("shutil.get_terminal_size", fake_size(60, 20))
229+
chars = display.get_chars_for_terminal(0)
230+
assert len(chars["0"].splitlines()) == 7
231+
232+
# But 100 minutes needs 70 columns at size 7, so we should drop to size 5
233+
chars = display.get_chars_for_terminal(6000) # 100 minutes
234+
assert len(chars["0"].splitlines()) == 5

tests/test_timer.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def test_duration_10_minutes():
3434
assert timer.duration("10m") == 600
3535

3636

37+
def test_duration_150_minutes():
38+
assert timer.duration("150m") == 9000
39+
40+
3741
def test_duration_25_minutes():
3842
assert timer.duration("25m") == 1500
3943

@@ -88,6 +92,18 @@ def test_get_number_lines_45_minutes():
8892
).strip("\n")
8993

9094

95+
def test_get_number_lines_101_minutes():
96+
# Use size 5 digits
97+
chars = CHARS_BY_SIZE[5]
98+
assert join_lines(timer.get_number_lines(6060, chars)) == (
99+
" ██ ██████ ██ ██████ ██████\n"
100+
" ███ ██ ██ ███ ██ ██ ██ ██ ██\n"
101+
" ██ ██ ██ ██ ██ ██ ██ ██\n"
102+
" ██ ██ ██ ██ ██ ██ ██ ██ ██\n"
103+
" ██ ██████ ██ ██████ ██████"
104+
)
105+
106+
91107
def test_get_number_lines_17_minutes_and_four_seconds():
92108
# Use size 5 digits
93109
chars = CHARS_BY_SIZE[5]

0 commit comments

Comments
 (0)