Skip to content

Commit 522ce2e

Browse files
laf0rgekmvanbrunt
andauthored
Added settable called scripts_add_to_history which determines whether scripts and pyscripts (#1291)
add commands to history. Co-authored-by: Kevin Van Brunt <kmvanbrunt@gmail.com>
1 parent 4bc3727 commit 522ce2e

File tree

12 files changed

+160
-75
lines changed

12 files changed

+160
-75
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
add output to the operating system clipboard
1111
* Updated unit tests to be Python 3.12 compliant.
1212
* Fall back to bz2 compression of history file when lzma is not installed.
13+
* Added settable called `scripts_add_to_history` which determines whether scripts and pyscripts
14+
add commands to history.
1315
* Deletions (potentially breaking changes)
1416
* Removed `apply_style` from `Cmd.pwarning()`.
1517

cmd2/cmd2.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ def __init__(
322322
self.editor = Cmd.DEFAULT_EDITOR
323323
self.feedback_to_output = False # Do not include nonessentials in >, | output by default (things like timing)
324324
self.quiet = False # Do not suppress nonessential output
325+
self.scripts_add_to_history = True # Scripts and pyscripts add commands to history
325326
self.timing = False # Prints elapsed time for each command
326327

327328
# The maximum number of CompletionItems to display during tab completion. If the number of completion
@@ -1084,12 +1085,7 @@ def allow_style_type(value: str) -> ansi.AllowStyle:
10841085
)
10851086

10861087
self.add_settable(
1087-
Settable(
1088-
'always_show_hint',
1089-
bool,
1090-
'Display tab completion hint even when completion suggestions print',
1091-
self,
1092-
)
1088+
Settable('always_show_hint', bool, 'Display tab completion hint even when completion suggestions print', self)
10931089
)
10941090
self.add_settable(Settable('debug', bool, "Show full traceback on exception", self))
10951091
self.add_settable(Settable('echo', bool, "Echo command issued into output", self))
@@ -1099,6 +1095,7 @@ def allow_style_type(value: str) -> ansi.AllowStyle:
10991095
Settable('max_completion_items', int, "Maximum number of CompletionItems to display during tab completion", self)
11001096
)
11011097
self.add_settable(Settable('quiet', bool, "Don't print nonessential feedback", self))
1098+
self.add_settable(Settable('scripts_add_to_history', bool, 'Scripts and pyscripts add commands to history', self))
11021099
self.add_settable(Settable('timing', bool, "Report execution times", self))
11031100

11041101
# ----- Methods related to presenting output to the user -----
@@ -4459,7 +4456,8 @@ def py_quit() -> None:
44594456
PyBridge,
44604457
)
44614458

4462-
py_bridge = PyBridge(self)
4459+
add_to_history = self.scripts_add_to_history if pyscript else True
4460+
py_bridge = PyBridge(self, add_to_history=add_to_history)
44634461
saved_sys_path = None
44644462

44654463
if self.in_pyscript():
@@ -4955,7 +4953,13 @@ def _persist_history(self) -> None:
49554953
except OSError as ex:
49564954
self.perror(f"Cannot write persistent history file '{self.persistent_history_file}': {ex}")
49574955

4958-
def _generate_transcript(self, history: Union[List[HistoryItem], List[str]], transcript_file: str) -> None:
4956+
def _generate_transcript(
4957+
self,
4958+
history: Union[List[HistoryItem], List[str]],
4959+
transcript_file: str,
4960+
*,
4961+
add_to_history: bool = True,
4962+
) -> None:
49594963
"""Generate a transcript file from a given history of commands"""
49604964
self.last_result = False
49614965

@@ -5005,7 +5009,11 @@ def _generate_transcript(self, history: Union[List[HistoryItem], List[str]], tra
50055009

50065010
# then run the command and let the output go into our buffer
50075011
try:
5008-
stop = self.onecmd_plus_hooks(history_item, raise_keyboard_interrupt=True)
5012+
stop = self.onecmd_plus_hooks(
5013+
history_item,
5014+
add_to_history=add_to_history,
5015+
raise_keyboard_interrupt=True,
5016+
)
50095017
except KeyboardInterrupt as ex:
50105018
self.perror(ex)
50115019
stop = True
@@ -5149,9 +5157,17 @@ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]:
51495157

