Skip to content

Commit

Permalink
Added settable called scripts_add_to_history which determines whether…
Browse files Browse the repository at this point in the history
… scripts and pyscripts (#1291)

add commands to history.

Co-authored-by: Kevin Van Brunt <[email protected]>
  • Loading branch information
laf0rge and kmvanbrunt authored Sep 21, 2024
1 parent 4bc3727 commit 522ce2e
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 75 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
add output to the operating system clipboard
* Updated unit tests to be Python 3.12 compliant.
* Fall back to bz2 compression of history file when lzma is not installed.
* Added settable called `scripts_add_to_history` which determines whether scripts and pyscripts
add commands to history.
* Deletions (potentially breaking changes)
* Removed `apply_style` from `Cmd.pwarning()`.

Expand Down
38 changes: 27 additions & 11 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ def __init__(
self.editor = Cmd.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

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

self.add_settable(
Settable(
'always_show_hint',
bool,
'Display tab completion hint even when completion suggestions print',
self,
)
Settable('always_show_hint', bool, 'Display tab completion hint even when completion suggestions print', self)
)
self.add_settable(Settable('debug', bool, "Show full traceback on exception", self))
self.add_settable(Settable('echo', bool, "Echo command issued into output", self))
Expand All @@ -1099,6 +1095,7 @@ def allow_style_type(value: str) -> ansi.AllowStyle:
Settable('max_completion_items', int, "Maximum number of CompletionItems to display during tab completion", self)
)
self.add_settable(Settable('quiet', bool, "Don't print nonessential feedback", self))
self.add_settable(Settable('scripts_add_to_history', bool, 'Scripts and pyscripts add commands to history', self))
self.add_settable(Settable('timing', bool, "Report execution times", self))

# ----- Methods related to presenting output to the user -----
Expand Down Expand Up @@ -4459,7 +4456,8 @@ def py_quit() -> None:
PyBridge,
)

py_bridge = PyBridge(self)
add_to_history = self.scripts_add_to_history if pyscript else True
py_bridge = PyBridge(self, add_to_history=add_to_history)
saved_sys_path = None

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

def _generate_transcript(self, history: Union[List[HistoryItem], List[str]], transcript_file: str) -> None:
def _generate_transcript(
self,
history: Union[List[HistoryItem], List[str]],
transcript_file: str,
*,
add_to_history: bool = True,
) -> None:
"""Generate a transcript file from a given history of commands"""
self.last_result = False

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

# then run the command and let the output go into our buffer
try:
stop = self.onecmd_plus_hooks(history_item, raise_keyboard_interrupt=True)
stop = self.onecmd_plus_hooks(
history_item,
add_to_history=add_to_history,
raise_keyboard_interrupt=True,
)
except KeyboardInterrupt as ex:
self.perror(ex)
stop = True
Expand Down Expand Up @@ -5149,9 +5157,17 @@ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]:

if args.transcript:
# self.last_resort will be set by _generate_transcript()
self._generate_transcript(script_commands, os.path.expanduser(args.transcript))
self._generate_transcript(
script_commands,
os.path.expanduser(args.transcript),
add_to_history=self.scripts_add_to_history,
)
else:
stop = self.runcmds_plus_hooks(script_commands, stop_on_keyboard_interrupt=True)
stop = self.runcmds_plus_hooks(
script_commands,
add_to_history=self.scripts_add_to_history,
stop_on_keyboard_interrupt=True,
)
self.last_result = True
return stop

Expand Down
17 changes: 14 additions & 3 deletions cmd2/py_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,17 @@ def __bool__(self) -> bool:


class PyBridge:
"""Provides a Python API wrapper for application commands."""
"""
Provides a Python API wrapper for application commands.
:param cmd2_app: app being controlled by this PyBridge.
:param add_to_history: If True, then add all commands run by this PyBridge to history.
Defaults to True.
"""

def __init__(self, cmd2_app: 'cmd2.Cmd') -> None:
def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None:
self._cmd2_app = cmd2_app
self._add_to_history = add_to_history
self.cmd_echo = False

