From a6da663e1d96ddff9a07570d2380056c86ee5c77 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:29:10 +0200 Subject: [PATCH 1/5] Put CLI calendar highlighting in private class --- Lib/calendar.py | 131 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 34 deletions(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index d2e5e08d02dbb9..a7880566ba0a86 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -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): """ @@ -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) @@ -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. """ @@ -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) @@ -672,6 +647,94 @@ def formatmonthname(self, theyear, themonth, withyear=True): with different_locale(self.locale): return super().formatmonthname(theyear, themonth, withyear) + +class _CLIDemoCalendar(LocaleTextCalendar): + 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, *, highlight_day=None): + """ + 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) + 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, *, highlight_day=None): + """ + 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 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): + 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) + ) + a(formatstring(weeks, colwidth, c).rstrip()) + a('\n' * l) + return ''.join(v) + + # Support for old module level interface c = TextCalendar() @@ -813,9 +876,9 @@ def main(args=None): write(cal.formatyearpage(options.year, **optdict)) else: if options.locale: - cal = LocaleTextCalendar(locale=locale) + cal = _CLIDemoCalendar(locale=locale) else: - cal = TextCalendar() + cal = _CLIDemoCalendar() cal.setfirstweekday(options.first_weekday) optdict = dict(w=options.width, l=options.lines) if options.month is None: From 2ddd90e969492ab7ee4702bcee775789660c478b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:21:35 +0200 Subject: [PATCH 2/5] Add __init__ with self.highlight_day = highlight_day --- Doc/library/calendar.rst | 21 +++------------------ Lib/calendar.py | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 1c6b5e03af3560..39090e36ed9c0d 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -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 - `. - .. 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. - .. 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 @@ -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 :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 - `. - .. 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`. - .. 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 @@ -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 - `. - .. method:: pryear(theyear, w=2, l=1, c=6, m=3) diff --git a/Lib/calendar.py b/Lib/calendar.py index a7880566ba0a86..1105a705a8036a 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -649,6 +649,10 @@ def formatmonthname(self, theyear, themonth, withyear=True): 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). @@ -671,11 +675,18 @@ def formatweek(self, theweek, width, *, highlight_day=None): for (d, wd) in theweek ) - 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 + 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) @@ -688,7 +699,7 @@ def formatmonth(self, theyear, themonth, w=0, l=0, *, highlight_day=None): 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. """ @@ -713,8 +724,12 @@ 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) + 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 @@ -726,7 +741,9 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3, *, highlight_day=None): if j >= len(cal): weeks.append('') else: - day = highlight_day.day if k == month_pos else None + day = ( + self.highlight_day.day if k == month_pos else None + ) weeks.append( self.formatweek(cal[j], w, highlight_day=day) ) @@ -876,26 +893,21 @@ def main(args=None): write(cal.formatyearpage(options.year, **optdict)) else: if options.locale: - cal = _CLIDemoCalendar(locale=locale) + cal = _CLIDemoCalendar(highlight_day=today, locale=locale) else: - cal = _CLIDemoCalendar() + 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: From c148bde930b69760a06f1c99adbf944906fd0c4c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:32:31 +0200 Subject: [PATCH 3/5] Add blurb --- .../next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst diff --git a/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst new file mode 100644 index 00000000000000..817bc7c9e4cc4b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst @@ -0,0 +1,2 @@ +Put CLI calendar highlighting in private class, removing ``highlight_day`` +from public :class:`HTMLCalendar` API. Patch by Hugo van Kemenade. From 52fb36d3d1485cef08b21d68993e2c97dd069b92 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:45:11 +0200 Subject: [PATCH 4/5] Fix blurb --- .../next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst index 817bc7c9e4cc4b..003d63848ff095 100644 --- a/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst +++ b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst @@ -1,2 +1,2 @@ Put CLI calendar highlighting in private class, removing ``highlight_day`` -from public :class:`HTMLCalendar` API. Patch by Hugo van Kemenade. +from public :class:`calendar.HTMLCalendar` API. Patch by Hugo van Kemenade. From 4424ea9c53407f9bcbf188dc2737cf3dc704a448 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:51:57 +0200 Subject: [PATCH 5/5] Fix typo Co-authored-by: Petr Viktorin --- .../next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst index 003d63848ff095..eaff03384a976a 100644 --- a/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst +++ b/Misc/NEWS.d/next/Library/2025-02-03-22-31-43.gh-issue-128317.n2Swnh.rst @@ -1,2 +1,2 @@ Put CLI calendar highlighting in private class, removing ``highlight_day`` -from public :class:`calendar.HTMLCalendar` API. Patch by Hugo van Kemenade. +from public :class:`calendar.TextCalendar` API. Patch by Hugo van Kemenade.