Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,10 @@ The following options are accepted:
By default, today's date is highlighted in color and can be
:ref:`controlled using environment variables <using-on-controlling-color>`.

.. versionchanged:: next
By default, the calendar is in color and can be
:ref:`controlled using environment variables <using-on-controlling-color>`.

*HTML-mode options:*

.. option:: --css CSS, -c CSS
Expand Down
11 changes: 8 additions & 3 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -713,14 +713,19 @@ binascii
calendar
--------

* Calendar pages generated by the :class:`calendar.HTMLCalendar` class now support
dark mode and have been migrated to the HTML5 standard for improved accessibility.
(Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.)
* :mod:`calendar`'s :ref:`command-line <calendar-cli>` text output has more
color. This can be controlled with :ref:`environment variables
<using-on-controlling-color>`.
(Contributed by Hugo van Kemenade in :gh:`148352`.)

* The :mod:`calendar`'s :ref:`command-line <calendar-cli>` HTML output now
accepts the year-month option: ``python -m calendar -t html 2009 06``.
(Contributed by Pål Grønås Drange in :gh:`140212`.)

* Calendar pages generated by the :class:`calendar.HTMLCalendar` class now support
dark mode and have been migrated to the HTML5 standard for improved accessibility.
(Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.)


collections
-----------
Expand Down
12 changes: 12 additions & 0 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ class Argparse(ThemeSection):
message: str = ANSIColors.MAGENTA


@dataclass(frozen=True, kw_only=True)
class Calendar(ThemeSection):
header: str = ANSIColors.BOLD
highlight: str = ANSIColors.BLACK + ANSIColors.BACKGROUND_YELLOW
weekday: str = ANSIColors.CYAN
reset: str = ANSIColors.RESET


@dataclass(frozen=True, kw_only=True)
class Difflib(ThemeSection):
"""A 'git diff'-like theme for `difflib.unified_diff`."""
Expand Down Expand Up @@ -404,6 +412,7 @@ class Theme:
below.
"""
argparse: Argparse = field(default_factory=Argparse)
calendar: Calendar = field(default_factory=Calendar)
difflib: Difflib = field(default_factory=Difflib)
fancycompleter: FancyCompleter = field(default_factory=FancyCompleter)
http_server: HttpServer = field(default_factory=HttpServer)
Expand All @@ -417,6 +426,7 @@ def copy_with(
self,
*,
argparse: Argparse | None = None,
calendar: Calendar | None = None,
difflib: Difflib | None = None,
fancycompleter: FancyCompleter | None = None,
http_server: HttpServer | None = None,
Expand All @@ -433,6 +443,7 @@ def copy_with(
"""
return type(self)(
argparse=argparse or self.argparse,
calendar=calendar or self.calendar,
difflib=difflib or self.difflib,
fancycompleter=fancycompleter or self.fancycompleter,
http_server=http_server or self.http_server,
Expand All @@ -453,6 +464,7 @@ def no_colors(cls) -> Self:
"""
return cls(
argparse=Argparse.no_colors(),
calendar=Calendar.no_colors(),
difflib=Difflib.no_colors(),
fancycompleter=FancyCompleter.no_colors(),
http_server=HttpServer.no_colors(),
Expand Down
81 changes: 59 additions & 22 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,28 +686,61 @@ 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):
def _get_theme(self):
from _colorize import get_theme

return get_theme(tty_file=sys.stdout)

def formatday(self, day, weekday, width, *, highlight_day=None):
"""
Returns a single week in a string (no newline).
Returns a formatted day.
"""
if highlight_day:
from _colorize import get_colors

ansi = get_colors()
highlight = f"{ansi.BLACK}{ansi.BACKGROUND_YELLOW}"
reset = ansi.RESET
if day == 0:
s = ''
else:
highlight = reset = ""
s = f'{day:2}'
s = s.center(width)
if day == highlight_day:
theme = self._get_theme().calendar
s = f"{theme.highlight}{s}{theme.reset}"
return s

def formatweek(self, theweek, width, *, highlight_day=None):
"""
Returns a single week in a string (no newline).
"""
return ' '.join(
(
f"{highlight}{self.formatday(d, wd, width)}{reset}"
if d == highlight_day
else self.formatday(d, wd, width)
)
self.formatday(d, wd, width, highlight_day=highlight_day)
for (d, wd) in theweek
)

def formatweekheader(self, width):
"""
Return a header for a week.
"""
header = super().formatweekheader(width)
theme = self._get_theme().calendar
return f"{theme.weekday}{header}{theme.reset}"

def formatmonthname(self, theyear, themonth, width, withyear=True):
"""
Return a formatted month name.
"""
name = super().formatmonthname(theyear, themonth, width, withyear)
theme = self._get_theme().calendar
if (
self.highlight_day
and self.highlight_day.year == theyear
and self.highlight_day.month == themonth
):
color = theme.highlight
name_only = name.strip()
colored_name = f"{color}{name_only}{theme.reset}"
return name.replace(name_only, colored_name, 1)
else:
color = theme.header
return f"{color}{name}{theme.reset}"

def formatmonth(self, theyear, themonth, w=0, l=0):
"""
Return a month's calendar string (multi-line).
Expand Down Expand Up @@ -742,7 +775,9 @@ def formatyear(self, theyear, w=2, l=1, c=6, m=3):
colwidth = (w + 1) * 7 - 1
v = []
a = v.append
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
theme = self._get_theme().calendar
year = repr(theyear).center(colwidth*m+c*(m-1)).rstrip()
a(f"{theme.header}{year}{theme.reset}")
a('\n'*l)
header = self.formatweekheader(w)
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
Expand Down Expand Up @@ -843,28 +878,30 @@ def timegm(tuple):

def main(args=None):
import argparse
parser = argparse.ArgumentParser(color=True)
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
textgroup = parser.add_argument_group('text only arguments')
htmlgroup = parser.add_argument_group('html only arguments')
textgroup.add_argument(
"-w", "--width",
type=int, default=2,
help="width of date column (default 2)"
help="width of date column"
)
textgroup.add_argument(
"-l", "--lines",
type=int, default=1,
help="number of lines for each week (default 1)"
help="number of lines for each week"
)
textgroup.add_argument(
"-s", "--spacing",
type=int, default=6,
help="spacing between months (default 6)"
help="spacing between months"
)
textgroup.add_argument(
"-m", "--months",
type=int, default=3,
help="months per row (default 3)"
help="months per row"
)
htmlgroup.add_argument(
"-c", "--css",
Expand All @@ -879,7 +916,7 @@ def main(args=None):
parser.add_argument(
"-e", "--encoding",
default=None,
help="encoding to use for output (default utf-8)"
help="encoding to use for output"
)
parser.add_argument(
"-t", "--type",
Expand All @@ -890,7 +927,7 @@ def main(args=None):
parser.add_argument(
"-f", "--first-weekday",
type=int, default=0,
help="weekday (0 is Monday, 6 is Sunday) to start each week (default 0)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why stop telling the user the defaults? I think it adds clarity in certain situations :) up to you however

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the contrary! 1fe0d93 formats them with ArgumentDefaultsHelpFormatter to add colour :)

image image

help="weekday (0 is Monday, 6 is Sunday) to start each week"
)
parser.add_argument(
"year",
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ def test_several_leapyears_in_range(self):
def conv(s):
return s.replace('\n', os.linesep).encode()

@support.force_not_colorized_test_class
class CommandLineTestCase(unittest.TestCase):
def setUp(self):
self.runners = [self.run_cli_ok, self.run_cmd_ok]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add more colour to :mod:`calendar`'s CLI output. Patch by Hugo van Kemenade.
Loading