]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-128317: Highlight today in colour in calendar CLI output (#128318)
authorHugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Fri, 3 Jan 2025 12:56:24 +0000 (14:56 +0200)
committerGitHub <noreply@github.com>
Fri, 3 Jan 2025 12:56:24 +0000 (14:56 +0200)
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Doc/library/calendar.rst
Doc/whatsnew/3.14.rst
Lib/_colorize.py
Lib/calendar.py
Misc/NEWS.d/next/Library/2024-12-29-00-33-34.gh-issue-128317.WgFina.rst [new file with mode: 0644]

index 97ca34b6c6184cb05ebc2cf07ae6eb5879ca6627..ace8529d6e7e0c59456ab663ee24951e383aa38c 100644 (file)
@@ -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:*
 
index 69c356ba60e6d44b081205f3718c3e96a367bef0..cb9167300260cb55d0a6f10bf9609bad62df7198 100644 (file)
@@ -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
 ------------------
 
index 709081e25ec59bf80273f6a91daafcefeac7229b..f609901887a26baa57f443a3b9be5ae55e88f935 100644 (file)
@@ -6,9 +6,11 @@ COLORIZE = True
 
 
 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"
index 8c1c646da46a98160dda869b56e68d2f40915f08..d2e5e08d02dbb9887f5ccf2e13bc0ea979589fbc 100644 (file)
@@ -349,11 +349,27 @@ class TextCalendar(Calendar):
             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 @@ class TextCalendar(Calendar):
         """
         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 @@ class TextCalendar(Calendar):
         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 @@ class TextCalendar(Calendar):
             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 (file)
index 0000000..4441108
--- /dev/null
@@ -0,0 +1,2 @@
+Highlight today in colour in :mod:`calendar`'s CLI output. Patch by Hugo van
+Kemenade.