Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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,
)
)
Expand Down Expand Up @@ -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,
)
)
Expand Down
9 changes: 6 additions & 3 deletions cmd2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion examples/getting_started.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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 (
Expand Down Expand Up @@ -94,7 +95,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,
)
Expand Down
31 changes: 30 additions & 1 deletion tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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
Loading