From d04770acd80d28f9a99baa7cd4d3c2d18a899a84 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:28:09 +0200 Subject: [PATCH] Highlight today in colour in calendar CLI output --- Doc/library/calendar.rst | 5 +++- Doc/whatsnew/3.14.rst | 13 +++++++++++ Lib/_colorize.py | 2 ++ Lib/calendar.py | 49 ++++++++++++++++++++++++++++++++-------- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index eafc038d6cb7224..239aa4547b8f4a9 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -516,7 +516,7 @@ The :mod:`calendar` module defines the following exceptions: .. _calendar-cli: -Command-Line Usage +Command-line usage ------------------ .. versionadded:: 2.5 @@ -654,6 +654,9 @@ The following options are accepted: The number of months printed per row. Defaults to 3. +.. versionchanged:: 3.14 + By default, today's date is highlighted in color and can be + :ref:`controlled using environment variables `. *HTML-mode options:* diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 97a37a82f76b9bf..baaf0b6034a0cc0 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -292,6 +292,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 ` 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:`100000000000000000000`.) + + concurrent.futures ------------------ diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 709081e25ec59bf..f609901887a26ba 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 8c1c646da46a981..d80d5f95e8cfd4d 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_color = f"{ansi.BLACK}{ansi.BACKGROUND_YELLOW}" + reset = ansi.RESET + else: + highlight_color = reset = "" + + return ' '.join( + ( + f"{highlight_color}{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,7 +404,7 @@ 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). """ @@ -400,11 +416,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 +444,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 +790,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 +807,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 +823,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: