Skip to content

Commit 77633bc

Browse files
committed
Removed support for cmd.cmdqueue
allow_cli_args is now an argument to __init__ instead of a cmd2 class member
1 parent 1aee031 commit 77633bc

10 files changed

+96
-93
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
* Removed internally used `eos` command that was used to keep track of when a text script's commands ended
5050
* Removed `cmd2` member called `_STOP_AND_EXIT` since it was just a boolean value that should always be True
5151
* Removed `cmd2` member called `_should_quit` since `PyscriptBridge` now handles this logic
52+
* Removed support for `cmd.cmdqueue`
53+
* `allow_cli_args` is now an argument to __init__ instead of a `cmd2` class member
5254
* **Python 3.4 EOL notice**
5355
* Python 3.4 reached its [end of life](https://www.python.org/dev/peps/pep-0429/) on March 18, 2019
5456
* This is the last release of `cmd2` which will support Python 3.4

Diff for: cmd2/cmd2.py

+48-43
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,12 @@ class Cmd(cmd.Cmd):
335335
DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
336336
DEFAULT_EDITOR = utils.find_editor()
337337

338-
def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent_history_file: str = '',
339-
persistent_history_length: int = 1000, startup_script: Optional[str] = None, use_ipython: bool = False,
340-
transcript_files: Optional[List[str]] = None, allow_redirection: bool = True,
341-
multiline_commands: Optional[List[str]] = None, terminators: Optional[List[str]] = None,
342-
shortcuts: Optional[Dict[str, str]] = None) -> None:
338+
def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
339+
persistent_history_file: str = '', persistent_history_length: int = 1000,
340+
startup_script: Optional[str] = None, use_ipython: bool = False,
341+
allow_cli_args: bool = True, transcript_files: Optional[List[str]] = None,
342+
allow_redirection: bool = True, multiline_commands: Optional[List[str]] = None,
343+
terminators: Optional[List[str]] = None, shortcuts: Optional[Dict[str, str]] = None) -> None:
343344
"""An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
344345
345346
:param completekey: (optional) readline name of a completion key, default to Tab
@@ -349,6 +350,9 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
349350
:param persistent_history_length: (optional) max number of history items to write to the persistent history file
350351
:param startup_script: (optional) file path to a a script to load and execute at startup
351352
:param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell
353+
:param allow_cli_args: (optional) if True, then cmd2 will process command line arguments as either
354+
commands to be run or, if -t is specified, transcript files to run.
355+
This should be set to False if your application parses its own arguments.
352356
:param transcript_files: (optional) allows running transcript tests when allow_cli_args is False
353357
:param allow_redirection: (optional) should output redirection and pipes be allowed
354358
:param multiline_commands: (optional) list of commands allowed to accept multi-line input
@@ -372,7 +376,6 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
372376
super().__init__(completekey=completekey, stdin=stdin, stdout=stdout)
373377

374378
# Attributes which should NOT be dynamically settable at runtime
375-
self.allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
376379
self.default_to_shell = False # Attempt to run unrecognized commands as shell commands
377380
self.quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt
378381

@@ -423,7 +426,6 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
423426
terminators=terminators,
424427
multiline_commands=multiline_commands,
425428
shortcuts=shortcuts)
426-
self._transcript_files = transcript_files
427429

428430
# True if running inside a Python script or interactive console, False otherwise
429431
self._in_py = False
@@ -460,11 +462,33 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
460462
# If this string is non-empty, then this warning message will print if a broken pipe error occurs while printing
461463
self.broken_pipe_warning = ''
462464

465+
# Commands that will run at the beginning of the command loop
466+
self._startup_commands = []
467+
463468
# If a startup script is provided, then add it in the queue to load
464469
if startup_script is not None:
465470
startup_script = os.path.abspath(os.path.expanduser(startup_script))
466471
if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0:
467-
self.cmdqueue.append("load '{}'".format(startup_script))
472+
self._startup_commands.append("load '{}'".format(startup_script))
473+
474+
# Transcript files to run instead of interactive command loop
475+
self._transcript_files = None
476+
477+
# Check for command line args
478+
if allow_cli_args:
479+
parser = argparse.ArgumentParser()
480+
parser.add_argument('-t', '--test', action="store_true",
481+
help='Test against transcript(s) in FILE (wildcards OK)')
482+
callopts, callargs = parser.parse_known_args()
483+
484+
# If transcript testing was called for, use other arguments as transcript files
485+
if callopts.test:
486+
self._transcript_files = callargs
487+
# If commands were supplied at invocation, then add them to the command queue
488+
elif callargs:
489+
self._startup_commands.extend(callargs)
490+
elif transcript_files:
491+
self._transcript_files = transcript_files
468492

469493
# The default key for sorting tab completion matches. This only applies when the matches are not
470494
# already marked as sorted by setting self.matches_sorted to True. Its default value performs a
@@ -2242,25 +2266,23 @@ def _cmdloop(self) -> None:
22422266
readline.parse_and_bind(self.completekey + ": complete")
22432267

22442268
try:
2245-
stop = False
2269+
# Run startup commands
2270+
stop = self.runcmds_plus_hooks(self._startup_commands)
2271+
self._startup_commands.clear()
2272+
22462273
while not stop:
2247-
if self.cmdqueue:
2248-
# Run commands out of cmdqueue if nonempty (populated by commands at invocation)
2249-
stop = self.runcmds_plus_hooks(self.cmdqueue)
2250-
self.cmdqueue.clear()
2251-
else:
2252-
# Otherwise, read a command from stdin
2253-
try:
2254-
line = self.pseudo_raw_input(self.prompt)
2255-
except KeyboardInterrupt as ex:
2256-
if self.quit_on_sigint:
2257-
raise ex
2258-
else:
2259-
self.poutput('^C')
2260-
line = ''
2274+
# Get commands from user
2275+
try:
2276+
line = self.pseudo_raw_input(self.prompt)
2277+
except KeyboardInterrupt as ex:
2278+
if self.quit_on_sigint:
2279+
raise ex
2280+
else:
2281+
self.poutput('^C')
2282+
line = ''
22612283

2262-
# Run the command along with all associated pre and post hooks
2263-
stop = self.onecmd_plus_hooks(line)
2284+
# Run the command along with all associated pre and post hooks
2285+
stop = self.onecmd_plus_hooks(line)
22642286
finally:
22652287
if self.use_rawinput and self.completekey and rl_type != RlType.NONE:
22662288

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

22792300
# ----- Alias sub-command functions -----
22802301

@@ -2889,10 +2910,8 @@ def cmdenvironment(self) -> str:
28892910
"""
28902911
read_only_settings = """
28912912
Commands may be terminated with: {}
2892-
Arguments at invocation allowed: {}
28932913
Output redirection and pipes allowed: {}"""
2894-
return read_only_settings.format(str(self.statement_parser.terminators), self.allow_cli_args,
2895-
self.allow_redirection)
2914+
return read_only_settings.format(str(self.statement_parser.terminators), self.allow_redirection)
28962915