# Tells if any of the commands run via __call__ returned True for stop
Expand Down Expand Up @@ -126,7 +133,11 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout)
with redirect_stdout(cast(IO[str], copy_cmd_stdout)):
with redirect_stderr(cast(IO[str], copy_stderr)):
stop = self._cmd2_app.onecmd_plus_hooks(command, py_bridge_call=True)
stop = self._cmd2_app.onecmd_plus_hooks(
command,
add_to_history=self._add_to_history,
py_bridge_call=True,
)
finally:
with self._cmd2_app.sigint_protection:
self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream)
Expand Down
13 changes: 3 additions & 10 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,9 @@
# Ignore nitpicky warnings from autodoc which are occurring for very new versions of Sphinx and autodoc
# They seem to be happening because autodoc is now trying to add hyperlinks to docs for typehint classes
nitpick_ignore = [
('py:class', 'Callable[[None], None]'),
('py:class', 'cmd2.cmd2.Cmd'),
('py:class', 'cmd2.parsing.Statement'),
('py:class', 'IO'),
('py:class', 'None'),
('py:class', 'Optional[Callable[[...], argparse.Namespace]]'),
('py:class', 'TextIO'),
('py:class', 'Union[None, Iterable, Callable]'),
('py:class', 'cmd2.decorators.CommandParent'),
('py:obj', 'cmd2.decorators.CommandParent'),
('py:class', 'argparse._SubParsersAction'),
('py:class', 'cmd2.utils._T'),
('py:class', 'StdSim'),
('py:class', 'frame'),
('py:class', 'types.FrameType'),
]
29 changes: 15 additions & 14 deletions docs/features/builtin_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,21 @@ within a running application:
.. code-block:: text
(Cmd) set
Name Value Description
==================================================================================================================
allow_style Terminal Allow ANSI text style sequences in output (valid values:
Always, Never, Terminal)
always_show_hint False Display tab completion hint even when completion suggestions
print
debug True Show full traceback on exception
echo False Echo command issued into output
editor vi Program used by 'edit'
feedback_to_output False Include nonessentials in '|', '>' results
max_completion_items 50 Maximum number of CompletionItems to display during tab
completion
quiet False Don't print nonessential feedback
timing False Report execution times
Name Value Description
====================================================================================================================
allow_style Terminal Allow ANSI text style sequences in output (valid values:
Always, Never, Terminal)
always_show_hint False Display tab completion hint even when completion suggestions
print
debug True Show full traceback on exception
echo False Echo command issued into output
editor vi Program used by 'edit'
feedback_to_output False Include nonessentials in '|', '>' results
max_completion_items 50 Maximum number of CompletionItems to display during tab
completion
quiet False Don't print nonessential feedback
scripts_add_to_history True Scripts and pyscripts add commands to history
timing False Report execution times
Any of these user-settable parameters can be set while running your app with
Expand Down
18 changes: 11 additions & 7 deletions docs/features/initialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,19 @@ Public instance attributes
Here are instance attributes of ``cmd2.Cmd`` which developers might wish
override:

- **always_show_hint**: if ``True``, display tab completion hint even when
completion suggestions print (Default: ``False``)
- **broken_pipe_warning**: if non-empty, this string will be displayed if a
broken pipe error occurs
- **continuation_prompt**: used for multiline commands on 2nd+ line of input
- **debug**: if ``True`` show full stack trace on error (Default: ``False``)
- **debug**: if ``True``, show full stack trace on error (Default: ``False``)
- **default_category**: if any command has been categorized, then all other
commands that haven't been categorized will display under this section in the
help output.
- **default_error**: the error that prints when a non-existent command is run
- **default_sort_key**: the default key for sorting string results. Its default
value performs a case-insensitive alphabetical sort.
- **default_to_shell**: if ``True`` attempt to run unrecognized commands as
- **default_to_shell**: if ``True``, attempt to run unrecognized commands as
shell commands (Default: ``False``)
- **disabled_commands**: commands that have been disabled from use. This is to
support commands that are only available during specific states of the
Expand All @@ -130,7 +132,7 @@ override:
- **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
- **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
Expand All @@ -139,8 +141,6 @@ override:
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.
- **self_in_py**: if ``True`` allow access to your application in *py*
command via ``self`` (Default: ``False``)
- **macros**: dictionary of macro names and their values
- **max_completion_items**: max number of CompletionItems to display during
tab completion (Default: 50)
Expand All @@ -154,9 +154,13 @@ override:
- **py_locals**: dictionary that defines specific variables/functions available
in Python shells and scripts (provides more fine-grained control than making
everything available with **self_in_py**)
- **quiet**: if ``True`` then completely suppress nonessential output (Default:
- **quiet**: if ``True``, then completely suppress nonessential output (Default:
``False``)
- **scripts_add_to_history**: if ``True``, scripts and pyscripts add commands to
history (Default: ``True``)
- **self_in_py**: if ``True``, allow access to your application in *py*
command via ``self`` (Default: ``False``)
- **settable**: dictionary that controls which of these instance attributes
are settable at runtime using the *set* command
- **timing**: if ``True`` display execution time for each command (Default:
- **timing**: if ``True``, display execution time for each command (Default:
``False``)
2 changes: 1 addition & 1 deletion docs/features/multiline_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ user to type in a SQL command, which can often span lines and which are
terminated with a semicolon.

We estimate that less than 5 percent of ``cmd2`` applications use this feature.
But it is here for those uses cases where it provides value.
But it is here for those use cases where it provides value.
14 changes: 14 additions & 0 deletions docs/features/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ This setting can be one of three values:
- ``Always`` - ANSI escape sequences are always passed through to the output


always_show_hint
~~~~~~~~~~~~~~~~

If ``True``, display tab completion hint even when completion suggestions print.
The default value of this setting is ``False``.


debug
~~~~~

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


scripts_add_to_history
~~~~~~~~~~~~~~~~~~~~~~

If ``True``, scripts and pyscripts add commands to history. The default value of
this setting is ``True``.


timing
~~~~~~

Expand Down
29 changes: 15 additions & 14 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,21 @@ def verify_help_text(

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


Expand Down
21 changes: 21 additions & 0 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,27 @@ def test_run_script_with_utf8_file(base_app, request):
assert script_err == manual_err


def test_scripts_add_to_history(base_app, request):
test_dir = os.path.dirname(request.module.__file__)
filename = os.path.join(test_dir, 'scripts', 'help.txt')
command = f'run_script {filename}'

# Add to history
base_app.scripts_add_to_history = True
base_app.history.clear()
run_cmd(base_app, command)
assert len(base_app.history) == 2
assert base_app.history.get(1).raw == command
assert base_app.history.get(2).raw == 'help -v'

# Do not add to history
base_app.scripts_add_to_history = False
base_app.history.clear()
run_cmd(base_app, command)
assert len(base_app.history) == 1
assert base_app.history.get(1).raw == command


def test_run_script_nested_run_scripts(base_app, request):
# Verify that running a script with nested run_script commands works correctly,
# and runs the nested script commands in the correct order.
Expand Down
21 changes: 21 additions & 0 deletions tests/test_run_pyscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ def test_run_pyscript_help(base_app, request):
assert out1 and out1 == out2


def test_scripts_add_to_history(base_app, request):
test_dir = os.path.dirname(request.module.__file__)
python_script = os.path.join(test_dir, 'pyscript', 'help.py')
command = f'run_pyscript {python_script}'

# Add to history
base_app.scripts_add_to_history = True
base_app.history.clear()
run_cmd(base_app, command)
assert len(base_app.history) == 2
assert base_app.history.get(1).raw == command
assert base_app.history.get(2).raw == 'help'

# Do not add to history
base_app.scripts_add_to_history = False
base_app.history.clear()
run_cmd(base_app, command)
assert len(base_app.history) == 1
assert base_app.history.get(1).raw == command


def test_run_pyscript_dir(base_app, request):
test_dir = os.path.dirname(request.module.__file__)
python_script = os.path.join(test_dir, 'pyscript', 'pyscript_dir.py')
Expand Down
Loading

0 comments on commit 522ce2e

Please sign in to comment.