51505158
if args.transcript:
51515159
# self.last_resort will be set by _generate_transcript()
5152-
self._generate_transcript(script_commands, os.path.expanduser(args.transcript))
5160+
self._generate_transcript(
5161+
script_commands,
5162+
os.path.expanduser(args.transcript),
5163+
add_to_history=self.scripts_add_to_history,
5164+
)
51535165
else:
5154-
stop = self.runcmds_plus_hooks(script_commands, stop_on_keyboard_interrupt=True)
5166+
stop = self.runcmds_plus_hooks(
5167+
script_commands,
5168+
add_to_history=self.scripts_add_to_history,
5169+
stop_on_keyboard_interrupt=True,
5170+
)
51555171
self.last_result = True
51565172
return stop
51575173

cmd2/py_bridge.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,17 @@ def __bool__(self) -> bool:
8383

8484

8585
class PyBridge:
86-
"""Provides a Python API wrapper for application commands."""
86+
"""
87+
Provides a Python API wrapper for application commands.
88+
89+
:param cmd2_app: app being controlled by this PyBridge.
90+
:param add_to_history: If True, then add all commands run by this PyBridge to history.
91+
Defaults to True.
92+
"""
8793

88-
def __init__(self, cmd2_app: 'cmd2.Cmd') -> None:
94+
def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None:
8995
self._cmd2_app = cmd2_app
96+
self._add_to_history = add_to_history
9097
self.cmd_echo = False
9198

9299
# Tells if any of the commands run via __call__ returned True for stop
@@ -126,7 +133,11 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
126133
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout)
127134
with redirect_stdout(cast(IO[str], copy_cmd_stdout)):
128135
with redirect_stderr(cast(IO[str], copy_stderr)):
129-
stop = self._cmd2_app.onecmd_plus_hooks(command, py_bridge_call=True)
136+
stop = self._cmd2_app.onecmd_plus_hooks(
137+
command,
138+
add_to_history=self._add_to_history,
139+
py_bridge_call=True,
140+
)
130141
finally:
131142
with self._cmd2_app.sigint_protection:
132143
self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream)

docs/conf.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,9 @@
174174
# Ignore nitpicky warnings from autodoc which are occurring for very new versions of Sphinx and autodoc
175175
# They seem to be happening because autodoc is now trying to add hyperlinks to docs for typehint classes
176176
nitpick_ignore = [
177-
('py:class', 'Callable[[None], None]'),
178-
('py:class', 'cmd2.cmd2.Cmd'),
179-
('py:class', 'cmd2.parsing.Statement'),
180-
('py:class', 'IO'),
181-
('py:class', 'None'),
182-
('py:class', 'Optional[Callable[[...], argparse.Namespace]]'),
183-
('py:class', 'TextIO'),
184-
('py:class', 'Union[None, Iterable, Callable]'),
177+
('py:class', 'cmd2.decorators.CommandParent'),
178+
('py:obj', 'cmd2.decorators.CommandParent'),
185179
('py:class', 'argparse._SubParsersAction'),
186180
('py:class', 'cmd2.utils._T'),
187-
('py:class', 'StdSim'),
188-
('py:class', 'frame'),
181+
('py:class', 'types.FrameType'),
189182
]

docs/features/builtin_commands.rst

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,21 @@ within a running application:
103103
.. code-block:: text
104104
105105
(Cmd) set
106-
Name Value Description
107-
==================================================================================================================
108-
allow_style Terminal Allow ANSI text style sequences in output (valid values:
109-
Always, Never, Terminal)
110-
always_show_hint False Display tab completion hint even when completion suggestions
111-
print
112-
debug True Show full traceback on exception
113-
echo False Echo command issued into output
114-
editor vi Program used by 'edit'
115-
feedback_to_output False Include nonessentials in '|', '>' results
116-
max_completion_items 50 Maximum number of CompletionItems to display during tab
117-
completion
118-
quiet False Don't print nonessential feedback
119-
timing False Report execution times
106+
Name Value Description
107+
====================================================================================================================
108+
allow_style Terminal Allow ANSI text style sequences in output (valid values:
109+
Always, Never, Terminal)
110+
always_show_hint False Display tab completion hint even when completion suggestions
111+
print
112+
debug True Show full traceback on exception
113+
echo False Echo command issued into output
114+
editor vi Program used by 'edit'
115+
feedback_to_output False Include nonessentials in '|', '>' results
116+
max_completion_items 50 Maximum number of CompletionItems to display during tab
117+
completion
118+
quiet False Don't print nonessential feedback
119+
scripts_add_to_history True Scripts and pyscripts add commands to history
120+
timing False Report execution times
120121
121122
122123
Any of these user-settable parameters can be set while running your app with

