diff options
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | cmd2/cmd2.py | 61 | ||||
-rw-r--r-- | cmd2/parsing.py | 9 | ||||
-rw-r--r-- | cmd2/utils.py | 2 | ||||
-rwxr-xr-x | examples/arg_print.py | 2 | ||||
-rw-r--r-- | tests/conftest.py | 5 | ||||
-rw-r--r-- | tests/test_completion.py | 8 |
7 files changed, 44 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f4283cd..bad61734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,14 @@ * ``alias`` is now an argparse command with subcommands to create, list, and delete aliases * Deprecations * Deprecated the built-in ``cmd2`` support for colors including ``Cmd.colorize()`` and ``Cmd._colorcodes`` - * `unalias` is no longer a command since ``alias delete`` replaced it -* Deletions +* Deletions (potentially breaking changes) * The ``preparse``, ``postparsing_precmd``, and ``postparsing_postcmd`` methods *deprecated* in the previous release have been deleted * The new application lifecycle hook system allows for registration of callbacks to be called at various points in the lifecycle and is more powerful and flexible than the previous system + * ``alias`` is now a command with subcommands to create, list, and delete aliases. Therefore its syntax + has changed. All current alias commands in startup scripts or transcripts will break with this release. + * `unalias` was deleted since ``alias delete`` replaced it ## 0.9.4 (August 21, 2018) * Bug Fixes diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 2256558a..a2c2a46f 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1486,10 +1486,10 @@ class Cmd(cmd.Cmd): if compfunc is None: # There's no completer function, next see if the command uses argparser - cmd_func = self._cmd_func(command) - if cmd_func and hasattr(cmd_func, 'argparser'): + func = self.cmd_func(command) + if func and hasattr(func, 'argparser'): compfunc = functools.partial(self._autocomplete_default, - argparser=getattr(cmd_func, 'argparser')) + argparser=getattr(func, 'argparser')) else: compfunc = self.completedefault @@ -1970,17 +1970,16 @@ class Cmd(cmd.Cmd): self.redirecting = False - def _cmd_func(self, command: str) -> Optional[Callable]: + def cmd_func(self, command: str) -> Optional[Callable]: """ Get the function for a command :param command: the name of the command """ - func_name = self._cmd_func_name(command) + func_name = self.cmd_func_name(command) if func_name: return getattr(self, func_name) - return None - def _cmd_func_name(self, command: str) -> str: + def cmd_func_name(self, command: str) -> str: """Get the method name associated with a given command. :param command: command to look up method name which implements it @@ -2006,9 +2005,9 @@ class Cmd(cmd.Cmd): if statement.command in self.macros: stop = self._run_macro(statement) else: - cmd_func = self._cmd_func(statement.command) - if cmd_func: - stop = cmd_func(statement) + func = self.cmd_func(statement.command) + if func: + stop = func(statement) # Since we have a valid command store it in the history if statement.command not in self.exclude_from_history: @@ -2588,10 +2587,10 @@ class Cmd(cmd.Cmd): matches = [] # Check if this is a command with an argparse function - cmd_func = self._cmd_func(command) - if cmd_func and hasattr(cmd_func, 'argparser'): - completer = AutoCompleter(getattr(cmd_func, 'argparser'), cmd2_app=self) - matches = completer.complete_command_help(tokens[cmd_index:], text, line, begidx, endidx) + func = self.cmd_func(command) + if func and hasattr(func, 'argparser'): + completer = AutoCompleter(getattr(func, 'argparser'), cmd2_app=self) + matches = completer.complete_command_help(tokens[cmd_index:], text, line, begidx, endidx) return matches @@ -2611,21 +2610,15 @@ class Cmd(cmd.Cmd): if not args.command or args.verbose: self._help_menu(args.verbose) - elif args.command: + else: # Getting help for a specific command - cmd_func = self._cmd_func(args.command) - if cmd_func: - # Check to see if this function was decorated with an argparse ArgumentParser - if hasattr(cmd_func, 'argparser'): - completer = AutoCompleter(getattr(cmd_func, 'argparser'), cmd2_app=self) - tokens = [args.command] - tokens.extend(args.subcommand) - self.poutput(completer.format_help(tokens)) - else: - # No special behavior needed, delegate to cmd base class do_help() - super().do_help(args.command) + func = self.cmd_func(args.command) + if func and hasattr(func, 'argparser'): + completer = AutoCompleter(getattr(func, 'argparser'), cmd2_app=self) + tokens = [args.command] + args.subcommand + self.poutput(completer.format_help(tokens)) else: - # This could be a help topic + # No special behavior needed, delegate to cmd base class do_help() super().do_help(args.command) def _help_menu(self, verbose: bool=False) -> None: @@ -2642,12 +2635,12 @@ class Cmd(cmd.Cmd): cmds_cats = {} for command in visible_commands: - cmd_func = self._cmd_func(command) - if command in help_topics or cmd_func.__doc__: + func = self.cmd_func(command) + if command in help_topics or func.__doc__: if command in help_topics: help_topics.remove(command) - if hasattr(cmd_func, HELP_CATEGORY): - category = getattr(cmd_func, HELP_CATEGORY) + if hasattr(func, HELP_CATEGORY): + category = getattr(func, HELP_CATEGORY) cmds_cats.setdefault(category, []) cmds_cats[category].append(command) else: @@ -2700,13 +2693,13 @@ class Cmd(cmd.Cmd): func = getattr(self, 'help_' + command) except AttributeError: # Couldn't find a help function - cmd_func = self._cmd_func(command) + func = self.cmd_func(command) try: # Now see if help_summary has been set - doc = cmd_func.help_summary + doc = func.help_summary except AttributeError: # Last, try to directly access the function's doc-string - doc = cmd_func.__doc__ + doc = func.__doc__ else: # we found the help function result = io.StringIO() diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 27d17d21..e90eac43 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -34,12 +34,12 @@ class MacroArg: # Pattern used to find normal argument # Digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side # Match strings like: {5}, {{{{{4}, {2}}}}} - macro_normal_arg_pattern = re.compile(r'(?<!\{)\{\d+\}|\{\d+\}(?!\})') + macro_normal_arg_pattern = re.compile(r'(?<!{){\d+}|{\d+}(?!})') # Pattern used to find escaped arguments # Digits surrounded by 2 or more braces on both sides # Match strings like: {{5}}, {{{{{4}}, {{2}}}}} - macro_escaped_arg_pattern = re.compile(r'\{{2}\d+\}{2}') + macro_escaped_arg_pattern = re.compile(r'{{2}\d+}{2}') # Finds a string of digits digit_pattern = re.compile(r'\d+') @@ -315,10 +315,11 @@ class StatementParser: if not word: return False, 'cannot be an empty string' - for (shortcut, expansion) in self.shortcuts: + for (shortcut, _) in self.shortcuts: if word.startswith(shortcut): + # Build an error string with all shortcuts listed errmsg = 'cannot start with a shortcut: ' - errmsg += ', '.join(shortcut for (shortcut, expansion) in self.shortcuts) + errmsg += ', '.join(shortcut for (shortcut, _) in self.shortcuts) return False, errmsg errmsg = 'cannot contain: whitespace, quotes, ' diff --git a/cmd2/utils.py b/cmd2/utils.py index 7f7a9b48..ddd43507 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -229,7 +229,7 @@ def natural_keys(input_str: str) -> List[Union[int, str]]: :param input_str: string to convert :return: list of strings and integers """ - return [try_int_or_force_to_lower_case(substr) for substr in re.split('(\d+)', input_str)] + return [try_int_or_force_to_lower_case(substr) for substr in re.split(r'(\d+)', input_str)] def natural_sort(list_to_sort: Iterable[str]) -> List[str]: diff --git a/examples/arg_print.py b/examples/arg_print.py index 6b7d030e..f2168126 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -2,7 +2,7 @@ # coding=utf-8 """A simple example demonstrating the following: 1) How arguments and options get parsed and passed to commands - 2) How to change what syntax get parsed as a comment and stripped from the arguments + 2) How to change what syntax gets parsed as a comment and stripped from the arguments This is intended to serve as a live demonstration so that developers can experiment with and understand how command and argument parsing work. diff --git a/tests/conftest.py b/tests/conftest.py index 93348098..da7e8b08 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -160,9 +160,8 @@ def complete_tester(text: str, line: str, begidx: int, endidx: int, app) -> Opti def get_endidx(): return endidx + # Run the readline tab-completion function with readline mocks in place with mock.patch.object(readline, 'get_line_buffer', get_line): with mock.patch.object(readline, 'get_begidx', get_begidx): with mock.patch.object(readline, 'get_endidx', get_endidx): - # Run the readline tab-completion function with readline mocks in place - first_match = app.complete(text, 0) - return first_match + return app.complete(text, 0) diff --git a/tests/test_completion.py b/tests/test_completion.py index 8bf0cc02..b1ca80b8 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -101,8 +101,7 @@ def test_complete_empty_arg(cmd2_app): expected = sorted(cmd2_app.get_visible_commands()) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and \ - cmd2_app.completion_matches == expected + assert first_match is not None and cmd2_app.completion_matches == expected def test_complete_bogus_command(cmd2_app): text = '' @@ -121,14 +120,15 @@ def test_complete_macro(base_app, request): # Macros do path completion test_dir = os.path.dirname(request.module.__file__) - text = os.path.join(test_dir, 'script.py') + text = os.path.join(test_dir, 's') line = 'fake {}'.format(text) endidx = len(line) begidx = endidx - len(text) + expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep] first_match = complete_tester(text, line, begidx, endidx, base_app) - assert first_match == text + ' ' + assert first_match is not None and base_app.completion_matches def test_cmd2_command_completion_multiple(cmd2_app): |