Skip to content

Commit bd5dd0d

Browse files
committed
Merge branch 'main' into drop-3.10
2 parents 1a077b9 + 5b3bc53 commit bd5dd0d

10 files changed

Lines changed: 158 additions & 69 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ prompt is displayed.
9595
- `RawDescriptionCmd2HelpFormatter`
9696
- `RawTextCmd2HelpFormatter`
9797
- `TextGroup`
98-
- Replaced the global `APP_THEME` constant in `rich_utils.py` with `get_theme()` and
99-
`set_theme()` functions in `theme.py` to support lazy initialization and safer in-place
100-
updates of the theme.
98+
- Replaced the global `APP_THEME` constant in `rich_utils.py` with `get_theme()`,
99+
`reset_theme()`, and `update_theme()` functions in `theme.py` to support lazy
100+
initialization and safer in-place updates of the theme.
101101
- Renamed `Cmd._command_parsers` to `Cmd.command_parsers`.
102102
- Removed `RichPrintKwargs` `TypedDict` in favor of using `Mapping[str, Any]`, allowing for
103103
greater flexibility in passing keyword arguments to `console.print()` calls.

cmd2/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
from .styles import Cmd2Style
5757
from .theme import (
5858
get_theme,
59-
set_theme,
59+
reset_theme,
60+
update_theme,
6061
)
6162
from .utils import (
6263
CustomCompletionSettings,
@@ -112,7 +113,8 @@
112113
"Cmd2Style",
113114
# Theme
114115
"get_theme",
115-
"set_theme",
116+
"reset_theme",
117+
"update_theme",
116118
# Utilities
117119
"categorize",
118120
"CustomCompletionSettings",

cmd2/rich_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ def __repr__(self) -> str:
107107
class Cmd2HelpFormatter(RichHelpFormatter):
108108
"""Custom help formatter to configure ordering of help text."""
109109

110-
# Have our own copy of the styles so set_theme() can synchronize them with
111-
# the cmd2 application theme without overwriting RichHelpFormatter's defaults.
110+
# Create our own copy of the styles so cmd2 can synchronize them with
111+
# the application theme without overwriting RichHelpFormatter's defaults.
112112
styles: ClassVar[dict[str, StyleType]] = DEFAULT_ARGPARSE_STYLES.copy()
113113

114114
# Disable automatic highlighting in the help text.
@@ -341,10 +341,10 @@ def __init__(
341341
"Passing 'force_interactive' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
342342
)
343343

344-
# Don't allow a theme to be passed in, as it is controlled by get_theme() and set_theme().
345-
# Use set_theme() to set the global theme or use a temporary theme with console.use_theme().
344+
# Don't allow a theme to be passed in. Use update_theme() to modify the global theme
345+
# or use a temporary theme with console.use_theme().
346346
if "theme" in kwargs:
347-
raise TypeError("Passing 'theme' is not allowed. Its behavior is controlled by get_theme() and set_theme().")
347+
raise TypeError("Passing 'theme' is not allowed. Modify the global theme with update_theme().")
348348

349349
# Store the configuration key used by cmd2 to cache this console.
350350
self._config_key = self._build_config_key(file=file, **kwargs)

cmd2/styles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
their own default styles.
1515
1616
For a complete theming experience, you can create a custom theme that includes
17-
styles from Rich and rich-argparse. The `cmd2.theme.set_theme()` function
17+
styles from Rich and rich-argparse. The `cmd2.theme.update_theme()` function
1818
automatically updates rich-argparse's styles with any custom styles provided in
1919
your theme dictionary, so you don't have to modify them directly.
2020

cmd2/theme.py

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
from typing import cast
2424

2525
from prompt_toolkit.styles import Style as PtStyle
26-
from rich.style import StyleType
26+
from rich.style import (
27+
Style,
28+
StyleType,
29+
)
2730
from rich.theme import Theme
2831

2932
from .pt_utils import rich_to_pt_style
@@ -35,16 +38,16 @@
3538
)
3639

3740
# The application-wide theme, defined using Rich's styling system.
38-
# Use get_theme() to access it and set_theme() to modify it.
41+
# Use get_theme() to access it.
42+
# Use reset_theme() and update_theme() to modify it.
3943
_THEME: Theme | None = None
4044

4145
# The prompt-toolkit version of the theme, synchronized from the Rich theme.
42-
# Use get_pt_theme() to access it. This object is automatically updated whenever
43-
# set_theme() is called.
46+
# Use get_pt_theme() to access it.
4447
_PT_THEME: PtStyle | None = None
4548

