diff --git a/CHANGELOG.md b/CHANGELOG.md index 6600db7e3..8f594983f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## (TBA) + +- Enhancements + - Added possibility to use rich Text objects to set the description argument of a `Settable` + ## 4.1.1 (July 9, 2026) - Bug Fixes diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d6adbfbd8..c8653d4e1 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1382,7 +1382,7 @@ def allow_style_type(value: str) -> ru.AllowStyle: Settable( "allow_style", allow_style_type, - ru.rich_text_to_string(settable_description), + settable_description, self, choices_provider=get_allow_style_choices, ) @@ -1399,7 +1399,7 @@ def allow_style_type(value: str) -> ru.AllowStyle: Settable( "editor", str, - ru.rich_text_to_string(editor_description), + editor_description, self, ) ) @@ -1434,7 +1434,7 @@ def allow_style_type(value: str) -> ru.AllowStyle: Settable( "traceback_width", utils.optional_int, - ru.rich_text_to_string(traceback_width_description), + traceback_width_description, self, ) ) diff --git a/cmd2/utils.py b/cmd2/utils.py index 250f353f9..a0c8d9067 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -25,7 +25,10 @@ cast, ) +from rich.text import Text + from . import constants +from . import rich_utils as ru from . import string_utils as su from .types import ( CmdOrSet, @@ -88,7 +91,7 @@ def __init__( self, name: str, val_type: type[Any] | Callable[[Any], Any], - description: str, + description: str | Text, settable_object: object, *, settable_attrib_name: str | None = None, @@ -108,7 +111,7 @@ def __init__( input is a valid integer. Specifying bool automatically provides completion for 'true' and 'false' and uses a built-in function for conversion and validation. - :param description: A concise string that describes the purpose of this setting. + :param description: A concise string or rich Text object that describes the purpose of this setting. :param settable_object: The object that owns the attribute being made settable (e.g. self). :param settable_attrib_name: The name of the attribute on the settable_object that will be modified. This defaults to the value of the name @@ -142,7 +145,7 @@ def get_bool_choices(_cmd2_self: CmdOrSet) -> Choices: self.name = name self.val_type = val_type - self.description = description + self.description = ru.rich_text_to_string(description) if isinstance(description, Text) else description self.settable_obj = settable_object self.settable_attrib_name = settable_attrib_name if settable_attrib_name is not None else name self.onchange_cb = onchange_cb diff --git a/examples/getting_started.py b/examples/getting_started.py index 77a07d4e1..7f79d7bd7 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -30,6 +30,7 @@ from prompt_toolkit.application import get_app from prompt_toolkit.formatted_text import AnyFormattedText from rich.style import Style +from rich.text import Text import cmd2 from cmd2 import ( @@ -89,7 +90,16 @@ def __init__(self) -> None: cmd2.Settable( "foreground_color", str, - "Foreground color to use with echo command", + Text.assemble( + "Foreground color to use with echo command ", + "(Options: ", + Text("Green", Style(color=Color.GREEN)), + ", ", + Text("Red", Style(color=Color.RED)), + ", ", + Text("Blue", Style(color=Color.BLUE)), + ", ...)", + ), self, choices=fg_colors, ) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index e55197bc3..879304ca9 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -18,6 +18,7 @@ from prompt_toolkit.input import DummyInput, create_pipe_input from prompt_toolkit.output import DummyOutput from prompt_toolkit.shortcuts import PromptSession +from rich.style import Style from rich.text import Text import cmd2 @@ -352,6 +353,31 @@ def test_set_with_choices(base_app) -> None: assert err[0].startswith("Error setting fake: invalid choice") +def test_set_with_rich_description(base_app) -> None: + """Test with rich Text description.""" + description = "Rich text description" + base_app.rich_settable = "old" + rich_settable = cmd2.Settable( + "rich_settable", type(base_app.rich_settable), Text(description, Style(color="red", bold=True)), base_app + ) + base_app.add_settable(rich_settable) + + # Try see list settables info + out, err = run_cmd(base_app, "set rich_settable") + assert not err + name, value, text = out[-1].strip().split(maxsplit=2) + assert name == "rich_settable" + assert value == "old" + assert text == description + + # Try to set a valid value + out, err = run_cmd(base_app, "set rich_settable new") + assert base_app.last_result is True + assert not err + assert out[0].startswith("rich_settable") + assert out[0].endswith("─> 'new'") + + class OnChangeHookApp(cmd2.Cmd): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -4304,7 +4330,6 @@ def test_completekey_empty_string() -> None: def test_create_main_session_exception(monkeypatch): - # Mock PromptSession to raise ValueError on first call, then succeed valid_session_mock = mock.MagicMock(spec=PromptSession) mock_session = mock.MagicMock(side_effect=[ValueError, valid_session_mock]) @@ -4727,3 +4752,7 @@ def do_base(self, _: argparse.Namespace) -> None: root_parser = cast(cmd2.Cmd2ArgumentParser, app.command_parsers.get(app.do_base)) subparsers_action = root_parser.get_subparsers_action() assert not subparsers_action._name_parser_map + subparsers_action = root_parser.get_subparsers_action() + assert not subparsers_action._name_parser_map + subparsers_action = root_parser.get_subparsers_action() + assert not subparsers_action._name_parser_map