28972916
def show(self, args: argparse.Namespace, parameter: str = '') -> None:
28982917
"""Shows current settings of parameters.
@@ -3991,7 +4010,6 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
39914010
39924011
_cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with
39934012
the following extra features provided by cmd2:
3994-
- commands at invocation
39954013
- transcript testing
39964014
- intro banner
39974015
- exit code
@@ -4008,19 +4026,6 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
40084026
original_sigint_handler = signal.getsignal(signal.SIGINT)
40094027
signal.signal(signal.SIGINT, self.sigint_handler)
40104028

4011-
if self.allow_cli_args:
4012-
parser = argparse.ArgumentParser()
4013-
parser.add_argument('-t', '--test', action="store_true",
4014-
help='Test against transcript(s) in FILE (wildcards OK)')
4015-
callopts, callargs = parser.parse_known_args()
4016-
4017-
# If transcript testing was called for, use other arguments as transcript files
4018-
if callopts.test:
4019-
self._transcript_files = callargs
4020-
# If commands were supplied at invocation, then add them to the command queue
4021-
elif callargs:
4022-
self.cmdqueue.extend(callargs)
4023-
40244029
# Grab terminal lock before the prompt has been drawn by readline
40254030
self.terminal_lock.acquire()
40264031

Diff for: docs/freefeatures.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ quotation marks if it is more than a one-word command.
8181
.. note::
8282

8383
If you wish to disable cmd2's consumption of command-line arguments, you can do so by setting the ``allow_cli_args``
84-
attribute of your ``cmd2.Cmd`` class instance to ``False``. This would be useful, for example, if you wish to use
84+
argument of your ``cmd2.Cmd`` class instance to ``False``. This would be useful, for example, if you wish to use
8585
something like Argparse_ to parse the overall command line arguments for your application::
8686

8787
from cmd2 import Cmd
8888
class App(Cmd):
8989
def __init__(self):
90-
self.allow_cli_args = False
90+
super().__init__(allow_cli_args=False)
9191

9292
.. _Argparse: https://docs.python.org/3/library/argparse.html
9393

Diff for: docs/hooks.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ value is ignored.
5454
Application Lifecycle Attributes
5555
--------------------------------
5656

57-
There are numerous attributes (member variables of the ``cmd2.Cmd``) which have
57+
There are numerous attributes of and arguments to ``cmd2.Cmd`` which have
5858
a significant effect on the application behavior upon entering or during the
5959
main loop. A partial list of some of the more important ones is presented here:
6060

Diff for: examples/cmd_as_argument.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ def __init__(self):
3131
shortcuts = dict(self.DEFAULT_SHORTCUTS)
3232
shortcuts.update({'&': 'speak'})
3333
# Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell
34-
super().__init__(use_ipython=False, multiline_commands=['orate'], shortcuts=shortcuts)
34+
super().__init__(allow_cli_args=False, use_ipython=False, multiline_commands=['orate'], shortcuts=shortcuts)
3535

36-
self.allow_cli_args = False
3736
self.maxrepeats = 3
3837
# Make maxrepeats settable at runtime
3938
self.settable['maxrepeats'] = 'max repetitions for speak command'

Diff for: examples/decorator_example.py

-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ def __init__(self, ip_addr=None, port=None, transcript_files=None):
2929
# Make maxrepeats settable at runtime
3030
self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed'
3131

32-
# Disable cmd's usage of command-line arguments as commands to be run at invocation
33-
# self.allow_cli_args = False
34-
3532
# Example of args set from the command-line (but they aren't being used here)
3633
self._ip = ip_addr
3734
self._port = port

Diff for: examples/persistent_history.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ def __init__(self, hist_file):
1515
1616
:param hist_file: file to load readline history from at start and write it to at end
1717
"""
18-
super().__init__(persistent_history_file=hist_file, persistent_history_length=500)
19-
self.allow_cli_args = False
18+
super().__init__(persistent_history_file=hist_file, persistent_history_length=500, allow_cli_args=False)
2019
self.prompt = 'ph> '
2120