4649
# Maps style names to internal UI component names used by prompt-toolkit.
47-
# This allows developers to use application-specific style names in set_theme()
50+
# This allows developers to use application-specific style names in update_theme()
4851
# while ensuring the underlying prompt-toolkit UI is styled correctly.
4952
# Use register_pt_mapping() and unregister_pt_mapping() to manage these mappings.
5053
#
@@ -72,52 +75,73 @@
7275
def get_theme() -> Theme:
7376
"""Get the application-wide Rich theme. Initializes it on the first call."""
7477
if _THEME is None:
75-
set_theme()
78+
reset_theme()
7679
return cast(Theme, _THEME)
7780

7881

7982
def get_pt_theme() -> PtStyle:
8083
"""Get the application-wide prompt-toolkit style. Initializes it on the first call."""
8184
if _PT_THEME is None:
82-
set_theme()
85+
reset_theme()
8386
return cast(PtStyle, _PT_THEME)
8487

8588

86-
def set_theme(styles: Mapping[str, StyleType] | None = None) -> None:
87-
"""Set the application-wide theme.
89+
def reset_theme() -> None:
90+
"""Reset the application-wide theme to its initial state.
8891
89-
This function performs an in-place update of the existing Rich theme's
92+
This function performs an in-place reset of the existing Rich theme's
9093
styles. This ensures that any Console objects already using the theme
9194
will reflect the changes immediately without needing to be recreated.
9295
93-
It also automatically synchronizes the prompt-toolkit theme for any
94-
styles with registered prefixes or mapped UI components.
95-
96-
Call set_theme() with no arguments to reset to the default theme.
97-
This will clear any custom styles that were previously applied.
98-
99-
:param styles: optional mapping of style names to styles
96+
Changes are automatically propagated to all synchronized components.
10097
"""
10198
global _THEME # noqa: PLW0603
99+
100+
# Include default styles from cmd2, rich-argparse, and Rich.
101+
styles = DEFAULT_CMD2_STYLES.copy()
102+
styles.update(DEFAULT_ARGPARSE_STYLES)
103+
default_theme = Theme(styles, inherit=True)
104+
102105
if _THEME is None:
103-
_THEME = Theme()
106+
# Initial assignment
107+
_THEME = default_theme
108+
else:
109+
# Perform in-place reset to preserve existing references
110+
_THEME.styles.clear()
111+
_THEME.styles.update(default_theme.styles)
112+
113+
_sync_all()
114+
115+
116+
def update_theme(styles: Mapping[str, StyleType]) -> None:
117+
"""Update the existing theme.
118+
119+
This function performs an in-place update of the existing Rich theme's
120+
styles. This ensures that any Console objects already using the theme
121+
will reflect the changes immediately without needing to be recreated.
122+
123+
Changes are automatically propagated to all synchronized components.
104124
105-
# Start with a fresh copy of the default styles.
106-
unparsed_styles: dict[str, StyleType] = {}
107-
unparsed_styles.update(_create_default_theme().styles)
125+
:param styles: mapping of style names to styles
126+
"""
127+
# Convert any string styles to Style objects
128+
parsed_styles = {name: style if isinstance(style, Style) else Style.parse(style) for name, style in styles.items()}
129+
130+
# Perform in-place update to preserve existing references
131+
get_theme().styles.update(parsed_styles)
108132

109-
# Add the custom styles, which may contain unparsed strings
110-
if styles is not None:
111-
unparsed_styles.update(styles)
133+
_sync_all()
112134

113-
# Use Rich's Theme class to perform the parsing
114-
parsed_styles = Theme(unparsed_styles).styles
115135

116-
# Perform the in-place update with the results
117-
_THEME.styles.clear()
118-
_THEME.styles.update(parsed_styles)
136+
def _sync_all() -> None:
137+
"""Propagate the global theme to rich-argparse and prompt-toolkit.
138+
139+
If the theme hasn't been initialized yet, this is a no-op.
140+
"""
141+
if _THEME is None:
142+
return
119143

120-
# Synchronize rich-argparse styles with the main application theme.
144+
# Synchronize rich-argparse styles
121145
for name in Cmd2HelpFormatter.styles.keys() & _THEME.styles.keys():
122146
Cmd2HelpFormatter.styles[name] = _THEME.styles[name]
123147

@@ -126,11 +150,16 @@ def set_theme(styles: Mapping[str, StyleType] | None = None) -> None:
126150

127151

128152
def _sync_pt_theme() -> None:
129-
"""Build a new global PT style object based on the current Rich theme."""
130-
theme = get_theme()
153+
"""Build a new global prompt-toolkit style object based on the current Rich theme.
154+
155+
If the theme hasn't been initialized yet, this is a no-op.
156+
"""
157+
if _THEME is None:
158+
return
159+
131160
style_rules: list[tuple[str, str]] = []
132161

133-
for name, rich_style in theme.styles.items():
162+
for name, rich_style in _THEME.styles.items():
134163
# Only synchronize if it has a registered prefix or mapped UI component.
135164
is_framework_style = any(name.startswith(p) for p in _SYNCHRONIZED_PREFIXES)
136165
is_mapped_style = name in _PT_UI_MAP
@@ -149,16 +178,6 @@ def _sync_pt_theme() -> None:
149178
_PT_THEME = PtStyle(style_rules)
150179

151180

152-
def _create_default_theme() -> Theme:
153-
"""Create a default theme for the application.
154-
155-
This theme combines the default styles from cmd2, rich-argparse, and Rich.
156-
"""
157-
app_styles = DEFAULT_CMD2_STYLES.copy()
158-
app_styles.update(DEFAULT_ARGPARSE_STYLES)
159-
return Theme(app_styles, inherit=True)
160-
161-
162181
def register_pt_mapping(style_name: str, pt_ui_names: str | Iterable[str]) -> None:
163182
"""Map a Rich theme style name to one or more prompt-toolkit UI components.
164183

