diff options
-rw-r--r-- | CONTRIBUTING.md | 2 | ||||
-rwxr-xr-x | cmd2.py | 57 | ||||
-rw-r--r-- | tests/scripts/nested.txt | 4 | ||||
-rw-r--r-- | tests/scripts/postcmds.txt | 2 | ||||
-rw-r--r-- | tests/scripts/precmds.txt | 2 | ||||
-rw-r--r-- | tests/test_cmd2.py | 55 |
6 files changed, 109 insertions, 13 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88898f8d..5eace248 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -192,7 +192,7 @@ Once you have cmd2 cloned, before you start any cmd2 application, you first need ```bash # Install cmd2 prerequisites -pip install -U six pyparsing +pip install -U six pyparsing pyperclip # Install prerequisites for running cmd2 unit tests pip install -U pytest mock @@ -789,6 +789,44 @@ class Cmd(cmd.Cmd): finally: return self.postparsing_postcmd(stop) + def runcmds_plus_hooks(self, cmds): + """Convenience method to run multiple commands by onecmd_plus_hooks. + + This method adds the given cmds to the command queue and processes the + queue until completion or an error causes it to abort. Scripts that are + loaded will have their commands added to the queue. Scripts may even + load other scripts recursively. This means, however, that you should not + use this method if there is a running cmdloop or some other event-loop. + This method is only intended to be used in "one-off" scenarios. + + NOTE: You may need this method even if you only have one command. If + that command is a load, then you will need this command to fully process + all the subsequent commands that are loaded from the script file. This + is an improvement over onecmd_plus_hooks, which expects to be used + inside of a command loop which does the processing of loaded commands. + + Example: cmd_obj.runcmds_plus_hooks(['load myscript.txt']) + + :param cmds: list - Command strings suitable for onecmd_plus_hooks. + :return: bool - True implies the entire application should exit. + + """ + stop = False + self.cmdqueue = list(cmds) + self.cmdqueue + try: + while self.cmdqueue and not stop: + stop = self.onecmd_plus_hooks(self.cmdqueue.pop(0)) + finally: + # Clear out the command queue and script directory stack, just in + # case we hit an error and they were not completed. + self.cmdqueue = [] + self._script_dir = [] + # NOTE: placing this return here inside the finally block will + # swallow exceptions. This is consistent with what is done in + # onecmd_plus_hooks and _cmdloop, although it may not be + # necessary/desired here. + return stop + def _complete_statement(self, line): """Keep accepting lines of input until the command is complete.""" if not line or (not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line)): @@ -1775,18 +1813,13 @@ Script should contain one command per line, just like command would be typed in return try: - # Specify file encoding in Python 3, but Python 2 doesn't allow that argument to open() - if six.PY3: - # Add all commands in the script to the command queue - with open(expanded_path, encoding='utf-8') as target: - self.cmdqueue.extend(target.read().splitlines()) - else: - # Add all commands in the script to the command queue - with open(expanded_path) as target: - self.cmdqueue.extend(target.read().splitlines()) - - # Append in an "end of script (eos)" command to cleanup the self._script_dir list - self.cmdqueue.append('eos') + # Read all lines of the script and insert into the head of the + # command queue. Add an "end of script (eos)" command to cleanup the + # self._script_dir list when done. Specify file encoding in Python + # 3, but Python 2 doesn't allow that argument to open(). + kwargs = {'encoding' : 'utf-8'} if six.PY3 else {} + with open(expanded_path, **kwargs) as target: + self.cmdqueue = target.read().splitlines() + ['eos'] + self.cmdqueue except IOError as e: self.perror('Problem accessing script from {}:\n{}'.format(expanded_path, e)) return diff --git a/tests/scripts/nested.txt b/tests/scripts/nested.txt new file mode 100644 index 00000000..ac3b4748 --- /dev/null +++ b/tests/scripts/nested.txt @@ -0,0 +1,4 @@ +_relative_load precmds.txt +help +shortcuts +_relative_load postcmds.txt diff --git a/tests/scripts/postcmds.txt b/tests/scripts/postcmds.txt new file mode 100644 index 00000000..760bcdf5 --- /dev/null +++ b/tests/scripts/postcmds.txt @@ -0,0 +1,2 @@ +set abbrev off +set colors off diff --git a/tests/scripts/precmds.txt b/tests/scripts/precmds.txt new file mode 100644 index 00000000..d8857e92 --- /dev/null +++ b/tests/scripts/precmds.txt @@ -0,0 +1,2 @@ +set abbrev on +set colors on diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 4f65606d..68924e08 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -401,6 +401,61 @@ def test_load_with_utf8_file(base_app, capsys, request): assert base_app._current_script_dir == sdir +def test_load_nested_loads(base_app, request): + # Verify that loading a script with nested load commands works correctly, + # and loads the nested script commands in the correct order. The recursive + # loads don't happen all at once, but as the commands are interpreted. So, + # we will need to drain the cmdqueue and inspect the stdout to see if all + # steps were executed in the expected order. + test_dir = os.path.dirname(request.module.__file__) + filename = os.path.join(test_dir, 'scripts', 'nested.txt') + assert base_app.cmdqueue == [] + + # Load the top level script and then run the command queue until all + # commands have been exhausted. + initial_load = 'load ' + filename + run_cmd(base_app, initial_load) + while base_app.cmdqueue: + base_app.onecmd_plus_hooks(base_app.cmdqueue.pop(0)) + + # Check that the right commands were executed. + expected = """ +%s +_relative_load precmds.txt +set abbrev on +set colors on +help +shortcuts +_relative_load postcmds.txt +set abbrev off +set colors off""" % initial_load + assert run_cmd(base_app, 'history -s') == normalize(expected) + + +def test_base_runcmds_plus_hooks(base_app, request): + # Make sure that runcmds_plus_hooks works as intended. I.E. to run multiple + # commands and process any commands added, by them, to the command queue. + test_dir = os.path.dirname(request.module.__file__) + prefilepath = os.path.join(test_dir, 'scripts', 'precmds.txt') + postfilepath = os.path.join(test_dir, 'scripts', 'postcmds.txt') + assert base_app.cmdqueue == [] + + base_app.runcmds_plus_hooks(['load ' + prefilepath, + 'help', + 'shortcuts', + 'load ' + postfilepath]) + expected = """ +load %s +set abbrev on +set colors on +help +shortcuts +load %s +set abbrev off +set colors off""" % (prefilepath, postfilepath) + assert run_cmd(base_app, 'history -s') == normalize(expected) + + def test_base_relative_load(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') |