summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcmd2/cmd2.py91
-rw-r--r--tests/test_argparse.py13
-rw-r--r--tests/test_transcript.py175
-rw-r--r--tests/transcripts/from_cmdloop.txt1
4 files changed, 108 insertions, 172 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index f4f30bd4..ad2038d4 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -3002,34 +3002,7 @@ a..b, a:b, a:, ..b items by indices (inclusive)
except Exception as e:
self.perror('Saving {!r} - {}'.format(args.output_file, e), traceback_war=False)
elif args.transcript:
- # Make sure echo is on so commands print to standard out
- saved_echo = self.echo
- self.echo = True
-
- # Redirect stdout to the transcript file
- saved_self_stdout = self.stdout
- self.stdout = open(args.transcript, 'w')
-
- # Run all of the commands in the history with output redirected to transcript and echo on
- self.runcmds_plus_hooks(history)
-
- # Restore stdout to its original state
- self.stdout.close()
- self.stdout = saved_self_stdout
-
- # Set echo back to its original state
- self.echo = saved_echo
-
- # Post-process the file to escape un-escaped "/" regex escapes
- with open(args.transcript, 'r') as fin:
- data = fin.read()
- post_processed_data = data.replace('/', '\/')
- with open(args.transcript, 'w') as fout:
- fout.write(post_processed_data)
-
- plural = 's' if len(history) > 1 else ''
- self.pfeedback('{} command{} and outputs saved to transcript file {!r}'.format(len(history), plural,
- args.transcript))
+ self._generate_transcript(history, args.transcript)
else:
# Display the history items retrieved
for hi in history:
@@ -3038,6 +3011,68 @@ a..b, a:b, a:, ..b items by indices (inclusive)
else:
self.poutput(hi.pr())
+ def _generate_transcript(self, history, transcript_file):
+ """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
+ saved_echo = self.echo
+ 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
+ # are not really intended to indicate regular expressions, so they should
+ # be escaped.
+ #
+ # We have to jump through some hoops here in order to catch the commands
+ # separately from the output and escape the slashes in the output.
+ transcript = ''
+ for history_item in history:
+ # build the command, complete with prompts. When we replay
+ # the transcript, we look for the prompts to separate
+ # the command from the output
+ first = True
+ command = ''
+ for line in history_item.splitlines():
+ if first:
+ command += '{}{}\n'.format(self.prompt, line)
+ first = False
+ else:
+ command += '{}{}\n'.format(self.continuation_prompt, line)
+ transcript += command
+ # create a new string buffer and set it to stdout to catch the output
+ # of the command
+ membuf = io.StringIO()
+ self.stdout = membuf
+ # then run the command and let the output go into our buffer
+ self.onecmd_plus_hooks(history_item)
+ # rewind the buffer to the beginning
+ membuf.seek(0)
+ # get the output out of the buffer
+ output = membuf.read()
+ # and add the regex-escaped output to the transcript
+ transcript += output.replace('/', '\/')
+
+ # Restore stdout to its original state
+ self.stdout = saved_self_stdout
+ # Set echo back to its original state
+ self.echo = saved_echo
+
+ # finally, we can write the transcript out to the file
+ with open(transcript_file, 'w') as fout:
+ fout.write(transcript)
+
+ # and let the user know what we did
+ if len(history) > 1:
+ plural = 'commands and their outputs'
+ else:
+ plural = 'command and its output'
+ msg = '{} {} saved to transcript file {!r}'
+ self.pfeedback(msg.format(len(history), plural, transcript_file))
+
@with_argument_list
def do_edit(self, arglist):
"""Edit a file in a text editor.
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 94a7b5ed..f1a2b357 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -119,6 +119,11 @@ def argparse_app():
return app
+def test_invalid_syntax(argparse_app, capsys):
+ run_cmd(argparse_app, 'speak "')
+ out, err = capsys.readouterr()
+ assert err == "ERROR: Invalid syntax: No closing quotation\n"
+
def test_argparse_basic_command(argparse_app):
out = run_cmd(argparse_app, 'say hello')
assert out == ['hello']
@@ -135,6 +140,14 @@ def test_argparse_with_list_and_empty_doc(argparse_app):
out = run_cmd(argparse_app, 'speak -s hello world!')
assert out == ['HELLO WORLD!']
+def test_argparse_comment_stripping(argparse_app):
+ out = run_cmd(argparse_app, 'speak it was /* not */ delicious! # Yuck!')
+ assert out == ['it was delicious!']
+
+def test_argparser_correct_args_with_quotes_and_midline_options(argparse_app):
+ out = run_cmd(argparse_app, "speak 'This is a' -s test of the emergency broadcast system!")
+ assert out == ['THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!']
+
def test_argparse_quoted_arguments_multiple(argparse_app):
out = run_cmd(argparse_app, 'say "hello there" "rick & morty"')
assert out == ['hello there rick & morty']
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index c0fb49c1..70658161 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -10,12 +10,13 @@ import os
import sys
import re
import random
+import tempfile
from unittest import mock
import pytest
from cmd2 import cmd2
-from .conftest import run_cmd, StdOut, normalize
+from .conftest import run_cmd, StdOut
class CmdLineApp(cmd2.Cmd):
@@ -26,7 +27,6 @@ class CmdLineApp(cmd2.Cmd):
def __init__(self, *args, **kwargs):
self.multiline_commands = ['orate']
self.maxrepeats = 3
- self.redirector = '->'
# Add stuff to settable and/or shortcuts before calling base class initializer
self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed'
@@ -63,7 +63,7 @@ class CmdLineApp(cmd2.Cmd):
def do_mumble(self, opts, arg):
"""Mumbles what you tell me to."""
repetitions = opts.repeat or 1
- arg = arg.split()
+ #arg = arg.split()
for i in range(min(repetitions, self.maxrepeats)):
output = []
if random.random() < .33:
@@ -77,136 +77,6 @@ class CmdLineApp(cmd2.Cmd):
self.poutput(' '.join(output))
-class DemoApp(cmd2.Cmd):
- hello_parser = argparse.ArgumentParser()
- hello_parser.add_argument('-n', '--name', help="your name")
- @cmd2.with_argparser_and_unknown_args(hello_parser)
- def do_hello(self, opts, arg):
- """Says hello."""
- if opts.name:
- self.stdout.write('Hello {}\n'.format(opts.name))
- else:
- self.stdout.write('Hello Nobody\n')
-
-
-@pytest.fixture
-def _cmdline_app():
- c = CmdLineApp()
- c.stdout = StdOut()
- return c
-
-
-@pytest.fixture
-def _demo_app():
- c = DemoApp()
- c.stdout = StdOut()
- return c
-
-
-def _get_transcript_blocks(transcript):
- cmd = None
- expected = ''
- for line in transcript.splitlines():
- if line.startswith('(Cmd) '):
- if cmd is not None:
- yield cmd, normalize(expected)
-
- cmd = line[6:]
- expected = ''
- else:
- expected += line + '\n'
- yield cmd, normalize(expected)
-
-
-def test_base_with_transcript(_cmdline_app):
- app = _cmdline_app
- transcript = """
-(Cmd) help
-
-Documented commands (type help <topic>):
-========================================
-alias help load orate pyscript say shell speak
-edit history mumble py quit set shortcuts unalias
-
-(Cmd) help say
-usage: speak [-h] [-p] [-s] [-r REPEAT]
-
-Repeats what you tell me to.
-
-optional arguments:
- -h, --help show this help message and exit
- -p, --piglatin atinLay
- -s, --shout N00B EMULATION MODE
- -r REPEAT, --repeat REPEAT
- output [n] times
-
-(Cmd) say goodnight, Gracie
-goodnight, Gracie
-(Cmd) say -ps --repeat=5 goodnight, Gracie
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-(Cmd) set maxrepeats 5
-maxrepeats - was: 3
-now: 5
-(Cmd) say -ps --repeat=5 goodnight, Gracie
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-(Cmd) history
--------------------------[1]
-help
--------------------------[2]
-help say
--------------------------[3]
-say goodnight, Gracie
--------------------------[4]
-say -ps --repeat=5 goodnight, Gracie
--------------------------[5]
-set maxrepeats 5
--------------------------[6]
-say -ps --repeat=5 goodnight, Gracie
-(Cmd) history -r 4
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-OODNIGHT, GRACIEGAY
-(Cmd) set prompt "---> "
-prompt - was: (Cmd)
-now: --->
-"""
-
- for cmd, expected in _get_transcript_blocks(transcript):
- out = run_cmd(app, cmd)
- assert out == expected
-
-
-class TestMyAppCase(cmd2.Cmd2TestCase):
- CmdApp = CmdLineApp
- CmdApp.testfiles = ['tests/transcript.txt']
-
-
-def test_comment_stripping(_cmdline_app):
- out = run_cmd(_cmdline_app, 'speak it was /* not */ delicious! # Yuck!')
- expected = normalize("""it was delicious!""")
- assert out == expected
-
-
-def test_argparser_correct_args_with_quotes_and_midline_options(_cmdline_app):
- out = run_cmd(_cmdline_app, "speak 'This is a' -s test of the emergency broadcast system!")
- expected = normalize("""THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!""")
- assert out == expected
-
-
-def test_argparser_options_with_spaces_in_quotes(_demo_app):
- out = run_cmd(_demo_app, "hello foo -n 'Bugs Bunny' bar baz")
- expected = normalize("""Hello Bugs Bunny""")
- assert out == expected
-
-
def test_commands_at_invocation():
testargs = ["prog", "say hello", "say Gracie", "quit"]
expected = "This is an intro banner ...\nhello\nGracie\n"
@@ -217,19 +87,12 @@ def test_commands_at_invocation():
out = app.stdout.buffer
assert out == expected
-def test_invalid_syntax(_cmdline_app, capsys):
- run_cmd(_cmdline_app, 'speak "')
- out, err = capsys.readouterr()
- expected = normalize("""ERROR: Invalid syntax: No closing quotation""")
- assert normalize(str(err)) == expected
-
-
-@pytest.mark.parametrize('filename, feedback_to_output', [
+@pytest.mark.parametrize('filename,feedback_to_output', [
('bol_eol.txt', False),
('characterclass.txt', False),
('dotstar.txt', False),
('extension_notation.txt', False),
- # ('from_cmdloop.txt', True),
+ ('from_cmdloop.txt', True),
('multiline_no_regex.txt', False),
('multiline_regex.txt', False),
('regex_set.txt', False),
@@ -237,7 +100,7 @@ def test_invalid_syntax(_cmdline_app, capsys):
('slashes_escaped.txt', False),
('slashslash.txt', False),
('spaces.txt', False),
- # ('word_boundaries.txt', False),
+ ('word_boundaries.txt', False),
])
def test_transcript(request, capsys, filename, feedback_to_output):
# Create a cmd2.Cmd() instance and make sure basic settings are
@@ -263,6 +126,32 @@ def test_transcript(request, capsys, filename, feedback_to_output):
assert err.startswith(expected_start)
assert err.endswith(expected_end)
+def test_history_transcript(request, capsys):
+ app = CmdLineApp()
+ app.stdout = StdOut()
+ run_cmd(app, 'orate this is\na /multiline/\ncommand;\n')
+ run_cmd(app, 'speak /tmp/file.txt is not a regex')
+
+ expected = r"""(Cmd) orate this is
+> a /multiline/
+> command;
+this is a \/multiline\/ command
+(Cmd) speak /tmp/file.txt is not a regex
+\/tmp\/file.txt is not a regex
+"""
+
+ # make a tmp file
+ fd, history_fname = tempfile.mkstemp(prefix='', suffix='.txt')
+ os.close(fd)
+
+ # tell the history command to create a transcript
+ run_cmd(app, 'history -t "{}"'.format(history_fname))
+
+ # read in the transcript created by the history command
+ with open(history_fname) as f:
+ transcript = f.read()
+
+ assert transcript == expected
@pytest.mark.parametrize('expected, transformed', [
# strings with zero or one slash or with escaped slashes means no regular
diff --git a/tests/transcripts/from_cmdloop.txt b/tests/transcripts/from_cmdloop.txt
index 13b61b00..5f22d756 100644
--- a/tests/transcripts/from_cmdloop.txt
+++ b/tests/transcripts/from_cmdloop.txt
@@ -19,7 +19,6 @@ optional arguments:/ */
-s, --shout N00B EMULATION MODE/ */
-r REPEAT, --repeat REPEAT/ */
output [n] times
-
(Cmd) say goodnight, Gracie
goodnight, Gracie
(Cmd) say -ps --repeat=5 goodnight, Gracie