docs/features/generating_output.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ all colors available to your `cmd2` application.
131131

132132
`cmd2` uses a `rich` [Theme](https://rich.readthedocs.io/en/stable/reference/theme.html) object to
133133
define styles for various UI elements. You can define your own custom theme using
134-
[cmd2.rich_utils.set_theme][]. See the
134+
[cmd2.theme.update_theme][]. See the
135135
[rich_theme.py](https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py) example for
136136
more information.
137137

docs/features/theme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Theme
22

33
`cmd2` provides the ability to configure an overall theme for your application using the
4-
[cmd2.rich_utils.set_theme][] function. This is based on the
4+
[cmd2.theme.update_theme][] function. This is based on the
55
[rich.theme](https://rich.readthedocs.io/en/stable/reference/theme.html) container for style
66
information. You can use this to brand your application and set an overall consistent look and feel
77
that is appealing to your user base.

docs/upgrades.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ periodically.
5252
customize its appearance using the `cmd2` theme.
5353

5454
- **Customization**: Override the `Cmd2Style.COMPLETION_MENU_CURRENT` and
55-
`Cmd2Style.COMPLETION_MENU_META` styles using `cmd2.theme.set_theme()`. See
55+
`Cmd2Style.COMPLETION_MENU_META` styles using `cmd2.theme.update_theme()`. See
5656
[Customizing Completion Menu Colors](features/theme.md#customizing-completion-menu-colors) for
5757
more details.
5858

@@ -136,7 +136,7 @@ The new [cmd2.rich_utils][] module provides common utility classes and functions
136136
use of `rich` within `cmd2` applications. Most of what is here is not intended to be user-facing.
137137

138138
The one thing many `cmd2` application developers will likely be interested in using is the
139-
[cmd2.rich_utils.set_theme][] function. See the
139+
[cmd2.theme.update_theme][] function. See the
140140
[rich_theme.py](https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py) example for a
141141
demonstration for how to set a theme (color scheme) for your app.
142142

examples/rich_theme.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rich.style import Style
55

66
import cmd2
7-
from cmd2 import Cmd2Style, Color, set_theme
7+
from cmd2 import Cmd2Style, Color, update_theme
88

99

1010
class ThemedApp(cmd2.Cmd):
@@ -41,7 +41,7 @@ def __init__(self, *args, **kwargs):
4141
"traceback.exc_type": Style(color=Color.RED, bgcolor=Color.LIGHT_YELLOW3, bold=True),
4242
"argparse.args": Style(color=Color.AQUAMARINE3, underline=True),
4343
}
44-
set_theme(custom_theme)
44+
update_theme(custom_theme)
4545

4646
@cmd2.with_category("Theme Commands")
4747
def do_theme_show(self, _: cmd2.Statement):

0 commit comments

Comments
 (0)