summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/cmd2.py38
-rw-r--r--docs/freefeatures.rst5
-rw-r--r--docs/transcript.rst17
-rw-r--r--tests/scripts/help.txt1
-rw-r--r--tests/test_transcript.py31
5 files changed, 77 insertions, 15 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index c1cebdd2..f46ce496 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -3347,18 +3347,17 @@ class Cmd(cmd.Cmd):
for hi in history:
self.poutput(hi.pr(script=args.script, expanded=args.expanded, verbose=args.verbose))
- def _generate_transcript(self, history: List[HistoryItem], transcript_file: str) -> None:
+ def _generate_transcript(self, history: List[Union[HistoryItem, str]], transcript_file: str) -> None:
"""Generate a transcript file from a given history of commands."""
- # Save the current echo state, and turn it off. We inject commands into the
- # output using a different mechanism
import io
+ # Disable echo and redirection while we manually redirect stdout to a StringIO buffer
+ saved_allow_redirection = self.allow_redirection
saved_echo = self.echo
+ saved_stdout = self.stdout
+ self.allow_redirection = False
self.echo = False
- # Redirect stdout to the transcript file
- saved_self_stdout = self.stdout
-
# The problem with supporting regular expressions in transcripts
# is that they shouldn't be processed in the command, just the output.
# In addition, when we generate a transcript, any slashes in the output
@@ -3394,10 +3393,10 @@ class Cmd(cmd.Cmd):
# and add the regex-escaped output to the transcript
transcript += output.replace('/', r'\/')
- # Restore stdout to its original state
- self.stdout = saved_self_stdout
- # Set echo back to its original state
+ # Restore altered attributes to their original state
+ self.allow_redirection = saved_allow_redirection
self.echo = saved_echo
+ self.stdout = saved_stdout
# finally, we can write the transcript out to the file
try:
@@ -3453,9 +3452,21 @@ class Cmd(cmd.Cmd):
load_description = ("Run commands in script file that is encoded as either ASCII or UTF-8 text\n"
"\n"
"Script should contain one command per line, just like the command would be\n"
- "typed in the console.")
+ "typed in the console.\n"
+ "\n"
+ "It loads commands from a script file into a queue and then the normal cmd2\n"
+ "REPL resumes control and executes the commands in the queue in FIFO order.\n"
+ "If you attempt to redirect/pipe a load command, it will capture the output\n"
+ "of the load command itself, not what it adds to the queue.\n"
+ "\n"
+ "If the -r/--record_transcript flag is used, this command instead records\n"
+ "the output of the script commands to a transcript for testing purposes.\n"
+ )
load_parser = ACArgumentParser(description=load_description)
+ setattr(load_parser.add_argument('-r', '--record_transcript',
+ help='record the output of the script as a transcript file'),
+ ACTION_ARG_CHOICES, ('path_complete',))
setattr(load_parser.add_argument('script_path', help="path to the script file"),
ACTION_ARG_CHOICES, ('path_complete',))
@@ -3489,11 +3500,16 @@ class Cmd(cmd.Cmd):
# command queue. Add an "end of script (eos)" command to cleanup the
# self._script_dir list when done.
with open(expanded_path, encoding='utf-8') as target:
- self.cmdqueue = target.read().splitlines() + ['eos'] + self.cmdqueue
+ script_commands = target.read().splitlines()
except OSError as ex: # pragma: no cover
self.perror("Problem accessing script from '{}': {}".format(expanded_path, ex))
return
+ if args.record_transcript:
+ self._generate_transcript(script_commands, args.record_transcript)
+ return
+
+ self.cmdqueue = script_commands + ['eos'] + self.cmdqueue
self._script_dir.append(os.path.dirname(expanded_path))
relative_load_description = load_description
diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst
index 001a7599..5a08dcd6 100644
--- a/docs/freefeatures.rst
+++ b/docs/freefeatures.rst
@@ -19,6 +19,11 @@ Both ASCII and UTF-8 encoded unicode text files are supported.
Simply include one command per line, typed exactly as you would inside a ``cmd2`` application.
+The ``load`` command loads commands from a script file into a queue and then the normal cmd2 REPL
+resumes control and executes the commands in the queue in FIFO order. A side effect of this
+is that if you redirect/pipe the output of a load command, it will redirect the output of the ``load``
+command itself, but will NOT redirect the output of the command loaded from the script file.
+
.. automethod:: cmd2.cmd2.Cmd.do_load
.. automethod:: cmd2.cmd2.Cmd.do__relative_load
diff --git a/docs/transcript.rst b/docs/transcript.rst
index 36b35fcf..91679641 100644
--- a/docs/transcript.rst
+++ b/docs/transcript.rst
@@ -13,9 +13,9 @@ from commands that produce dynamic or variable output.
Creating a transcript
=====================
-Automatically
--------------
-A transcript can automatically generated based upon commands previously executed in the *history*::
+Automatically from history
+--------------------------
+A transcript can automatically generated based upon commands previously executed in the *history* using ``history -t``::
(Cmd) help
...
@@ -32,6 +32,17 @@ This is by far the easiest way to generate a transcript.
of the ``cmd2.Cmd`` class ensure that output is properly redirected when redirecting to a file, piping to a shell
command, and when generating a transcript.
+Automatically from a script file
+--------------------------------
+A transcript can also be automatically generated from a script file using ``load -r``::
+
+ (Cmd) load scripts/script.txt -r transcript.txt
+ 2 commands and their outputs saved to transcript file 'transcript.txt'
+ (Cmd)
+
+This is a particularly attractive option for automatically regenerating transcripts for regression testing as your ``cmd2``
+application changes.
+
Manually
--------
Here's a transcript created from ``python examples/example.py``::
diff --git a/tests/scripts/help.txt b/tests/scripts/help.txt
new file mode 100644
index 00000000..71d23d97
--- /dev/null
+++ b/tests/scripts/help.txt
@@ -0,0 +1 @@
+help -v
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index 4a03ebe0..709648fc 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -17,7 +17,7 @@ from unittest import mock
import pytest
import cmd2
-from .conftest import run_cmd
+from .conftest import run_cmd, BASE_HELP_VERBOSE
from cmd2 import transcript
from cmd2.utils import StdSim
@@ -189,6 +189,35 @@ this is a \/multiline\/ command
transcript = f.read()
assert transcript == expected
+
+def test_load_record_transcript(base_app, request):
+ test_dir = os.path.dirname(request.module.__file__)
+ filename = os.path.join(test_dir, 'scripts', 'help.txt')
+
+ assert base_app.cmdqueue == []
+ assert base_app._script_dir == []
+ assert base_app._current_script_dir is None
+
+ # make a tmp file to use as a transcript
+ fd, transcript_fname = tempfile.mkstemp(prefix='', suffix='.trn')
+ os.close(fd)
+
+ # Run the load command with the -r option to generate a transcript
+ run_cmd(base_app, 'load {} -r {}'.format(filename, transcript_fname))
+
+ assert base_app.cmdqueue == []
+ assert base_app._script_dir == []
+ assert base_app._current_script_dir is None
+
+ # read in the transcript created by the history command
+ with open(transcript_fname) as f:
+ xscript = f.read()
+
+ expected = '(Cmd) help -v\n' + BASE_HELP_VERBOSE + '\n'
+
+ assert xscript == expected
+
+
@pytest.mark.parametrize('expected, transformed', [
# strings with zero or one slash or with escaped slashes means no regular
# expression present, so the result should just be what re.escape returns.