Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ prompt is displayed.
specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
the property.
- Updated `set` command to consolidate its confirmation output into a single, colorized line.
The confirmation now uses `pfeedback()`, allowing it to be silenced when the `quiet` settable
is enabled.

## 3.5.0 (April 13, 2026)

Expand Down
11 changes: 10 additions & 1 deletion cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4721,7 +4721,16 @@ def do_set(self, args: argparse.Namespace) -> None:
except ValueError as ex:
self.perror(f"Error setting {args.param}: {ex}")
else:
self.poutput(f"{args.param} - was: {orig_value!r}\nnow: {settable.value!r}")
# Create the feedback message using Rich Text for color
feedback_msg = Text.assemble(
args.param,
": ",
(f"{orig_value!r}", "red"),
" -> ",
(f"{settable.value!r}", "green"),
)
self.pfeedback(feedback_msg)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see 3 fundamental distinct changes here:

  1. Output is more abbreviated - 1 line instead of 2
  2. Previous and new values are colorized to enhance readability as a diff
  3. pfeedback is used instead of poutput

In general I think the later two are unquestionably welcome changes.

For most use cases I like the more succinct single-line output format. However, I can envision a theoretical edge case where both the old and new values are many characters such that fitting on a single line might be problematic. I'd be fine with a 2-line solution or with an intelligent splitting based on terminal width.

self.last_result = True
return

Expand Down
61 changes: 25 additions & 36 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,17 @@ def test_base_set(base_app) -> None:


def test_set(base_app) -> None:
out, _err = run_cmd(base_app, "set quiet True")
expected = normalize(
"""
quiet - was: False
now: True
"""
)
assert out == expected
out, err = run_cmd(base_app, "set quiet True")
assert not out
assert base_app.last_result is True

# Test quiet respect
out, err = run_cmd(base_app, "set timing False")
assert not out
assert not err
assert base_app.last_result is True

# Show one settable (this always goes to out)
line_found = False
out, _err = run_cmd(base_app, "set quiet")
for line in out:
Expand All @@ -181,6 +182,7 @@ def test_set(base_app) -> None:
assert line_found
assert len(base_app.last_result) == 1
assert base_app.last_result["quiet"] is True
base_app.quiet = False


def test_set_val_empty(base_app) -> None:
Expand Down Expand Up @@ -237,8 +239,8 @@ def test_set_allow_style(base_app, new_val, is_valid, expected) -> None:
# Verify the results
assert expected == ru.ALLOW_STYLE
if is_valid:
assert not err
assert out
assert err
assert not out


def test_set_with_choices(base_app) -> None:
Expand All @@ -250,9 +252,10 @@ def test_set_with_choices(base_app) -> None:
base_app.add_settable(fake_settable)

# Try a valid choice
_out, err = run_cmd(base_app, f"set fake {fake_choices[1]}")
out, err = run_cmd(base_app, f"set fake {fake_choices[1]}")
assert base_app.last_result is True
assert not err
assert not out
assert err == [f"fake: {fake_choices[0]!r} -> {fake_choices[1]!r}"]

# Try an invalid choice
_out, err = run_cmd(base_app, "set fake bad_value")
Expand All @@ -276,15 +279,10 @@ def onchange_app():


def test_set_onchange_hook(onchange_app) -> None:
out, _err = run_cmd(onchange_app, "set quiet True")
expected = normalize(
"""
You changed quiet
quiet - was: False
now: True
"""
)
assert out == expected
out, err = run_cmd(onchange_app, "set quiet True")
assert out == ["You changed quiet"]
# quiet: False -> True is not shown because quiet is now True
assert not err
assert onchange_app.last_result is True


Expand Down Expand Up @@ -875,17 +873,13 @@ def test_allow_clipboard(base_app) -> None:
def test_base_timing(base_app) -> None:
base_app.feedback_to_output = False
out, err = run_cmd(base_app, "set timing True")
expected = normalize(
"""timing - was: False
now: True
"""
)
assert out == expected
assert not out
assert err[0] == "timing: False -> True"

if sys.platform == "win32":
assert err[0].startswith("Elapsed: 0:00:00")
assert err[1].startswith("Elapsed: 0:00:00")
else:
assert err[0].startswith("Elapsed: 0:00:00.0")
assert err[1].startswith("Elapsed: 0:00:00.0")


def test_base_debug(base_app) -> None:
Expand All @@ -899,13 +893,8 @@ def test_base_debug(base_app) -> None:

# Set debug true
out, err = run_cmd(base_app, "set debug True")
expected = normalize(
"""
debug - was: False
now: True
"""
)
assert out == expected
assert not out
assert err == ["debug: False -> True"]

# Verify that we now see the exception traceback
out, err = run_cmd(base_app, "edit")
Expand Down
7 changes: 2 additions & 5 deletions tests/test_commandset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,11 +1103,8 @@ def __init__(self) -> None:

# change the value and verify the value changed
out, err = run_cmd(app, "set arbitrary_value 10")
expected = """
arbitrary_value - was: 5
now: 10
"""
assert out == normalize(expected)
assert not out
assert err == ["arbitrary_value: 5 -> 10"]
out, err = run_cmd(app, "set arbitrary_value")
any("arbitrary_value" in line and "10" in line for line in out)

Expand Down
Loading