gh-128317: Move CLI calendar highlighting to private class (#129625)

Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Hugo van Kemenade 2025-02-08 17:56:57 +02:00 committed by GitHub
parent a56ead089c
commit 1bccd6c34f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 120 additions and 58 deletions

View file

@ -166,18 +166,13 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
the specified width, representing an empty day. The *weekday* parameter the specified width, representing an empty day. The *weekday* parameter
is unused. is unused.
.. method:: formatweek(theweek, w=0, highlight_day=None) .. method:: formatweek(theweek, w=0)
Return a single week in a string with no newline. If *w* is provided, it Return a single week in a string with no newline. If *w* is provided, it
specifies the width of the date columns, which are centered. Depends specifies the width of the date columns, which are centered. Depends
on the first weekday as specified in the constructor or set by the on the first weekday as specified in the constructor or set by the
:meth:`setfirstweekday` method. :meth:`setfirstweekday` method.
.. versionchanged:: 3.14
If *highlight_day* is given, this date is highlighted in color.
This can be :ref:`controlled using environment variables
<using-on-controlling-color>`.
.. method:: formatweekday(weekday, width) .. method:: formatweekday(weekday, width)
@ -193,7 +188,7 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
settings and are padded to the specified width. settings and are padded to the specified width.
.. method:: formatmonth(theyear, themonth, w=0, l=0, highlight_day=None) .. method:: formatmonth(theyear, themonth, w=0, l=0)
Return a month's calendar in a multi-line string. If *w* is provided, it Return a month's calendar in a multi-line string. If *w* is provided, it
specifies the width of the date columns, which are centered. If *l* is specifies the width of the date columns, which are centered. If *l* is
@ -201,11 +196,6 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
on the first weekday as specified in the constructor or set by the on the first weekday as specified in the constructor or set by the
:meth:`setfirstweekday` method. :meth:`setfirstweekday` method.
.. versionchanged:: 3.14
If *highlight_day* is given, this date is highlighted in color.
This can be :ref:`controlled using environment variables
<using-on-controlling-color>`.
.. method:: formatmonthname(theyear, themonth, width=0, withyear=True) .. method:: formatmonthname(theyear, themonth, width=0, withyear=True)
@ -220,7 +210,7 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
Print a month's calendar as returned by :meth:`formatmonth`. Print a month's calendar as returned by :meth:`formatmonth`.
.. method:: formatyear(theyear, w=2, l=1, c=6, m=3, highlight_day=None) .. method:: formatyear(theyear, w=2, l=1, c=6, m=3)
Return a *m*-column calendar for an entire year as a multi-line string. Return a *m*-column calendar for an entire year as a multi-line string.
Optional parameters *w*, *l*, and *c* are for date column width, lines per Optional parameters *w*, *l*, and *c* are for date column width, lines per
@ -229,11 +219,6 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
:meth:`setfirstweekday` method. The earliest year for which a calendar :meth:`setfirstweekday` method. The earliest year for which a calendar
can be generated is platform-dependent. can be generated is platform-dependent.
.. versionchanged:: 3.14
If *highlight_day* is given, this date is highlighted in color.
This can be :ref:`controlled using environment variables
<using-on-controlling-color>`.
.. method:: pryear(theyear, w=2, l=1, c=6, m=3) .. method:: pryear(theyear, w=2, l=1, c=6, m=3)

View file

@ -349,27 +349,11 @@ class TextCalendar(Calendar):
s = '%2i' % day # right-align single-digit days s = '%2i' % day # right-align single-digit days
return s.center(width) return s.center(width)
def formatweek(self, theweek, width, *, highlight_day=None): def formatweek(self, theweek, width):
""" """
Returns a single week in a string (no newline). Returns a single week in a string (no newline).
""" """
if highlight_day: return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
from _colorize import get_colors
ansi = get_colors()
highlight = f"{ansi.BLACK}{ansi.BACKGROUND_YELLOW}"
reset = ansi.RESET
else:
highlight = reset = ""
return ' '.join(
(
f"{highlight}{self.formatday(d, wd, width)}{reset}"
if d == highlight_day
else self.formatday(d, wd, width)
)
for (d, wd) in theweek
)
def formatweekday(self, day, width): def formatweekday(self, day, width):
""" """
@ -404,11 +388,10 @@ class TextCalendar(Calendar):
""" """
print(self.formatmonth(theyear, themonth, w, l), end='') print(self.formatmonth(theyear, themonth, w, l), end='')
def formatmonth(self, theyear, themonth, w=0, l=0, *, highlight_day=None): def formatmonth(self, theyear, themonth, w=0, l=0):
""" """
Return a month's calendar string (multi-line). Return a month's calendar string (multi-line).
""" """
highlight_day = highlight_day.day if highlight_day else None
w = max(2, w) w = max(2, w)
l = max(1, l) l = max(1, l)
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1) s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
@ -417,11 +400,11 @@ class TextCalendar(Calendar):
s += self.formatweekheader(w).rstrip() s += self.formatweekheader(w).rstrip()
s += '\n' * l s += '\n' * l
for week in self.monthdays2calendar(theyear, themonth): for week in self.monthdays2calendar(theyear, themonth):
s += self.formatweek(week, w, highlight_day=highlight_day).rstrip() s += self.formatweek(week, w).rstrip()
s += '\n' * l s += '\n' * l
return s return s
def formatyear(self, theyear, w=2, l=1, c=6, m=3, *, highlight_day=None): def formatyear(self, theyear, w=2, l=1, c=6, m=3):
""" """
Returns a year's calendar as a multi-line string. Returns a year's calendar as a multi-line string.
""" """
@ -446,23 +429,15 @@ class TextCalendar(Calendar):
a(formatstring(headers, colwidth, c).rstrip()) a(formatstring(headers, colwidth, c).rstrip())
a('\n'*l) a('\n'*l)
if highlight_day and highlight_day.month in months:
month_pos = months.index(highlight_day.month)
else:
month_pos = None
# max number of weeks for this row # max number of weeks for this row
height = max(len(cal) for cal in row) height = max(len(cal) for cal in row)
for j in range(height): for j in range(height):
weeks = [] weeks = []
for k, cal in enumerate(row): for cal in row:
if j >= len(cal): if j >= len(cal):
weeks.append('') weeks.append('')
else: else:
day = highlight_day.day if k == month_pos else None weeks.append(self.formatweek(cal[j], w))
weeks.append(
self.formatweek(cal[j], w, highlight_day=day)
)
a(formatstring(weeks, colwidth, c).rstrip()) a(formatstring(weeks, colwidth, c).rstrip())
a('\n' * l) a('\n' * l)
return ''.join(v) return ''.join(v)
@ -672,6 +647,111 @@ class LocaleHTMLCalendar(HTMLCalendar):
with different_locale(self.locale): with different_locale(self.locale):
return super().formatmonthname(theyear, themonth, withyear) return super().formatmonthname(theyear, themonth, withyear)
class _CLIDemoCalendar(LocaleTextCalendar):
def __init__(self, highlight_day=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.highlight_day = highlight_day
def formatweek(self, theweek, width, *, highlight_day=None):
"""
Returns a single week in a string (no newline).
"""
if highlight_day:
from _colorize import get_colors
ansi = get_colors()
highlight = f"{ansi.BLACK}{ansi.BACKGROUND_YELLOW}"
reset = ansi.RESET
else:
highlight = reset = ""
return ' '.join(
(
f"{highlight}{self.formatday(d, wd, width)}{reset}"
if d == highlight_day
else self.formatday(d, wd, width)
)
for (d, wd) in theweek
)
def formatmonth(self, theyear, themonth, w=0, l=0):
"""
Return a month's calendar string (multi-line).
"""
if (
self.highlight_day
and self.highlight_day.year == theyear
and self.highlight_day.month == themonth
):
highlight_day = self.highlight_day.day
else:
highlight_day = None
w = max(2, w)
l = max(1, l)
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
s = s.rstrip()
s += '\n' * l
s += self.formatweekheader(w).rstrip()
s += '\n' * l
for week in self.monthdays2calendar(theyear, themonth):
s += self.formatweek(week, w, highlight_day=highlight_day).rstrip()
s += '\n' * l
return s
def formatyear(self, theyear, w=2, l=1, c=6, m=3):
"""
Returns a year's calendar as a multi-line string.
"""
w = max(2, w)
l = max(1, l)
c = max(2, c)
colwidth = (w + 1) * 7 - 1
v = []
a = v.append
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
a('\n'*l)
header = self.formatweekheader(w)
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
# months in this row
months = range(m*i+1, min(m*(i+1)+1, 13))
a('\n'*l)
names = (self.formatmonthname(theyear, k, colwidth, False)
for k in months)
a(formatstring(names, colwidth, c).rstrip())
a('\n'*l)
headers = (header for k in months)
a(formatstring(headers, colwidth, c).rstrip())
a('\n'*l)
if (
self.highlight_day
and self.highlight_day.year == theyear
and self.highlight_day.month in months
):
month_pos = months.index(self.highlight_day.month)
else:
month_pos = None
# max number of weeks for this row
height = max(len(cal) for cal in row)
for j in range(height):
weeks = []
for k, cal in enumerate(row):
if j >= len(cal):
weeks.append('')
else:
day = (
self.highlight_day.day if k == month_pos else None
)
weeks.append(
self.formatweek(cal[j], w, highlight_day=day)
)
a(formatstring(weeks, colwidth, c).rstrip())
a('\n' * l)
return ''.join(v)
# Support for old module level interface # Support for old module level interface
c = TextCalendar() c = TextCalendar()
@ -813,26 +893,21 @@ def main(args=None):
write(cal.formatyearpage(options.year, **optdict)) write(cal.formatyearpage(options.year, **optdict))
else: else:
if options.locale: if options.locale:
cal = LocaleTextCalendar(locale=locale) cal = _CLIDemoCalendar(highlight_day=today, locale=locale)
else: else:
cal = TextCalendar() cal = _CLIDemoCalendar(highlight_day=today)
cal.setfirstweekday(options.first_weekday) cal.setfirstweekday(options.first_weekday)
optdict = dict(w=options.width, l=options.lines) optdict = dict(w=options.width, l=options.lines)
if options.month is None: if options.month is None:
optdict["c"] = options.spacing optdict["c"] = options.spacing
optdict["m"] = options.months optdict["m"] = options.months
if options.month is not None: else:
_validate_month(options.month) _validate_month(options.month)
if options.year is None: if options.year is None:
optdict["highlight_day"] = today
result = cal.formatyear(today.year, **optdict) result = cal.formatyear(today.year, **optdict)
elif options.month is None: elif options.month is None:
if options.year == today.year:
optdict["highlight_day"] = today
result = cal.formatyear(options.year, **optdict) result = cal.formatyear(options.year, **optdict)
else: else:
if options.year == today.year and options.month == today.month:
optdict["highlight_day"] = today
result = cal.formatmonth(options.year, options.month, **optdict) result = cal.formatmonth(options.year, options.month, **optdict)
write = sys.stdout.write write = sys.stdout.write
if options.encoding: if options.encoding:

View file

@ -0,0 +1,2 @@
Put CLI calendar highlighting in private class, removing ``highlight_day``
from public :class:`calendar.TextCalendar` API. Patch by Hugo van Kemenade.