2221
# ... your class code here ...

Diff for: tests/test_cmd2.py

+18-15
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \
2626
HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG
2727

28-
29-
@pytest.fixture
30-
def outsim_app():
28+
def CreateOutsimApp():
3129
c = cmd2.Cmd()
3230
c.stdout = utils.StdSim(c.stdout)
3331
return c
3432

33+
@pytest.fixture
34+
def outsim_app():
35+
return CreateOutsimApp()
36+
3537
def test_version(base_app):
3638
assert cmd2.__version__
3739

@@ -109,9 +111,8 @@ def test_base_show_readonly(base_app):
109111
out, err = run_cmd(base_app, 'set -a')
110112
expected = normalize(SHOW_TXT + '\nRead only settings:' + """
111113
Commands may be terminated with: {}
112-
Arguments at invocation allowed: {}
113114
Output redirection and pipes allowed: {}
114-
""".format(base_app.statement_parser.terminators, base_app.allow_cli_args, base_app.allow_redirection))
115+
""".format(base_app.statement_parser.terminators, base_app.allow_redirection))
115116
assert out == expected
116117

117118

@@ -734,23 +735,26 @@ def test_base_py_interactive(base_app):
734735
m.assert_called_once()
735736

736737

737-
def test_base_cmdloop_with_queue(outsim_app):
738-
# Create a cmd2.Cmd() instance and make sure basic settings are like we want for test
739-
outsim_app.use_rawinput = True
738+
def test_base_cmdloop_with_startup_commands():
740739
intro = 'Hello World, this is an intro ...'
741-
outsim_app.cmdqueue.append('quit\n')
742740

743741
# Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args
744-
testargs = ["prog"]
742+
testargs = ["prog", 'quit']
745743
expected = intro + '\n'
744+
746745
with mock.patch.object(sys, 'argv', testargs):
746+
# Create a cmd2.Cmd() instance and make sure basic settings are like we want for test
747+
app = CreateOutsimApp()
748+
app.use_rawinput = True
749+
747750
# Run the command loop with custom intro
748-
outsim_app.cmdloop(intro=intro)
749-
out = outsim_app.stdout.getvalue()
751+
app.cmdloop(intro=intro)
752+
753+
out = app.stdout.getvalue()
750754
assert out == expected
751755

752756

753-
def test_base_cmdloop_without_queue(outsim_app):
757+
def test_base_cmdloop_without_startup_commands(outsim_app):
754758
# Create a cmd2.Cmd() instance and make sure basic settings are like we want for test
755759
outsim_app.use_rawinput = True
756760
outsim_app.intro = 'Hello World, this is an intro ...'
@@ -823,8 +827,7 @@ def do_say(self, arg):
823827

824828
@pytest.fixture
825829
def say_app():
826-
app = SayApp()
827-
app.allow_cli_args = False
830+
app = SayApp(allow_cli_args=False)
828831
app.stdout = utils.StdSim(app.stdout)
829832
return app
830833

Diff for: tests/test_plugin.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ def test_register_preloop_hook_with_return_annotation():
264264
def test_preloop_hook(capsys):
265265
app = PluggedApp()
266266
app.register_preloop_hook(app.prepost_hook_one)
267-
app.cmdqueue.append('say hello')
268-
app.cmdqueue.append('quit')
267+
app._startup_commands.append('say hello')
268+
app._startup_commands.append('quit')
269269
app.cmdloop()
270270
out, err = capsys.readouterr()
271271
assert out == 'one\nhello\n'
@@ -275,8 +275,8 @@ def test_preloop_hooks(capsys):
275275
app = PluggedApp()
276276
app.register_preloop_hook(app.prepost_hook_one)
277277
app.register_preloop_hook(app.prepost_hook_two)
278-
app.cmdqueue.append('say hello')
279-
app.cmdqueue.append('quit')
278+
app._startup_commands.append('say hello')
279+
app._startup_commands.append('quit')
280280
app.cmdloop()
281281
out, err = capsys.readouterr()
282282
assert out == 'one\ntwo\nhello\n'
@@ -295,8 +295,8 @@ def test_register_postloop_hook_with_wrong_return_annotation():
295295
def test_postloop_hook(capsys):
296296
app = PluggedApp()
297297
app.register_postloop_hook(app.prepost_hook_one)
298-
app.cmdqueue.append('say hello')
299-
app.cmdqueue.append('quit')
298+
app._startup_commands.append('say hello')
299+
app._startup_commands.append('quit')
300300
app.cmdloop()
301301
out, err = capsys.readouterr()
302302
assert out == 'hello\none\n'
@@ -306,8 +306,8 @@ def test_postloop_hooks(capsys):
306306
app = PluggedApp()
307307
app.register_postloop_hook(app.prepost_hook_one)
308308
app.register_postloop_hook(app.prepost_hook_two)
309-
app.cmdqueue.append('say hello')
310-
app.cmdqueue.append('quit')
309+
app._startup_commands.append('say hello')
310+
app._startup_commands.append('quit')
311311
app.cmdloop()
312312
out, err = capsys.readouterr()
313313
assert out == 'hello\none\ntwo\n'

0 commit comments

Comments
 (0)