diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-06-11 17:39:11 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-06-11 17:39:11 -0400 |
commit | 77633bc4498d6f11b07ea0a3bd13d4830f809ff8 (patch) | |
tree | 037879fb49353ac66a5d2a3e6c74f0cb3a3b0dd2 | |
parent | 1aee0316973873e8967679f6778952c1d696866a (diff) | |
download | cmd2-git-77633bc4498d6f11b07ea0a3bd13d4830f809ff8.tar.gz |
Removed support for cmd.cmdqueue
allow_cli_args is now an argument to __init__ instead of a cmd2 class member
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | cmd2/cmd2.py | 91 | ||||
-rw-r--r-- | docs/freefeatures.rst | 4 | ||||
-rw-r--r-- | docs/hooks.rst | 2 | ||||
-rwxr-xr-x | examples/cmd_as_argument.py | 3 | ||||
-rwxr-xr-x | examples/decorator_example.py | 3 | ||||
-rwxr-xr-x | examples/persistent_history.py | 3 | ||||
-rw-r--r-- | tests/test_cmd2.py | 33 | ||||
-rw-r--r-- | tests/test_plugin.py | 16 | ||||
-rw-r--r-- | tests/test_transcript.py | 32 |
10 files changed, 96 insertions, 93 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index fceb8161..ac1cc89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ * Removed internally used `eos` command that was used to keep track of when a text script's commands ended * Removed `cmd2` member called `_STOP_AND_EXIT` since it was just a boolean value that should always be True * Removed `cmd2` member called `_should_quit` since `PyscriptBridge` now handles this logic + * Removed support for `cmd.cmdqueue` + * `allow_cli_args` is now an argument to __init__ instead of a `cmd2` class member * **Python 3.4 EOL notice** * Python 3.4 reached its [end of life](https://www.python.org/dev/peps/pep-0429/) on March 18, 2019 * This is the last release of `cmd2` which will support Python 3.4 diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 29f34175..591bc17f 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -335,11 +335,12 @@ class Cmd(cmd.Cmd): DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} DEFAULT_EDITOR = utils.find_editor() - def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent_history_file: str = '', - persistent_history_length: int = 1000, startup_script: Optional[str] = None, use_ipython: bool = False, - transcript_files: Optional[List[str]] = None, allow_redirection: bool = True, - multiline_commands: Optional[List[str]] = None, terminators: Optional[List[str]] = None, - shortcuts: Optional[Dict[str, str]] = None) -> None: + def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *, + persistent_history_file: str = '', persistent_history_length: int = 1000, + startup_script: Optional[str] = None, use_ipython: bool = False, + allow_cli_args: bool = True, transcript_files: Optional[List[str]] = None, + allow_redirection: bool = True, multiline_commands: Optional[List[str]] = None, + terminators: Optional[List[str]] = None, shortcuts: Optional[Dict[str, str]] = None) -> None: """An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package. :param completekey: (optional) readline name of a completion key, default to Tab @@ -349,6 +350,9 @@ class Cmd(cmd.Cmd): :param persistent_history_length: (optional) max number of history items to write to the persistent history file :param startup_script: (optional) file path to a a script to load and execute at startup :param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell + :param allow_cli_args: (optional) if True, then cmd2 will process command line arguments as either + commands to be run or, if -t is specified, transcript files to run. + This should be set to False if your application parses its own arguments. :param transcript_files: (optional) allows running transcript tests when allow_cli_args is False :param allow_redirection: (optional) should output redirection and pipes be allowed :param multiline_commands: (optional) list of commands allowed to accept multi-line input @@ -372,7 +376,6 @@ class Cmd(cmd.Cmd): super().__init__(completekey=completekey, stdin=stdin, stdout=stdout) # Attributes which should NOT be dynamically settable at runtime - self.allow_cli_args = True # Should arguments passed on the command-line be processed as commands? self.default_to_shell = False # Attempt to run unrecognized commands as shell commands self.quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt @@ -423,7 +426,6 @@ class Cmd(cmd.Cmd): terminators=terminators, multiline_commands=multiline_commands, shortcuts=shortcuts) - self._transcript_files = transcript_files # True if running inside a Python script or interactive console, False otherwise self._in_py = False @@ -460,11 +462,33 @@ class Cmd(cmd.Cmd): # If this string is non-empty, then this warning message will print if a broken pipe error occurs while printing self.broken_pipe_warning = '' + # Commands that will run at the beginning of the command loop + self._startup_commands = [] + # If a startup script is provided, then add it in the queue to load if startup_script is not None: startup_script = os.path.abspath(os.path.expanduser(startup_script)) if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0: - self.cmdqueue.append("load '{}'".format(startup_script)) + self._startup_commands.append("load '{}'".format(startup_script)) + + # Transcript files to run instead of interactive command loop + self._transcript_files = None + + # Check for command line args + if allow_cli_args: + parser = argparse.ArgumentParser() + parser.add_argument('-t', '--test', action="store_true", + help='Test against transcript(s) in FILE (wildcards OK)') + callopts, callargs = parser.parse_known_args() + + # If transcript testing was called for, use other arguments as transcript files + if callopts.test: + self._transcript_files = callargs + # If commands were supplied at invocation, then add them to the command queue + elif callargs: + self._startup_commands.extend(callargs) + elif transcript_files: + self._transcript_files = transcript_files # The default key for sorting tab completion matches. This only applies when the matches are not # already marked as sorted by setting self.matches_sorted to True. Its default value performs a @@ -2242,25 +2266,23 @@ class Cmd(cmd.Cmd): readline.parse_and_bind(self.completekey + ": complete") try: - stop = False + # Run startup commands + stop = self.runcmds_plus_hooks(self._startup_commands) + self._startup_commands.clear() + while not stop: - if self.cmdqueue: - # Run commands out of cmdqueue if nonempty (populated by commands at invocation) - stop = self.runcmds_plus_hooks(self.cmdqueue) - self.cmdqueue.clear() - else: - # Otherwise, read a command from stdin - try: - line = self.pseudo_raw_input(self.prompt) - except KeyboardInterrupt as ex: - if self.quit_on_sigint: - raise ex - else: - self.poutput('^C') - line = '' + # Get commands from user + try: + line = self.pseudo_raw_input(self.prompt) + except KeyboardInterrupt as ex: + if self.quit_on_sigint: + raise ex + else: + self.poutput('^C') + line = '' - # Run the command along with all associated pre and post hooks - stop = self.onecmd_plus_hooks(line) + # Run the command along with all associated pre and post hooks + stop = self.onecmd_plus_hooks(line) finally: if self.use_rawinput and self.completekey and rl_type != RlType.NONE: @@ -2274,7 +2296,6 @@ class Cmd(cmd.Cmd): elif rl_type == RlType.PYREADLINE: # noinspection PyUnresolvedReferences readline.rl.mode._display_completions = orig_pyreadline_display - self.cmdqueue.clear() # ----- Alias sub-command functions ----- @@ -2889,10 +2910,8 @@ class Cmd(cmd.Cmd): """ read_only_settings = """ Commands may be terminated with: {} - Arguments at invocation allowed: {} Output redirection and pipes allowed: {}""" - return read_only_settings.format(str(self.statement_parser.terminators), self.allow_cli_args, - self.allow_redirection) + return read_only_settings.format(str(self.statement_parser.terminators), self.allow_redirection) def show(self, args: argparse.Namespace, parameter: str = '') -> None: """Shows current settings of parameters. @@ -3991,7 +4010,6 @@ class Cmd(cmd.Cmd): _cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with the following extra features provided by cmd2: - - commands at invocation - transcript testing - intro banner - exit code @@ -4008,19 +4026,6 @@ class Cmd(cmd.Cmd): original_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, self.sigint_handler) - if self.allow_cli_args: - parser = argparse.ArgumentParser() - parser.add_argument('-t', '--test', action="store_true", - help='Test against transcript(s) in FILE (wildcards OK)') - callopts, callargs = parser.parse_known_args() - - # If transcript testing was called for, use other arguments as transcript files - if callopts.test: - self._transcript_files = callargs - # If commands were supplied at invocation, then add them to the command queue - elif callargs: - self.cmdqueue.extend(callargs) - # Grab terminal lock before the prompt has been drawn by readline self.terminal_lock.acquire() diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst index 05b5391d..12421601 100644 --- a/docs/freefeatures.rst +++ b/docs/freefeatures.rst @@ -81,13 +81,13 @@ quotation marks if it is more than a one-word command. .. note:: If you wish to disable cmd2's consumption of command-line arguments, you can do so by setting the ``allow_cli_args`` - attribute of your ``cmd2.Cmd`` class instance to ``False``. This would be useful, for example, if you wish to use + argument of your ``cmd2.Cmd`` class instance to ``False``. This would be useful, for example, if you wish to use something like Argparse_ to parse the overall command line arguments for your application:: from cmd2 import Cmd class App(Cmd): def __init__(self): - self.allow_cli_args = False + super().__init__(allow_cli_args=False) .. _Argparse: https://docs.python.org/3/library/argparse.html diff --git a/docs/hooks.rst b/docs/hooks.rst index 35753e59..5db97fe5 100644 --- a/docs/hooks.rst +++ b/docs/hooks.rst @@ -54,7 +54,7 @@ value is ignored. Application Lifecycle Attributes -------------------------------- -There are numerous attributes (member variables of the ``cmd2.Cmd``) which have +There are numerous attributes of and arguments to ``cmd2.Cmd`` which have a significant effect on the application behavior upon entering or during the main loop. A partial list of some of the more important ones is presented here: diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 9eb0befb..538feac1 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -31,9 +31,8 @@ class CmdLineApp(cmd2.Cmd): shortcuts = dict(self.DEFAULT_SHORTCUTS) shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=False, multiline_commands=['orate'], shortcuts=shortcuts) + super().__init__(allow_cli_args=False, use_ipython=False, multiline_commands=['orate'], shortcuts=shortcuts) - self.allow_cli_args = False self.maxrepeats = 3 # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' diff --git a/examples/decorator_example.py b/examples/decorator_example.py index e268c615..bb0d58c0 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -29,9 +29,6 @@ class CmdLineApp(cmd2.Cmd): # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed' - # Disable cmd's usage of command-line arguments as commands to be run at invocation - # self.allow_cli_args = False - # Example of args set from the command-line (but they aren't being used here) self._ip = ip_addr self._port = port diff --git a/examples/persistent_history.py b/examples/persistent_history.py index 12d8b813..e88fd5d9 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -15,8 +15,7 @@ class Cmd2PersistentHistory(cmd2.Cmd): :param hist_file: file to load readline history from at start and write it to at end """ - super().__init__(persistent_history_file=hist_file, persistent_history_length=500) - self.allow_cli_args = False + super().__init__(persistent_history_file=hist_file, persistent_history_length=500, allow_cli_args=False) self.prompt = 'ph> ' # ... your class code here ... diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 2b7b6578..795efcdb 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -25,13 +25,15 @@ from cmd2 import clipboard, constants, utils from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \ HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG - -@pytest.fixture -def outsim_app(): +def CreateOutsimApp(): c = cmd2.Cmd() c.stdout = utils.StdSim(c.stdout) return c +@pytest.fixture +def outsim_app(): + return CreateOutsimApp() + def test_version(base_app): assert cmd2.__version__ @@ -109,9 +111,8 @@ def test_base_show_readonly(base_app): out, err = run_cmd(base_app, 'set -a') expected = normalize(SHOW_TXT + '\nRead only settings:' + """ Commands may be terminated with: {} - Arguments at invocation allowed: {} Output redirection and pipes allowed: {} -""".format(base_app.statement_parser.terminators, base_app.allow_cli_args, base_app.allow_redirection)) +""".format(base_app.statement_parser.terminators, base_app.allow_redirection)) assert out == expected @@ -734,23 +735,26 @@ def test_base_py_interactive(base_app): m.assert_called_once() -def test_base_cmdloop_with_queue(outsim_app): - # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test - outsim_app.use_rawinput = True +def test_base_cmdloop_with_startup_commands(): intro = 'Hello World, this is an intro ...' - outsim_app.cmdqueue.append('quit\n') # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args - testargs = ["prog"] + testargs = ["prog", 'quit'] expected = intro + '\n' + with mock.patch.object(sys, 'argv', testargs): + # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test + app = CreateOutsimApp() + app.use_rawinput = True + # Run the command loop with custom intro - outsim_app.cmdloop(intro=intro) - out = outsim_app.stdout.getvalue() + app.cmdloop(intro=intro) + + out = app.stdout.getvalue() assert out == expected -def test_base_cmdloop_without_queue(outsim_app): +def test_base_cmdloop_without_startup_commands(outsim_app): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test outsim_app.use_rawinput = True outsim_app.intro = 'Hello World, this is an intro ...' @@ -823,8 +827,7 @@ class SayApp(cmd2.Cmd): @pytest.fixture def say_app(): - app = SayApp() - app.allow_cli_args = False + app = SayApp(allow_cli_args=False) app.stdout = utils.StdSim(app.stdout) return app diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d439c123..ce4eac2b 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -264,8 +264,8 @@ def test_register_preloop_hook_with_return_annotation(): def test_preloop_hook(capsys): app = PluggedApp() app.register_preloop_hook(app.prepost_hook_one) - app.cmdqueue.append('say hello') - app.cmdqueue.append('quit') + app._startup_commands.append('say hello') + app._startup_commands.append('quit') app.cmdloop() out, err = capsys.readouterr() assert out == 'one\nhello\n' @@ -275,8 +275,8 @@ def test_preloop_hooks(capsys): app = PluggedApp() app.register_preloop_hook(app.prepost_hook_one) app.register_preloop_hook(app.prepost_hook_two) - app.cmdqueue.append('say hello') - app.cmdqueue.append('quit') + app._startup_commands.append('say hello') + app._startup_commands.append('quit') app.cmdloop() out, err = capsys.readouterr() assert out == 'one\ntwo\nhello\n' @@ -295,8 +295,8 @@ def test_register_postloop_hook_with_wrong_return_annotation(): def test_postloop_hook(capsys): app = PluggedApp() app.register_postloop_hook(app.prepost_hook_one) - app.cmdqueue.append('say hello') - app.cmdqueue.append('quit') + app._startup_commands.append('say hello') + app._startup_commands.append('quit') app.cmdloop() out, err = capsys.readouterr() assert out == 'hello\none\n' @@ -306,8 +306,8 @@ def test_postloop_hooks(capsys): app = PluggedApp() app.register_postloop_hook(app.prepost_hook_one) app.register_postloop_hook(app.prepost_hook_two) - app.cmdqueue.append('say hello') - app.cmdqueue.append('quit') + app._startup_commands.append('say hello') + app._startup_commands.append('quit') app.cmdloop() out, err = capsys.readouterr() assert out == 'hello\none\ntwo\n' diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 4af547b1..214ef87b 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -110,11 +110,6 @@ def test_commands_at_invocation(): ('word_boundaries.txt', False), ]) def test_transcript(request, capsys, filename, feedback_to_output): - # Create a cmd2.Cmd() instance and make sure basic settings are - # like we want for test - app = CmdLineApp() - app.feedback_to_output = feedback_to_output - # Get location of the transcript test_dir = os.path.dirname(request.module.__file__) transcript_file = os.path.join(test_dir, 'transcripts', filename) @@ -123,6 +118,11 @@ def test_transcript(request, capsys, filename, feedback_to_output): # arguments equal to the py.test args testargs = ['prog', '-t', transcript_file] with mock.patch.object(sys, 'argv', testargs): + # Create a cmd2.Cmd() instance and make sure basic settings are + # like we want for test + app = CmdLineApp() + app.feedback_to_output = feedback_to_output + # Run the command loop sys_exit_code = app.cmdloop() assert sys_exit_code == 0 @@ -192,7 +192,6 @@ 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 @@ -203,7 +202,6 @@ def test_load_record_transcript(base_app, request): # Run the load command with the -r option to generate a transcript run_cmd(base_app, 'load {} -t {}'.format(filename, transcript_fname)) - assert base_app.cmdqueue == [] assert base_app._script_dir == [] assert base_app._current_script_dir is None @@ -271,11 +269,6 @@ def test_parse_transcript_expected(expected, transformed): def test_transcript_failure(request, capsys): - # Create a cmd2.Cmd() instance and make sure basic settings are - # like we want for test - app = CmdLineApp() - app.feedback_to_output = False - # Get location of the transcript test_dir = os.path.dirname(request.module.__file__) transcript_file = os.path.join(test_dir, 'transcripts', 'failure.txt') @@ -284,6 +277,11 @@ def test_transcript_failure(request, capsys): # arguments equal to the py.test args testargs = ['prog', '-t', transcript_file] with mock.patch.object(sys, 'argv', testargs): + # Create a cmd2.Cmd() instance and make sure basic settings are + # like we want for test + app = CmdLineApp() + app.feedback_to_output = False + # Run the command loop sys_exit_code = app.cmdloop() assert sys_exit_code != 0 @@ -296,15 +294,15 @@ def test_transcript_failure(request, capsys): def test_transcript_no_file(request, capsys): - # Create a cmd2.Cmd() instance and make sure basic settings are - # like we want for test - app = CmdLineApp() - app.feedback_to_output = False - # Need to patch sys.argv so cmd2 doesn't think it was called with # arguments equal to the py.test args testargs = ['prog', '-t'] with mock.patch.object(sys, 'argv', testargs): + # Create a cmd2.Cmd() instance and make sure basic settings are + # like we want for test + app = CmdLineApp() + app.feedback_to_output = False + # Run the command loop sys_exit_code = app.cmdloop() assert sys_exit_code != 0 |