Skip to content

Commit

Permalink
Removed support for cmd.cmdqueue
Browse files Browse the repository at this point in the history
allow_cli_args is now an argument to __init__ instead of a cmd2 class member
  • Loading branch information
kmvanbrunt committed Jun 11, 2019
1 parent 1aee031 commit 77633bc
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 93 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
91 changes: 48 additions & 43 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -349,6 +350,9 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
: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
Expand All @@ -372,7 +376,6 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
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

Expand Down Expand Up @@ -423,7 +426,6 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
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
Expand Down Expand Up @@ -460,11 +462,33 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
# 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
Expand Down Expand Up @@ -2242,25 +2266,23 @@ def _cmdloop(self) -> None:
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:

Expand All @@ -2274,7 +2296,6 @@ def _cmdloop(self) -> None:
elif rl_type == RlType.PYREADLINE:
# noinspection PyUnresolvedReferences
readline.rl.mode._display_completions = orig_pyreadline_display
self.cmdqueue.clear()

# ----- Alias sub-command functions -----

Expand Down Expand Up @@ -2889,10 +2910,8 @@ def cmdenvironment(self) -> str:
"""
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.
Expand Down Expand Up @@ -3991,7 +4010,6 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
_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
Expand All @@ -4008,19 +4026,6 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
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()

Expand Down
4 changes: 2 additions & 2 deletions docs/freefeatures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
3 changes: 1 addition & 2 deletions examples/cmd_as_argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ def __init__(self):
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'
Expand Down
3 changes: 0 additions & 3 deletions examples/decorator_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ def __init__(self, ip_addr=None, port=None, transcript_files=None):
# 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
Expand Down
3 changes: 1 addition & 2 deletions examples/persistent_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ def __init__(self, hist_file):
: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 ...
Expand Down
33 changes: 18 additions & 15 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
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__

Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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 ...'
Expand Down Expand Up @@ -823,8 +827,7 @@ def do_say(self, arg):

@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

Expand Down
16 changes: 8 additions & 8 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand Down
Loading

0 comments on commit 77633bc

Please sign in to comment.