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/8] 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/8] 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/8] 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) From 46dd594f3af0e836e73e2790bde22a34eca20ec6 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Tue, 5 May 2026 23:30:42 -0400 Subject: [PATCH 4/8] Update alias and macro create/delete to use pfeedback for non-essential success cases --- cmd2/cmd2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index c55760b8e..864edce96 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -3862,7 +3862,7 @@ def _alias_create(self, args: argparse.Namespace) -> None: # Set the alias result = "overwritten" if args.name in self.aliases else "created" - self.poutput(f"Alias '{args.name}' {result}") + self.pfeedback(f"Alias '{args.name}' {result}") self.aliases[args.name] = value self.last_result = True @@ -3891,7 +3891,7 @@ def _alias_delete(self, args: argparse.Namespace) -> None: if args.all: self.aliases.clear() - self.poutput("All aliases deleted") + self.pfeedback("All aliases deleted") elif not args.names: self.perror("Either --all or alias name(s) must be specified") self.last_result = False @@ -3899,7 +3899,7 @@ def _alias_delete(self, args: argparse.Namespace) -> None: for cur_name in utils.remove_duplicates(args.names): if cur_name in self.aliases: del self.aliases[cur_name] - self.poutput(f"Alias '{cur_name}' deleted") + self.pfeedback(f"Alias '{cur_name}' deleted") else: self.perror(f"Alias '{cur_name}' does not exist") @@ -4150,7 +4150,7 @@ def _macro_create(self, args: argparse.Namespace) -> None: # Set the macro result = "overwritten" if args.name in self.macros else "created" - self.poutput(f"Macro '{args.name}' {result}") + self.pfeedback(f"Macro '{args.name}' {result}") self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, args=macro_args) self.last_result = True @@ -4179,7 +4179,7 @@ def _macro_delete(self, args: argparse.Namespace) -> None: if args.all: self.macros.clear() - self.poutput("All macros deleted") + self.pfeedback("All macros deleted") elif not args.names: self.perror("Either --all or macro name(s) must be specified") self.last_result = False @@ -4187,7 +4187,7 @@ def _macro_delete(self, args: argparse.Namespace) -> None: for cur_name in utils.remove_duplicates(args.names): if cur_name in self.macros: del self.macros[cur_name] - self.poutput(f"Macro '{cur_name}' deleted") + self.pfeedback(f"Macro '{cur_name}' deleted") else: self.perror(f"Macro '{cur_name}' does not exist") From af0d8c46f3743d21bc231ffd7acb5b9fab2928d0 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Tue, 5 May 2026 23:50:57 -0400 Subject: [PATCH 5/8] Eliminate feedback_to_output settable and have pfeedback always print to stdout --- CHANGELOG.md | 4 +++ cmd2/cmd2.py | 47 +++++++++--------------------- docs/features/builtin_commands.md | 1 - docs/features/generating_output.md | 12 ++++---- docs/features/initialization.md | 1 - docs/features/settings.md | 13 ++------- tests/test_cmd2.py | 20 +------------ 7 files changed, 27 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d3ecd41a..6b353cc00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,8 @@ prompt is displayed. treated as command output and sent to `self.stdout`, allowing them to be captured. - Verbose help table descriptions are no longer generated from help function output. The system now relies exclusively on command function docstrings. + - Removed `feedback_to_output` settable and changed `cmd2.Cmd.pfeedback` to always print to + `self.stdout` - Enhancements - New `cmd2.Cmd` parameters - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These @@ -166,6 +168,8 @@ prompt is displayed. - 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. + - `alias` and `macro` subcommands for `create` and `delete` now output their non-essential + success case output using `pfeedback` ## 3.5.1 (April 24, 2026) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 864edce96..322365245 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -474,7 +474,6 @@ def __init__( self.debug = False self.echo = False self.editor = self.DEFAULT_EDITOR - self.feedback_to_output = False # Do not include nonessentials in >, | output by default (things like timing) self.quiet = False # Do not suppress nonessential output self.scripts_add_to_history = True # Scripts and pyscripts add commands to history self.timing = False # Prints elapsed time for each command @@ -1370,7 +1369,6 @@ def allow_style_type(value: str) -> ru.AllowStyle: self.add_settable(Settable("debug", bool, "Show full traceback on exception", self)) self.add_settable(Settable("echo", bool, "Echo command issued into output", self)) self.add_settable(Settable("editor", str, "Program used by 'edit'", self)) - self.add_settable(Settable("feedback_to_output", bool, "Include nonessentials in '|' and '>' results", self)) self.add_settable( Settable( "max_completion_table_items", @@ -1754,40 +1752,23 @@ def pfeedback( rich_print_kwargs: Mapping[str, Any] | None = None, **kwargs: Any, # noqa: ARG002 ) -> None: - """Print nonessential feedback. - - The output can be silenced with the `quiet` setting and its inclusion in redirected output - is controlled by the `feedback_to_output` setting. + """Print nonessential feedback where the output can be silenced with the `quiet` setting. For details on the parameters, refer to the `print_to` method documentation. """ if not self.quiet: - if self.feedback_to_output: - self.poutput( - *objects, - sep=sep, - end=end, - style=style, - soft_wrap=soft_wrap, - justify=justify, - emoji=emoji, - markup=markup, - highlight=highlight, - rich_print_kwargs=rich_print_kwargs, - ) - else: - self.perror( - *objects, - sep=sep, - end=end, - style=style, - soft_wrap=soft_wrap, - justify=justify, - emoji=emoji, - markup=markup, - highlight=highlight, - rich_print_kwargs=rich_print_kwargs, - ) + self.poutput( + *objects, + sep=sep, + end=end, + style=style, + soft_wrap=soft_wrap, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + rich_print_kwargs=rich_print_kwargs, + ) def ppaged( self, @@ -2992,7 +2973,7 @@ def onecmd_plus_hooks( stop = self.postcmd(stop, statement) if self.timing: - self.pfeedback(f"Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}") + self.perror(f"Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}", style=None) finally: # Get sigint protection while we restore stuff with self.sigint_protection: diff --git a/docs/features/builtin_commands.md b/docs/features/builtin_commands.md index ee92ec201..8957a1f0f 100644 --- a/docs/features/builtin_commands.md +++ b/docs/features/builtin_commands.md @@ -83,7 +83,6 @@ application: debug False Show full traceback on exception echo False Echo command issued into output editor vim Program used by 'edit' - feedback_to_output False Include nonessentials in '|' and '>' results max_column_completion_results 7 Maximum number of completion results to display in a single column max_completion_table_items 50 Maximum number of completion results allowed for a completion table to appear quiet False Don't print nonessential feedback diff --git a/docs/features/generating_output.md b/docs/features/generating_output.md index b93b7c2b9..e69c28bbd 100644 --- a/docs/features/generating_output.md +++ b/docs/features/generating_output.md @@ -84,15 +84,15 @@ output. You may have the need to display information to the user which is not intended to be part of the generated output. This could be debugging information or status information about the progress of -long running commands. It's not output, it's not error messages, it's feedback. If you use the +long running commands. It's not output, it's not error messages, it's status. If you use the [Timing](./settings.md#timing) setting, the output of how long it took the command to run will be -output as feedback. You can use the [pfeedback][cmd2.Cmd.pfeedback] method to produce this type of -output, and several [Settings](./settings.md) control how it is handled. +output as to `stderr` without any styling. You can use the [perror][cmd2.Cmd.perror] method to +produce this type of output by passing it `style=None`. If the [quiet](./settings.md#quiet) setting is `True`, then calling `cmd2.Cmd.pfeedback` produces no -output. If [quiet](./settings.md#quiet) is `False`, the -[feedback_to_output](./settings.md#feedback_to_output) setting is consulted to determine whether to -send the output to `stdout` or `stderr`. +output. If [quiet](./settings.md#quiet) is `False`,the `pfeedback` method sends output to `stdout`. +Hence, `pfeedback` is useful for non-essential output that you want the ability to silence when +`quiet` is `True`. ## Exceptions diff --git a/docs/features/initialization.md b/docs/features/initialization.md index c1ad6e2c0..f15cd613a 100644 --- a/docs/features/initialization.md +++ b/docs/features/initialization.md @@ -43,7 +43,6 @@ Here are instance attributes of `cmd2.Cmd` which developers might wish to overri - **editor**: text editor program to use with _edit_ command (e.g. `vim`) - **exclude_from_history**: commands to exclude from the _history_ command - **exit_code**: this determines the value returned by `cmdloop()` when exiting the application -- **feedback_to_output**: if `True`, send nonessential output to stdout, if `False` send them to stderr (Default: `False`) - **help_error**: the error that prints when no help information can be found - **hidden_commands**: commands to exclude from the help menu and tab completion - **last_result**: stores results from the last command run to enable usage of results in a Python script or interactive console. Built-in commands don't make use of this. It is purely there for user-defined commands and convenience. diff --git a/docs/features/settings.md b/docs/features/settings.md index e0eefe25a..568356686 100644 --- a/docs/features/settings.md +++ b/docs/features/settings.md @@ -54,15 +54,6 @@ the prompt. Similar to the `EDITOR` shell variable, this setting contains the name of the program which should be run by the [edit](./builtin_commands.md#edit) command. -### feedback_to_output - -Controls whether feedback generated with the `cmd2.Cmd.pfeedback` method is sent to `self.stdout` or -`sys.stderr`. If `False` the output will be sent to `sys.stderr` - -If `True` the output is sent to `stdout` (which is often the screen but may be -[redirected](./redirection.md#output-redirection-and-pipes)). The feedback output will be mixed in -with and indistinguishable from output generated with `cmd2.Cmd.poutput`. - ### max_completion_table_items The maximum number of items to display in a completion table. A completion table is a special kind @@ -74,8 +65,8 @@ appear. ### quiet -If `True`, output generated by calling `cmd2.Cmd.pfeedback` is suppressed. If `False`, the -[feedback_to_output](#feedback_to_output) setting controls where the output is sent. +If `True`, output generated by calling `cmd2.Cmd.pfeedback` is suppressed. If `False`, the output is +sent to `stdout`. ### scripts_add_to_history diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 45e4c1c73..e164f6b39 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -726,8 +726,7 @@ def test_output_redirection_to_too_long_filename(redirection_app) -> None: assert "Failed to redirect" in err[0] -def test_feedback_to_output_true(redirection_app) -> None: - redirection_app.feedback_to_output = True +def test_feedback(redirection_app) -> None: f, filename = tempfile.mkstemp(prefix="cmd2_test", suffix=".txt") os.close(f) @@ -740,22 +739,6 @@ def test_feedback_to_output_true(redirection_app) -> None: os.remove(filename) -def test_feedback_to_output_false(redirection_app) -> None: - redirection_app.feedback_to_output = False - f, filename = tempfile.mkstemp(prefix="feedback_to_output", suffix=".txt") - os.close(f) - - try: - _out, err = run_cmd(redirection_app, f"print_feedback > {filename}") - - with open(filename) as f: - content = f.read().splitlines() - assert not content - assert "feedback" in err - finally: - os.remove(filename) - - def test_disallow_redirection(redirection_app: RedirectionApp, capsys: pytest.CaptureFixture[str]) -> None: # Set allow_redirection to False redirection_app.allow_redirection = False @@ -872,7 +855,6 @@ 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") assert not out assert err[0].startswith("timing: False") From 962f8e9db66015a3f800c7c0d574ee69aca18285 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 6 May 2026 00:00:38 -0400 Subject: [PATCH 6/8] Changed output of set to fit on a single line when setting a new value --- cmd2/cmd2.py | 8 +------- tests/test_cmd2.py | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 322365245..4f67f999a 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4718,19 +4718,13 @@ def do_set(self, args: argparse.Namespace) -> None: if args.value: # Try to update the settable's value try: - orig_value = settable.value settable.value = su.strip_quotes(args.value) except ValueError as ex: self.perror(f"Error setting {args.param}: {ex}") else: # Create the feedback message using Rich Text for color feedback_msg = Text.assemble( - args.param, - ": ", - (f"{orig_value!r}", "red"), - "\n", - " " * max(0, len(args.param) - 1), - "-> ", + f"{args.param} -> ", (f"{settable.value!r}", "green"), ) self.pfeedback(feedback_msg) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index e164f6b39..794e819f7 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -238,8 +238,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 err - assert not out + assert out + assert not err def test_set_traceback_show_locals(base_app: cmd2.Cmd) -> None: From 9105ed2e10273d42e004b7d599cd6cc14372c20b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 6 May 2026 00:10:48 -0400 Subject: [PATCH 7/8] Fixed tests based on updates --- tests/test_cmd2.py | 21 ++++++++++----------- tests/test_commandset.py | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 794e819f7..f32ce8ea4 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -279,9 +279,9 @@ def test_set_with_choices(base_app) -> None: # Try a valid choice out, err = run_cmd(base_app, f"set fake {fake_choices[1]}") assert base_app.last_result is True - assert not out - assert err[0].startswith(f"fake: {fake_choices[0]!r}") - assert err[1].endswith(f"-> {fake_choices[1]!r}") + assert not err + assert out[0].startswith("fake") + assert out[0].endswith(f"-> {fake_choices[1]!r}") # Try an invalid choice _out, err = run_cmd(base_app, "set fake bad_value") @@ -856,14 +856,13 @@ def test_allow_clipboard(base_app) -> None: def test_base_timing(base_app) -> None: out, err = run_cmd(base_app, "set timing True") - assert not out - assert err[0].startswith("timing: False") - assert err[1].endswith("-> True") + assert out[0].startswith("timing") + assert out[0].endswith("-> True") if sys.platform == "win32": - assert err[2].startswith("Elapsed: 0:00:00") + assert err[0].startswith("Elapsed: 0:00:00") else: - assert err[2].startswith("Elapsed: 0:00:00.0") + assert err[0].startswith("Elapsed: 0:00:00.0") def test_base_debug(base_app) -> None: @@ -877,9 +876,9 @@ def test_base_debug(base_app) -> None: # Set debug true out, err = run_cmd(base_app, "set debug True") - assert not out - assert err[0].startswith("debug: False") - assert err[1].endswith("-> True") + assert not err + assert out[0].startswith("debug") + assert out[0].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 34ae2ef8a..f4d20a906 100644 --- a/tests/test_commandset.py +++ b/tests/test_commandset.py @@ -1103,9 +1103,9 @@ 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[0].startswith("arbitrary_value: 5") - assert err[1].endswith("-> 10") + assert not err + assert out[0].startswith("arbitrary_value") + assert out[0].endswith("-> 10") out, err = run_cmd(app, "set arbitrary_value") any("arbitrary_value" in line and "10" in line for line in out) From 70051de09cf9ed46418d8379c7be82627ce96726 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 6 May 2026 01:24:58 -0400 Subject: [PATCH 8/8] Using nicer looking arrow in 'set' output. --- cmd2/cmd2.py | 4 ++-- tests/test_cmd2.py | 6 +++--- tests/test_commandset.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 4f67f999a..672d877f4 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4009,7 +4009,7 @@ def _build_macro_create_parser(cls) -> Cmd2ArgumentParser: "When the macro is called, the provided arguments are resolved and the assembled command is run. For example:", "\n\n", (" my_macro beef broccoli", Cmd2Style.COMMAND_LINE), - (" ───> ", Style(bold=True)), + (" ─> ", Style(bold=True)), ("make_dinner --meat beef --veggie broccoli", Cmd2Style.COMMAND_LINE), ) macro_create_parser = argparse_utils.DEFAULT_ARGUMENT_PARSER(description=macro_create_description) @@ -4724,7 +4724,7 @@ def do_set(self, args: argparse.Namespace) -> None: else: # Create the feedback message using Rich Text for color feedback_msg = Text.assemble( - f"{args.param} -> ", + f"{args.param} ─> ", (f"{settable.value!r}", "green"), ) self.pfeedback(feedback_msg) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index f32ce8ea4..8fc0230a2 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -281,7 +281,7 @@ def test_set_with_choices(base_app) -> None: assert base_app.last_result is True assert not err assert out[0].startswith("fake") - assert out[0].endswith(f"-> {fake_choices[1]!r}") + assert out[0].endswith(f"─> {fake_choices[1]!r}") # Try an invalid choice _out, err = run_cmd(base_app, "set fake bad_value") @@ -857,7 +857,7 @@ def test_allow_clipboard(base_app) -> None: def test_base_timing(base_app) -> None: out, err = run_cmd(base_app, "set timing True") assert out[0].startswith("timing") - assert out[0].endswith("-> True") + assert out[0].endswith("─> True") if sys.platform == "win32": assert err[0].startswith("Elapsed: 0:00:00") @@ -878,7 +878,7 @@ def test_base_debug(base_app) -> None: out, err = run_cmd(base_app, "set debug True") assert not err assert out[0].startswith("debug") - assert out[0].endswith("-> True") + assert out[0].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 f4d20a906..66b1d1885 100644 --- a/tests/test_commandset.py +++ b/tests/test_commandset.py @@ -1105,7 +1105,7 @@ def __init__(self) -> None: out, err = run_cmd(app, "set arbitrary_value 10") assert not err assert out[0].startswith("arbitrary_value") - assert out[0].endswith("-> 10") + assert out[0].endswith("─> 10") out, err = run_cmd(app, "set arbitrary_value") any("arbitrary_value" in line and "10" in line for line in out)