diff options
-rwxr-xr-x | README.md | 9 | ||||
-rw-r--r-- | cmd2/cmd2.py | 47 | ||||
-rw-r--r-- | docs/settingchanges.rst | 2 | ||||
-rw-r--r-- | docs/unfreefeatures.rst | 5 | ||||
-rwxr-xr-x | examples/arg_print.py | 2 | ||||
-rwxr-xr-x | examples/cmd_as_argument.py | 3 | ||||
-rwxr-xr-x | examples/colors.py | 3 | ||||
-rwxr-xr-x | examples/decorator_example.py | 3 | ||||
-rwxr-xr-x | examples/example.py | 3 | ||||
-rwxr-xr-x | examples/pirate.py | 5 | ||||
-rwxr-xr-x | examples/plumbum_colors.py | 3 | ||||
-rw-r--r-- | tests/test_cmd2.py | 9 | ||||
-rw-r--r-- | tests/test_completion.py | 6 | ||||
-rw-r--r-- | tests/test_transcript.py | 3 |
14 files changed, 50 insertions, 53 deletions
@@ -185,9 +185,9 @@ Instructions for implementing each feature follow. - Multi-line commands - Any command accepts multi-line input when its name is listed in `Cmd.multiline_commands`. - The program will keep expecting input until a line ends with any of the characters - in `Cmd.terminators` . The default terminators are `;` and `/n` (empty newline). + Any command accepts multi-line input when its name is listed the `multiline_commands` optional argument to + `cmd2.Cmd.__init`. The program will keep expecting input until a line ends with any of the characters listed in the + `terminators` optional argument to `cmd2.Cmd.__init__()` . The default terminators are `;` and `/n` (empty newline). - Special-character shortcut commands (beyond cmd's "@" and "!") @@ -239,14 +239,13 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_LAST = ['right?'] def __init__(self): - self.multiline_commands = ['orate'] self.maxrepeats = 3 # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=False) + super().__init__(use_ipython=False, multiline_commands=['orate']) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index cd8ff110..2da8f561 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -293,14 +293,10 @@ class Cmd(cmd.Cmd): Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes. """ - # Attributes used to configure the StatementParser, best not to change these at runtime - multiline_commands = [] - shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} - terminators = [constants.MULTILINE_TERMINATOR] + DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} # Attributes which are NOT dynamically settable at runtime allow_cli_args = True # Should arguments passed on the command-line be processed as commands? - allow_redirection = True # Should output redirection and pipes be allowed default_to_shell = False # Attempt to run unrecognized commands as shell commands quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt reserved_words = [] @@ -338,7 +334,9 @@ class Cmd(cmd.Cmd): 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) -> None: + 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 +347,9 @@ class Cmd(cmd.Cmd): :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 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 + :param shortcuts: (optional) dictionary containing shortcuts for commands """ # If use_ipython is False, make sure the do_ipy() method doesn't exit if not use_ipython: @@ -377,21 +378,21 @@ class Cmd(cmd.Cmd): self.aliases = dict() self.macros = dict() - self._finalize_app_parameters() - self.initial_stdout = sys.stdout self.history = History() self.pystate = {} self.py_history = [] self.pyscript_name = 'app' self.keywords = self.reserved_words + self.get_all_commands() - self.statement_parser = StatementParser( - allow_redirection=self.allow_redirection, - terminators=self.terminators, - multiline_commands=self.multiline_commands, - aliases=self.aliases, - shortcuts=self.shortcuts, - ) + + if shortcuts is None: + shortcuts = self.DEFAULT_SHORTCUTS + shortcuts = sorted(shortcuts.items(), reverse=True) + self.statement_parser = StatementParser(allow_redirection=allow_redirection, + terminators=terminators, + multiline_commands=multiline_commands, + aliases=self.aliases, + shortcuts=shortcuts) self._transcript_files = transcript_files # Used to enable the ability for a Python script to quit the application @@ -545,10 +546,15 @@ class Cmd(cmd.Cmd): """ return utils.strip_ansi(self.prompt) - def _finalize_app_parameters(self) -> None: - """Finalize the shortcuts""" - # noinspection PyUnresolvedReferences - self.shortcuts = sorted(self.shortcuts.items(), reverse=True) + @property + def allow_redirection(self) -> bool: + """Read-only property to get whether or not redirection of stdout is allowed.""" + return self.statement_parser.allow_redirection + + @property + def shortcuts(self) -> List[Tuple[str, str]]: + """Read-only property to access the shortcuts stored in the StatementParser.""" + return self.statement_parser.shortcuts def decolorized_write(self, fileobj: IO, msg: str) -> None: """Write a string to a fileobject, stripping ANSI escape sequences if necessary @@ -2792,7 +2798,8 @@ class Cmd(cmd.Cmd): Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {}""" - return read_only_settings.format(str(self.terminators), self.allow_cli_args, self.allow_redirection) + return read_only_settings.format(str(self.statement_parser.terminators), self.allow_cli_args, + self.allow_redirection) def show(self, args: argparse.Namespace, parameter: str = '') -> None: """Shows current settings of parameters. diff --git a/docs/settingchanges.rst b/docs/settingchanges.rst index e1c437e4..c9345bc6 100644 --- a/docs/settingchanges.rst +++ b/docs/settingchanges.rst @@ -44,7 +44,7 @@ To define more shortcuts, update the dict ``App.shortcuts`` with the Shortcuts need to be created by updating the ``shortcuts`` dictionary attribute prior to calling the ``cmd2.Cmd`` super class ``__init__()`` method. Moreover, that super class init method needs to be called after updating the ``shortcuts`` attribute This warning applies in general to many other attributes which are not - settable at runtime such as ``multiline_commands``, etc. + settable at runtime. Aliases diff --git a/docs/unfreefeatures.rst b/docs/unfreefeatures.rst index 97953215..071a15b2 100644 --- a/docs/unfreefeatures.rst +++ b/docs/unfreefeatures.rst @@ -7,12 +7,11 @@ Multiline commands Command input may span multiple lines for the commands whose names are listed in the -parameter ``app.multiline_commands``. These +``multiline_commands`` argument to ``cmd2.Cmd.__init__()``. These commands will be executed only after the user has entered a *terminator*. By default, the command terminator is -``;``; replacing or appending to the list -``app.terminators`` allows different +``;``; specifying the ``terminators`` optional argument to ``cmd2.Cmd.__init__()`` allows different terminators. A blank line is *always* considered a command terminator (cannot be overridden). diff --git a/examples/arg_print.py b/examples/arg_print.py index 18d21787..a7b938e3 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -24,7 +24,7 @@ class ArgumentAndOptionPrinter(cmd2.Cmd): # Make sure to call this super class __init__ *after* setting and/or updating shortcuts super().__init__() # NOTE: It is critical that the super class __init__ method be called AFTER updating certain parameters which - # are not settable at runtime. This includes the shortcuts, multiline_commands, etc. + # are not settable at runtime. This includes the shortcuts, etc. def do_aprint(self, statement): """Print the argument string this basic command is called with.""" diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index dcec81c8..48397a42 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -30,14 +30,13 @@ class CmdLineApp(cmd2.Cmd): def __init__(self): self.allow_cli_args = False - self.multiline_commands = ['orate'] self.maxrepeats = 3 # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=False) + super().__init__(use_ipython=False, multiline_commands=['orate']) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' diff --git a/examples/colors.py b/examples/colors.py index 62df54e6..6f4912be 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -63,14 +63,13 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_LAST = ['right?'] def __init__(self): - self.multiline_commands = ['orate'] self.maxrepeats = 3 # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=True) + super().__init__(use_ipython=True, multiline_commands=['orate']) # 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 5d127619..873d37cd 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -19,12 +19,11 @@ import cmd2 class CmdLineApp(cmd2.Cmd): """ Example cmd2 application. """ def __init__(self, ip_addr=None, port=None, transcript_files=None): - self.multiline_commands = ['orate'] self.shortcuts.update({'&': 'speak'}) self.maxrepeats = 3 # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=False, transcript_files=transcript_files) + super().__init__(use_ipython=False, transcript_files=transcript_files, multiline_commands=['orate']) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed' diff --git a/examples/example.py b/examples/example.py index 04727ec6..145ddb79 100755 --- a/examples/example.py +++ b/examples/example.py @@ -27,14 +27,13 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_LAST = ['right?'] def __init__(self): - self.multiline_commands = ['orate'] self.maxrepeats = 3 # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=False) + super().__init__(use_ipython=False, multiline_commands=['orate']) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' diff --git a/examples/pirate.py b/examples/pirate.py index 32330404..a58f9a46 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -11,6 +11,7 @@ import argparse from colorama import Fore import cmd2 +from cmd2.constants import MULTILINE_TERMINATOR COLORS = { 'black': Fore.BLACK, @@ -28,15 +29,13 @@ class Pirate(cmd2.Cmd): """A piratical example cmd2 application involving looting and drinking.""" def __init__(self): self.default_to_shell = True - self.multiline_commands = ['sing'] - self.terminators = self.terminators + ['...'] self.songcolor = Fore.BLUE # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'~': 'sing'}) """Initialize the base class as well as this one""" - super().__init__() + super().__init__(multiline_commands=['sing'], terminators=[MULTILINE_TERMINATOR, '...']) # Make songcolor settable at runtime self.settable['songcolor'] = 'Color to ``sing`` in (black/red/green/yellow/blue/magenta/cyan/white)' diff --git a/examples/plumbum_colors.py b/examples/plumbum_colors.py index 3e5031d7..3867081b 100755 --- a/examples/plumbum_colors.py +++ b/examples/plumbum_colors.py @@ -66,14 +66,13 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_LAST = ['right?'] def __init__(self): - self.multiline_commands = ['orate'] self.maxrepeats = 3 # Add stuff to shortcuts before calling base class initializer self.shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=True) + super().__init__(use_ipython=True, multiline_commands=['orate']) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index b3942203..368ca221 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -105,7 +105,7 @@ def test_base_show_readonly(base_app): Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {} -""".format(base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection)) +""".format(base_app.statement_parser.terminators, base_app.allow_cli_args, base_app.allow_redirection)) assert out == expected @@ -557,9 +557,9 @@ def test_feedback_to_output_false(base_app, capsys): os.remove(filename) -def test_allow_redirection(base_app): +def test_disallow_redirection(base_app): # Set allow_redirection to False - base_app.allow_redirection = False + base_app.statement_parser.allow_redirection = False filename = 'test_allow_redirect.txt' @@ -1264,8 +1264,7 @@ def test_which_editor_bad(): class MultilineApp(cmd2.Cmd): def __init__(self, *args, **kwargs): - self.multiline_commands = ['orate'] - super().__init__(*args, **kwargs) + super().__init__(*args, multiline_commands=['orate'], **kwargs) orate_parser = argparse.ArgumentParser() orate_parser.add_argument('-s', '--shout', action="store_true", help="N00B EMULATION MODE") diff --git a/tests/test_completion.py b/tests/test_completion.py index da7fae65..a7c26928 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -635,7 +635,7 @@ def test_tokens_for_completion_redirect(cmd2_app): endidx = len(line) begidx = endidx - len(text) - cmd2_app.allow_redirection = True + cmd2_app.statement_parser.allow_redirection = True expected_tokens = ['command', '|', '<', '>>', 'file'] expected_raw_tokens = ['command', '|', '<', '>>', 'file'] @@ -649,7 +649,7 @@ def test_tokens_for_completion_quoted_redirect(cmd2_app): endidx = len(line) begidx = endidx - len(text) - cmd2_app.allow_redirection = True + cmd2_app.statement_parser.redirection = True expected_tokens = ['command', '>file'] expected_raw_tokens = ['command', '">file'] @@ -663,7 +663,7 @@ def test_tokens_for_completion_redirect_off(cmd2_app): endidx = len(line) begidx = endidx - len(text) - cmd2_app.allow_redirection = False + cmd2_app.statement_parser.allow_redirection = False expected_tokens = ['command', '>file'] expected_raw_tokens = ['command', '>file'] diff --git a/tests/test_transcript.py b/tests/test_transcript.py index f93642b8..4a03ebe0 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -29,10 +29,9 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_LAST = ['right?'] def __init__(self, *args, **kwargs): - self.multiline_commands = ['orate'] self.maxrepeats = 3 - super().__init__(*args, **kwargs) + super().__init__(*args, multiline_commands=['orate'], **kwargs) # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'Max number of `--repeat`s allowed' |