Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
specifies the width of the date columns, which are centered. Depends
on the first weekday as specified in the constructor or set by the
: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)

Expand All @@ -193,19 +188,14 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
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
specifies the width of the date columns, which are centered. If *l* is
given, it specifies the number of lines that each week will use. Depends
on the first weekday as specified in the constructor or set by the
: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)

Expand All @@ -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`.


.. 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.
Optional parameters *w*, *l*, and *c* are for date column width, lines per
Expand All @@ -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
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)

Expand Down
155 changes: 115 additions & 40 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,27 +349,11 @@ def formatday(self, day, weekday, width):
s = '%2i' % day # right-align single-digit days
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).
"""
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
)
return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)

def formatweekday(self, day, width):
"""
Expand Down Expand Up @@ -404,11 +388,10 @@ def prmonth(self, theyear, themonth, w=0, l=0):
"""
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).
"""
highlight_day = highlight_day.day if highlight_day else None
w = max(2, w)
l = max(1, l)
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
Expand All @@ -417,11 +400,11 @@ def formatmonth(self, theyear, themonth, w=0, l=0, *, highlight_day=None):
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 += self.formatweek(week, w).rstrip()
s += '\n' * l
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.
"""
Expand All @@ -446,23 +429,15 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3, *, highlight_day=None):
a(formatstring(headers, colwidth, c).rstrip())
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
height = max(len(cal) for cal in row)
for j in range(height):
weeks = []
for k, cal in enumerate(row):
for cal in row:
if j >= len(cal):
weeks.append('')
else:
day = highlight_day.day if k == month_pos else None
weeks.append(
self.formatweek(cal[j], w, highlight_day=day)
)
weeks.append(self.formatweek(cal[j], w))
a(formatstring(weeks, colwidth, c).rstrip())
a('\n' * l)
return ''.join(v)
Expand Down Expand Up @@ -672,6 +647,111 @@ def formatmonthname(self, theyear, themonth, withyear=True):
with different_locale(self.locale):
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
c = TextCalendar()

Expand Down Expand Up @@ -813,26 +893,21 @@ def main(args=None):
write(cal.formatyearpage(options.year, **optdict))
else:
if options.locale:
cal = LocaleTextCalendar(locale=locale)
cal = _CLIDemoCalendar(highlight_day=today, locale=locale)
else:
cal = TextCalendar()
cal = _CLIDemoCalendar(highlight_day=today)
cal.setfirstweekday(options.first_weekday)
optdict = dict(w=options.width, l=options.lines)
if options.month is None:
optdict["c"] = options.spacing
optdict["m"] = options.months
if options.month is not None:
else:
_validate_month(options.month)
if options.year is None:
optdict["highlight_day"] = today
result = cal.formatyear(today.year, **optdict)
elif options.month is None:
if options.year == today.year:
optdict["highlight_day"] = today
result = cal.formatyear(options.year, **optdict)
else:
if options.year == today.year and options.month == today.month:
optdict["highlight_day"] = today
result = cal.formatmonth(options.year, options.month, **optdict)
write = sys.stdout.write
if options.encoding:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading