gh-128317: Highlight today in colour in calendar CLI output (#128318)
https://github.com/python/cpython/commit/f21af186bf21c1c554209ac67d78d3cf99f... commit: f21af186bf21c1c554209ac67d78d3cf99f7d7c0 branch: main author: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> committer: hugovk <1324225+hugovk@users.noreply.github.com> date: 2025-01-03T14:56:24+02:00 summary: gh-128317: Highlight today in colour in calendar CLI output (#128318) Co-authored-by: Peter Bierma <zintensitydev@gmail.com> files: A Misc/NEWS.d/next/Library/2024-12-29-00-33-34.gh-issue-128317.WgFina.rst M Doc/library/calendar.rst M Doc/whatsnew/3.14.rst M Lib/_colorize.py M Lib/calendar.py diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 97ca34b6c6184c..ace8529d6e7e0c 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -146,26 +146,34 @@ 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) + .. method:: formatweek(theweek, w=0, highlight_day=None) 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:: next + 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) Return a string representing the name of a single weekday formatted to the specified *width*. The *weekday* parameter is an integer representing the day of the week, where ``0`` is Monday and ``6`` is Sunday. + .. method:: formatweekheader(width) Return a string containing the header row of weekday names, formatted with the given *width* for each column. The names depend on the locale settings and are padded to the specified width. - .. method:: formatmonth(theyear, themonth, w=0, l=0) + + .. method:: formatmonth(theyear, themonth, w=0, l=0, highlight_day=None) 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 @@ -173,6 +181,12 @@ 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:: next + 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) Return a string representing the month's name centered within the @@ -180,12 +194,13 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is output. The *theyear* and *themonth* parameters specify the year and month for the name to be formatted respectively. + .. method:: prmonth(theyear, themonth, w=0, l=0) Print a month's calendar as returned by :meth:`formatmonth`. - .. method:: formatyear(theyear, w=2, l=1, c=6, m=3) + .. method:: formatyear(theyear, w=2, l=1, c=6, m=3, highlight_day=None) 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 @@ -194,6 +209,11 @@ 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:: next + 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) @@ -549,7 +569,7 @@ The :mod:`calendar` module defines the following exceptions: .. _calendar-cli: -Command-Line Usage +Command-line usage ------------------ .. versionadded:: 2.5 @@ -687,6 +707,9 @@ The following options are accepted: The number of months printed per row. Defaults to 3. +.. versionchanged:: next + By default, today's date is highlighted in color and can be + :ref:`controlled using environment variables <using-on-controlling-color>`. *HTML-mode options:* diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 69c356ba60e6d4..cb9167300260cb 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -296,6 +296,19 @@ ast * The ``repr()`` output for AST nodes now includes more information. (Contributed by Tomas R in :gh:`116022`.) + +calendar +-------- + +* By default, today's date is highlighted in color in :mod:`calendar`'s + :ref:`command-line <calendar-cli>` text output. + This can be controlled via the :envvar:`PYTHON_COLORS` environment + variable as well as the canonical |NO_COLOR|_ + and |FORCE_COLOR|_ environment variables. + See also :ref:`using-on-controlling-color`. + (Contributed by Hugo van Kemenade in :gh:`128317`.) + + concurrent.futures ------------------ diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 709081e25ec59b..f609901887a26b 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -6,9 +6,11 @@ class ANSIColors: + BACKGROUND_YELLOW = "\x1b[43m" BOLD_GREEN = "\x1b[1;32m" BOLD_MAGENTA = "\x1b[1;35m" BOLD_RED = "\x1b[1;31m" + BLACK = "\x1b[30m" GREEN = "\x1b[32m" GREY = "\x1b[90m" MAGENTA = "\x1b[35m" diff --git a/Lib/calendar.py b/Lib/calendar.py index 8c1c646da46a98..d2e5e08d02dbb9 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -349,11 +349,27 @@ def formatday(self, day, weekday, width): s = '%2i' % day # right-align single-digit days return s.center(width) - def formatweek(self, theweek, width): + def formatweek(self, theweek, width, *, highlight_day=None): """ Returns a single week in a string (no newline). """ - return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek) + 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 formatweekday(self, day, width): """ @@ -388,10 +404,11 @@ 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): + 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) @@ -400,11 +417,11 @@ def formatmonth(self, theyear, themonth, w=0, l=0): s += self.formatweekheader(w).rstrip() s += '\n' * l for week in self.monthdays2calendar(theyear, themonth): - s += self.formatweek(week, w).rstrip() + 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): + 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. """ @@ -428,15 +445,24 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3): 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 cal in row: + for k, cal in enumerate(row): if j >= len(cal): weeks.append('') else: - weeks.append(self.formatweek(cal[j], w)) + 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) @@ -765,6 +791,7 @@ def main(args=None): sys.exit(1) locale = options.locale, options.encoding + today = datetime.date.today() if options.type == "html": if options.month: @@ -781,7 +808,7 @@ def main(args=None): optdict = dict(encoding=encoding, css=options.css) write = sys.stdout.buffer.write if options.year is None: - write(cal.formatyearpage(datetime.date.today().year, **optdict)) + write(cal.formatyearpage(today.year, **optdict)) else: write(cal.formatyearpage(options.year, **optdict)) else: @@ -797,10 +824,15 @@ def main(args=None): if options.month is not None: _validate_month(options.month) if options.year is None: - result = cal.formatyear(datetime.date.today().year, **optdict) + 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: diff --git a/Misc/NEWS.d/next/Library/2024-12-29-00-33-34.gh-issue-128317.WgFina.rst b/Misc/NEWS.d/next/Library/2024-12-29-00-33-34.gh-issue-128317.WgFina.rst new file mode 100644 index 00000000000000..4441108014569e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-29-00-33-34.gh-issue-128317.WgFina.rst @@ -0,0 +1,2 @@ +Highlight today in colour in :mod:`calendar`'s CLI output. Patch by Hugo van +Kemenade.
participants (1)
-
hugovk