docs/features/initialization.rst

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,19 @@ Public instance attributes
104104
Here are instance attributes of ``cmd2.Cmd`` which developers might wish
105105
override:
106106

107+
- **always_show_hint**: if ``True``, display tab completion hint even when
108+
completion suggestions print (Default: ``False``)
107109
- **broken_pipe_warning**: if non-empty, this string will be displayed if a
108110
broken pipe error occurs
109111
- **continuation_prompt**: used for multiline commands on 2nd+ line of input
110-
- **debug**: if ``True`` show full stack trace on error (Default: ``False``)
112+
- **debug**: if ``True``, show full stack trace on error (Default: ``False``)
111113
- **default_category**: if any command has been categorized, then all other
112114
commands that haven't been categorized will display under this section in the
113115
help output.
114116
- **default_error**: the error that prints when a non-existent command is run
115117
- **default_sort_key**: the default key for sorting string results. Its default
116118
value performs a case-insensitive alphabetical sort.
117-
- **default_to_shell**: if ``True`` attempt to run unrecognized commands as
119+
- **default_to_shell**: if ``True``, attempt to run unrecognized commands as
118120
shell commands (Default: ``False``)
119121
- **disabled_commands**: commands that have been disabled from use. This is to
120122
support commands that are only available during specific states of the
@@ -130,7 +132,7 @@ override:
130132
- **exclude_from_history**: commands to exclude from the *history* command
131133
- **exit_code**: this determines the value returned by ``cmdloop()`` when
132134
exiting the application
133-
- **feedback_to_output**: if ``True`` send nonessential output to stdout, if
135+
- **feedback_to_output**: if ``True``, send nonessential output to stdout, if
134136
``False`` send them to stderr (Default: ``False``)
135137
- **help_error**: the error that prints when no help information can be found
136138
- **hidden_commands**: commands to exclude from the help menu and tab
@@ -139,8 +141,6 @@ override:
139141
of results in a Python script or interactive console. Built-in commands don't
140142
make use of this. It is purely there for user-defined commands and
141143
convenience.
142-
- **self_in_py**: if ``True`` allow access to your application in *py*
143-
command via ``self`` (Default: ``False``)
144144
- **macros**: dictionary of macro names and their values
145145
- **max_completion_items**: max number of CompletionItems to display during
146146
tab completion (Default: 50)
@@ -154,9 +154,13 @@ override:
154154
- **py_locals**: dictionary that defines specific variables/functions available
155155
in Python shells and scripts (provides more fine-grained control than making
156156
everything available with **self_in_py**)
157-
- **quiet**: if ``True`` then completely suppress nonessential output (Default:
157+
- **quiet**: if ``True``, then completely suppress nonessential output (Default:
158158
``False``)
159+
- **scripts_add_to_history**: if ``True``, scripts and pyscripts add commands to
160+
history (Default: ``True``)
161+
- **self_in_py**: if ``True``, allow access to your application in *py*
162+
command via ``self`` (Default: ``False``)
159163
- **settable**: dictionary that controls which of these instance attributes
160164
are settable at runtime using the *set* command
161-
- **timing**: if ``True`` display execution time for each command (Default:
165+
- **timing**: if ``True``, display execution time for each command (Default:
162166
``False``)

docs/features/multiline_commands.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ user to type in a SQL command, which can often span lines and which are
3333
terminated with a semicolon.
3434

3535
We estimate that less than 5 percent of ``cmd2`` applications use this feature.
36-
But it is here for those uses cases where it provides value.
36+
But it is here for those use cases where it provides value.

docs/features/settings.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ This setting can be one of three values:
4747
- ``Always`` - ANSI escape sequences are always passed through to the output
4848

4949

50+
always_show_hint
51+
~~~~~~~~~~~~~~~~
52+
53+
If ``True``, display tab completion hint even when completion suggestions print.
54+
The default value of this setting is ``False``.
55+
56+
5057
debug
5158
~~~~~
5259

@@ -106,6 +113,13 @@ suppressed. If ``False``, the :ref:`features/settings:feedback_to_output`
106113
setting controls where the output is sent.
107114

108115

116+
scripts_add_to_history
117+
~~~~~~~~~~~~~~~~~~~~~~
118+
119+
If ``True``, scripts and pyscripts add commands to history. The default value of
120+
this setting is ``True``.
121+
122+
109123
timing
110124
~~~~~~
111125

tests/conftest.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,21 @@ def verify_help_text(
9898

9999
# Output from the set command
100100
SET_TXT = (
101-
"Name Value Description \n"
102-
"==================================================================================================================\n"
103-
"allow_style Terminal Allow ANSI text style sequences in output (valid values: \n"
104-
" Always, Never, Terminal) \n"
105-
"always_show_hint False Display tab completion hint even when completion suggestions\n"
106-
" print \n"
107-
"debug False Show full traceback on exception \n"
108-
"echo False Echo command issued into output \n"
109-
"editor vim Program used by 'edit' \n"
110-
"feedback_to_output False Include nonessentials in '|', '>' results \n"
111-
"max_completion_items 50 Maximum number of CompletionItems to display during tab \n"
112-
" completion \n"
113-
"quiet False Don't print nonessential feedback \n"
114-
"timing False Report execution times \n"
101+
"Name Value Description \n"
102+
"====================================================================================================================\n"
103+
"allow_style Terminal Allow ANSI text style sequences in output (valid values: \n"
104+
" Always, Never, Terminal) \n"
105+
"always_show_hint False Display tab completion hint even when completion suggestions\n"
106+
" print \n"
107+
"debug False Show full traceback on exception \n"
108+
"echo False Echo command issued into output \n"
109+
"editor vim Program used by 'edit' \n"
110+
"feedback_to_output False Include nonessentials in '|', '>' results \n"
111+
"max_completion_items 50 Maximum number of CompletionItems to display during tab \n"
112+
" completion \n"
113+
"quiet False Don't print nonessential feedback \n"
114+
"scripts_add_to_history True Scripts and pyscripts add commands to history \n"
115+
"timing False Report execution times \n"
115116
)
116117

117118

tests/test_cmd2.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,27 @@ def test_run_script_with_utf8_file(base_app, request):
442442
assert script_err == manual_err
443443

444444

445+
def test_scripts_add_to_history(base_app, request):
446+
test_dir = os.path.dirname(request.module.__file__)
447+
filename = os.path.join(test_dir, 'scripts', 'help.txt')
448+
command = f'run_script {filename}'
449+
450+
# Add to history
451+
base_app.scripts_add_to_history = True
452+
base_app.history.clear()
453+
run_cmd(base_app, command)
454+
assert len(base_app.history) == 2
455+
assert base_app.history.get(1).raw == command
456+
assert base_app.history.get(2).raw == 'help -v'
457+
458+
# Do not add to history
459+
base_app.scripts_add_to_history = False
460+
base_app.history.clear()
461+
run_cmd(base_app, command)
462+
assert len(base_app.history) == 1
463+
assert base_app.history.get(1).raw == command
464+
465+
445466
def test_run_script_nested_run_scripts(base_app, request):
446467
# Verify that running a script with nested run_script commands works correctly,
447468
# and runs the nested script commands in the correct order.

tests/test_run_pyscript.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ def test_run_pyscript_help(base_app, request):
107107
assert out1 and out1 == out2
108108

109109

110+
def test_scripts_add_to_history(base_app, request):
111+
test_dir = os.path.dirname(request.module.__file__)
112+
python_script = os.path.join(test_dir, 'pyscript', 'help.py')
113+
command = f'run_pyscript {python_script}'
114+
115+
# Add to history
116+
base_app.scripts_add_to_history = True
117+
base_app.history.clear()
118+
run_cmd(base_app, command)
119+
assert len(base_app.history) == 2
120+
assert base_app.history.get(1).raw == command
121+
assert base_app.history.get(2).raw == 'help'
122+
123+
# Do not add to history
124+
base_app.scripts_add_to_history = False
125+
base_app.history.clear()
126+
run_cmd(base_app, command)
127+
assert len(base_app.history) == 1
128+
assert base_app.history.get(1).raw == command
129+
130+
110131
def test_run_pyscript_dir(base_app, request):
111132
test_dir = os.path.dirname(request.module.__file__)
112133
python_script = os.path.join(test_dir, 'pyscript', 'pyscript_dir.py')

0 commit comments

Comments
 (0)