summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2019-06-11 17:39:11 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2019-06-11 17:39:11 -0400
commit77633bc4498d6f11b07ea0a3bd13d4830f809ff8 (patch)
tree037879fb49353ac66a5d2a3e6c74f0cb3a3b0dd2
parent1aee0316973873e8967679f6778952c1d696866a (diff)
downloadcmd2-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.md2
-rw-r--r--cmd2/cmd2.py91
-rw-r--r--docs/freefeatures.rst4
-rw-r--r--docs/hooks.rst2
-rwxr-xr-xexamples/cmd_as_argument.py3
-rwxr-xr-xexamples/decorator_example.py3
-rwxr-xr-xexamples/persistent_history.py3
-rw-r--r--tests/test_cmd2.py33
-rw-r--r--tests/test_plugin.py16
-rw-r--r--tests/test_transcript.py32
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