From 438ef95f2977c1b0102a856a04fa5a5f4dd0ee3d Mon Sep 17 00:00:00 2001 From: bambu <564972+bambu@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:48:52 -0400 Subject: [PATCH 1/3] Silenced the set command on success by default and consolidated its output. (#1627) * Made the set command silent on success by default. * Added a -v/--verbose flag to the set command to display change confirmations. * Consolidated the verbose output into a single, colorized line (e.g., param: old -> new). * Updated unit tests to reflect the new default behavior and output format. --- cmd2/cmd2.py | 17 +++++++++++++++- tests/test_cmd2.py | 43 ++++++++++++++++------------------------ tests/test_commandset.py | 9 +++------ 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 0d80dd007..dd19007cb 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4685,6 +4685,12 @@ def complete_set_value( def _build_set_parser(cls) -> Cmd2ArgumentParser: # Create the parser for the set command set_parser = cls._build_base_set_parser() + set_parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="show the change", + ) set_parser.add_argument( "value", nargs=argparse.OPTIONAL, @@ -4720,7 +4726,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}") + if args.verbose: + feedback_msg = Text.assemble( + args.param, + ": ", + (f"{orig_value!r}", "red"), + " -> ", + (f"{settable.value!r}", "green"), + ) + self.poutput(feedback_msg) + self.last_result = True return diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 39aad2d27..b10a6849c 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -121,7 +121,7 @@ def test_base_argparse_help(base_app) -> None: def test_base_invalid_option(base_app) -> None: _out, err = run_cmd(base_app, "set -z") - assert err[0] == "Usage: set [-h] [param] [value]" + assert err[0] == "Usage: set [-h] [-v] [param] [value]" assert "Error: unrecognized arguments: -z" in err[1] @@ -161,26 +161,27 @@ def test_base_set(base_app) -> None: def test_set(base_app) -> None: + # Test silent by default out, _err = run_cmd(base_app, "set quiet True") - expected = normalize( - """ -quiet - was: False -now: True -""" - ) + assert not out + assert base_app.last_result is True + + # Test verbose + out, _err = run_cmd(base_app, "set -v quiet False") + expected = ["quiet: True -> False"] assert out == expected assert base_app.last_result is True line_found = False out, _err = run_cmd(base_app, "set quiet") for line in out: - if "quiet" in line and "True" in line and "False" not in line: + if "quiet" in line and "False" in line and "True" not in line: line_found = True break assert line_found assert len(base_app.last_result) == 1 - assert base_app.last_result["quiet"] is True + assert base_app.last_result["quiet"] is False def test_set_val_empty(base_app) -> None: @@ -231,7 +232,7 @@ def test_set_no_settables(base_app) -> None: @with_ansi_style(ru.AllowStyle.TERMINAL) def test_set_allow_style(base_app, new_val, is_valid, expected) -> None: # Use the set command to alter allow_style - out, err = run_cmd(base_app, f"set allow_style {new_val}") + out, err = run_cmd(base_app, f"set -v allow_style {new_val}") assert base_app.last_result is is_valid # Verify the results @@ -276,12 +277,11 @@ def onchange_app(): def test_set_onchange_hook(onchange_app) -> None: - out, _err = run_cmd(onchange_app, "set quiet True") + out, _err = run_cmd(onchange_app, "set -v quiet True") expected = normalize( """ You changed quiet -quiet - was: False -now: True +quiet: False -> True """ ) assert out == expected @@ -874,12 +874,8 @@ 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 -""" - ) + out, err = run_cmd(base_app, "set -v timing True") + expected = ["timing: False -> True"] assert out == expected if sys.platform == "win32": @@ -898,13 +894,8 @@ def test_base_debug(base_app) -> None: assert "To enable full traceback" in err[3] # Set debug true - out, err = run_cmd(base_app, "set debug True") - expected = normalize( - """ -debug - was: False -now: True -""" - ) + out, err = run_cmd(base_app, "set -v debug True") + expected = ["debug: False -> True"] assert out == expected # Verify that we now see the exception traceback diff --git a/tests/test_commandset.py b/tests/test_commandset.py index 72d31e52e..03f7ca91d 100644 --- a/tests/test_commandset.py +++ b/tests/test_commandset.py @@ -1102,12 +1102,9 @@ def __init__(self) -> None: any("arbitrary_value" in line and "5" in line for line in out) # 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) + out, err = run_cmd(app, "set -v arbitrary_value 10") + expected = ["arbitrary_value: 5 -> 10"] + assert out == expected out, err = run_cmd(app, "set arbitrary_value") any("arbitrary_value" in line and "10" in line for line in out) From 45f17c459acb52ebb972e14e90bd919cc774d61f Mon Sep 17 00:00:00 2001 From: bambu <564972+bambu@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:04:34 -0400 Subject: [PATCH 2/3] Changed set command output unless quiet mode is enabled. * Updated do_set to use pfeedback for change confirmations, allowing it to be silenced via the quiet setting. * Updated unit tests to match the new output format and verify quiet mode behavior. --- CHANGELOG.md | 3 +++ cmd2/cmd2.py | 24 +++++++---------- tests/test_cmd2.py | 58 +++++++++++++++++++--------------------- tests/test_commandset.py | 6 ++--- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e24f2006..8b8b0b720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index dd19007cb..6cd333359 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4685,12 +4685,6 @@ def complete_set_value( def _build_set_parser(cls) -> Cmd2ArgumentParser: # Create the parser for the set command set_parser = cls._build_base_set_parser() - set_parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="show the change", - ) set_parser.add_argument( "value", nargs=argparse.OPTIONAL, @@ -4726,15 +4720,15 @@ def do_set(self, args: argparse.Namespace) -> None: except ValueError as ex: self.perror(f"Error setting {args.param}: {ex}") else: - if args.verbose: - feedback_msg = Text.assemble( - args.param, - ": ", - (f"{orig_value!r}", "red"), - " -> ", - (f"{settable.value!r}", "green"), - ) - self.poutput(feedback_msg) + # 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) self.last_result = True return diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index b10a6849c..4ae967498 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -121,7 +121,7 @@ def test_base_argparse_help(base_app) -> None: def test_base_invalid_option(base_app) -> None: _out, err = run_cmd(base_app, "set -z") - assert err[0] == "Usage: set [-h] [-v] [param] [value]" + assert err[0] == "Usage: set [-h] [param] [value]" assert "Error: unrecognized arguments: -z" in err[1] @@ -161,27 +161,28 @@ def test_base_set(base_app) -> None: def test_set(base_app) -> None: - # Test silent by default - out, _err = run_cmd(base_app, "set quiet True") + out, err = run_cmd(base_app, "set quiet True") assert not out assert base_app.last_result is True - # Test verbose - out, _err = run_cmd(base_app, "set -v quiet False") - expected = ["quiet: True -> False"] - assert out == expected + # 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: - if "quiet" in line and "False" in line and "True" not in line: + if "quiet" in line and "True" in line and "False" not in line: line_found = True break assert line_found assert len(base_app.last_result) == 1 - assert base_app.last_result["quiet"] is False + assert base_app.last_result["quiet"] is True + base_app.quiet = False def test_set_val_empty(base_app) -> None: @@ -232,14 +233,14 @@ def test_set_no_settables(base_app) -> None: @with_ansi_style(ru.AllowStyle.TERMINAL) def test_set_allow_style(base_app, new_val, is_valid, expected) -> None: # Use the set command to alter allow_style - out, err = run_cmd(base_app, f"set -v allow_style {new_val}") + out, err = run_cmd(base_app, f"set allow_style {new_val}") assert base_app.last_result is is_valid # 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: @@ -251,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") @@ -277,14 +279,10 @@ def onchange_app(): def test_set_onchange_hook(onchange_app) -> None: - out, _err = run_cmd(onchange_app, "set -v quiet True") - expected = normalize( - """ -You changed quiet -quiet: False -> 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 @@ -874,14 +872,14 @@ 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 -v timing True") - expected = ["timing: False -> True"] - assert out == expected + out, err = run_cmd(base_app, "set timing True") + 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: @@ -894,9 +892,9 @@ def test_base_debug(base_app) -> None: assert "To enable full traceback" in err[3] # Set debug true - out, err = run_cmd(base_app, "set -v debug True") - expected = ["debug: False -> True"] - assert out == expected + out, err = run_cmd(base_app, "set debug True") + assert not out + assert err == ["debug: False -> True"] # Verify that we now see the exception traceback out, err = run_cmd(base_app, "edit") diff --git a/tests/test_commandset.py b/tests/test_commandset.py index 03f7ca91d..a9478f997 100644 --- a/tests/test_commandset.py +++ b/tests/test_commandset.py @@ -1102,9 +1102,9 @@ def __init__(self) -> None: any("arbitrary_value" in line and "5" in line for line in out) # change the value and verify the value changed - out, err = run_cmd(app, "set -v arbitrary_value 10") - expected = ["arbitrary_value: 5 -> 10"] - assert out == expected + out, err = run_cmd(app, "set arbitrary_value 10") + 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) From 70480abbfa6c17d8ffddbd93d8c1f790a15c29d9 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Mon, 27 Apr 2026 11:35:56 -0400 Subject: [PATCH 3/3] Attempt at a compromise format that is split over 2 lines New format looks like: ```sh myapp> set allow_style always allow_style: 'Terminal' -> 'Always' ``` Where "'Terminal'" is red and "'Always'" is green. This is still more abbreviated than the original format, but not as abbreviated as putting everything on one line. Moreover, it preserves the more colorful diff-style display and the switch to using pfeedback. --- cmd2/cmd2.py | 4 +++- tests/test_cmd2.py | 13 ++++++++----- tests/test_commandset.py | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index abf0981fa..20d00acd1 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4732,7 +4732,9 @@ def do_set(self, args: argparse.Namespace) -> None: args.param, ": ", (f"{orig_value!r}", "red"), - " -> ", + "\n", + " " * max(0, len(args.param) - 1), + "-> ", (f"{settable.value!r}", "green"), ) self.pfeedback(feedback_msg) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 65892c57c..3e29c7154 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -273,7 +273,8 @@ def test_set_with_choices(base_app) -> None: out, err = run_cmd(base_app, f"set fake {fake_choices[1]}") assert base_app.last_result is True assert not out - assert err == [f"fake: {fake_choices[0]!r} -> {fake_choices[1]!r}"] + assert err[0].startswith(f"fake: {fake_choices[0]!r}") + assert err[1].endswith(f"-> {fake_choices[1]!r}") # Try an invalid choice _out, err = run_cmd(base_app, "set fake bad_value") @@ -892,12 +893,13 @@ def test_base_timing(base_app) -> None: base_app.feedback_to_output = False out, err = run_cmd(base_app, "set timing True") assert not out - assert err[0] == "timing: False -> True" + assert err[0].startswith("timing: False") + assert err[1].endswith("-> True") if sys.platform == "win32": - assert err[1].startswith("Elapsed: 0:00:00") + assert err[2].startswith("Elapsed: 0:00:00") else: - assert err[1].startswith("Elapsed: 0:00:00.0") + assert err[2].startswith("Elapsed: 0:00:00.0") def test_base_debug(base_app) -> None: @@ -912,7 +914,8 @@ def test_base_debug(base_app) -> None: # Set debug true out, err = run_cmd(base_app, "set debug True") assert not out - assert err == ["debug: False -> True"] + assert err[0].startswith("debug: False") + assert err[1].endswith("-> True") # Verify that we now see the exception traceback out, err = run_cmd(base_app, "edit") diff --git a/tests/test_commandset.py b/tests/test_commandset.py index a9478f997..34ae2ef8a 100644 --- a/tests/test_commandset.py +++ b/tests/test_commandset.py @@ -1104,7 +1104,8 @@ def __init__(self) -> None: # change the value and verify the value changed out, err = run_cmd(app, "set arbitrary_value 10") assert not out - assert err == ["arbitrary_value: 5 -> 10"] + assert err[0].startswith("arbitrary_value: 5") + assert err[1].endswith("-> 10") out, err = run_cmd(app, "set arbitrary_value") any("arbitrary_value" in line and "10" in line for line in out)