diff options
-rw-r--r-- | cmd2/cmd2.py | 38 | ||||
-rw-r--r-- | docs/freefeatures.rst | 5 | ||||
-rw-r--r-- | docs/transcript.rst | 17 | ||||
-rw-r--r-- | tests/scripts/help.txt | 1 | ||||
-rw-r--r-- | tests/test_transcript.py | 31 |
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. |