diff options
author | Eric Lin <anselor@gmail.com> | 2020-08-04 11:56:57 -0400 |
---|---|---|
committer | anselor <anselor@gmail.com> | 2020-08-04 13:38:08 -0400 |
commit | 4c0bdad9acd578536436246023ae884755089040 (patch) | |
tree | cd5e9a4f296982ff870f12023e38a6edd6954d51 | |
parent | 2d24953c71f8e850b5925c3923cbb596de4b0813 (diff) | |
download | cmd2-git-4c0bdad9acd578536436246023ae884755089040.tar.gz |
Minor formatting fixes. Injecting a function into namespace objects before passing to command handlers to access sub-command handlers
-rw-r--r-- | cmd2/argparse_custom.py | 1 | ||||
-rw-r--r-- | cmd2/cmd2.py | 15 | ||||
-rw-r--r-- | cmd2/constants.py | 3 | ||||
-rw-r--r-- | cmd2/decorators.py | 13 | ||||
-rw-r--r-- | cmd2/exceptions.py | 1 | ||||
-rw-r--r-- | docs/features/modular_commands.rst | 6 | ||||
-rw-r--r-- | examples/modular_subcommands.py | 6 | ||||
-rw-r--r-- | isolated_tests/test_commandset/test_commandset.py | 40 |
8 files changed, 54 insertions, 31 deletions
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index e08db005..5dbb9f66 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -584,7 +584,6 @@ def _SubParsersAction_remove_parser(self, name: str): del self.choices[name] - # noinspection PyProtectedMember setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_parser) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 65aa88e0..855431d0 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -413,9 +413,9 @@ class Cmd(cmd.Cmd): existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] for cmdset_type in all_commandset_defs: init_sig = inspect.signature(cmdset_type.__init__) - if not (cmdset_type in existing_commandset_types or - len(init_sig.parameters) != 1 or - 'self' not in init_sig.parameters): + if not (cmdset_type in existing_commandset_types + or len(init_sig.parameters) != 1 + or 'self' not in init_sig.parameters): cmdset = cmdset_type() self.install_command_set(cmdset) @@ -550,7 +550,7 @@ class Cmd(cmd.Cmd): methods = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) - and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX)) + and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX)) for method in methods: command_name = method[0][len(COMMAND_FUNC_PREFIX):] @@ -562,6 +562,7 @@ class Cmd(cmd.Cmd): command_func = self.cmd_func(command_name) command_parser = getattr(command_func, constants.CMD_ATTR_ARGPARSER, None) + def check_parser_uninstallable(parser): for action in parser._actions: if isinstance(action, argparse._SubParsersAction): @@ -621,7 +622,7 @@ class Cmd(cmd.Cmd): command_handler = _partial_passthru(method, self) else: command_handler = method - subcmd_parser.set_defaults(handler=command_handler) + subcmd_parser.set_defaults(cmd2_handler=command_handler) def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: if not subcmd_names: @@ -671,8 +672,8 @@ class Cmd(cmd.Cmd): command_func = self.cmd_func(command_name) if command_func is None: # pragma: no cover - # This really shouldn't be possible since _register_subcommands would prevent this from happening - # but keeping in case it does for some strange reason + # This really shouldn't be possible since _register_subcommands would prevent this from happening + # but keeping in case it does for some strange reason raise CommandSetRegistrationError('Could not find command "{}" needed by subcommand: {}' .format(command_name, str(method))) command_parser = getattr(command_func, constants.CMD_ATTR_ARGPARSER, None) diff --git a/cmd2/constants.py b/cmd2/constants.py index a88ad1e2..aa2ccb6a 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -50,6 +50,9 @@ CMD_ATTR_ARGPARSER = 'argparser' # Whether or not tokens are unquoted before sending to argparse CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes' +# optional attribute +SUBCMD_HANDLER = 'cmd2_handler' + # subcommand attributes for the base command name and the subcommand name SUBCMD_ATTR_COMMAND = 'parent_command' SUBCMD_ATTR_NAME = 'subcommand_name' diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 9704abbf..5947020f 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,6 +1,7 @@ # coding=utf-8 """Decorators for ``cmd2`` commands""" import argparse +import types from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, Union from . import constants @@ -231,6 +232,12 @@ def with_argparser_and_unknown_args(parser: argparse.ArgumentParser, *, raise Cmd2ArgparseError else: setattr(ns, '__statement__', statement) + + def get_handler(self: argparse.Namespace) -> Optional[Callable]: + return getattr(self, constants.SUBCMD_HANDLER, None) + + setattr(ns, 'get_handler', types.MethodType(get_handler, ns)) + args_list = _arg_swap(args, statement, ns, unknown) return func(*args_list, **kwargs) @@ -316,6 +323,12 @@ def with_argparser(parser: argparse.ArgumentParser, *, raise Cmd2ArgparseError else: setattr(ns, '__statement__', statement) + + def get_handler(self: argparse.Namespace) -> Optional[Callable]: + return getattr(self, constants.SUBCMD_HANDLER, None) + + setattr(ns, 'get_handler', types.MethodType(get_handler, ns)) + args_list = _arg_swap(args, statement, ns) return func(*args_list, **kwargs) diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index c1815e1b..b928f293 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -31,6 +31,7 @@ class CommandSetRegistrationError(Exception): # The following exceptions are NOT part of the public API and are intended for internal use only. ############################################################################################################ + class Cmd2ShlexError(Exception): """Raised when shlex fails to parse a command line string in StatementParser""" pass diff --git a/docs/features/modular_commands.rst b/docs/features/modular_commands.rst index 4c7286b7..9823d3ac 100644 --- a/docs/features/modular_commands.rst +++ b/docs/features/modular_commands.rst @@ -314,10 +314,10 @@ command and each CommandSet @with_argparser(cut_parser) def do_cut(self, ns: argparse.Namespace): - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help self.poutput('This command does nothing without sub-parsers registered') diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 1ac951ae..e1bc6b7b 100644 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -95,10 +95,10 @@ class ExampleApp(cmd2.Cmd): @with_argparser(cut_parser) def do_cut(self, ns: argparse.Namespace): - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help self.poutput('This command does nothing without sub-parsers registered') diff --git a/isolated_tests/test_commandset/test_commandset.py b/isolated_tests/test_commandset/test_commandset.py index 5387a9ff..90f0448c 100644 --- a/isolated_tests/test_commandset/test_commandset.py +++ b/isolated_tests/test_commandset/test_commandset.py @@ -281,13 +281,13 @@ class LoadableBase(cmd2.CommandSet): @cmd2.with_argparser(cut_parser) def do_cut(self, cmd: cmd2.Cmd, ns: argparse.Namespace): """Cut something""" - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help - cmd.poutput('This command does nothing without sub-parsers registered') + cmd.pwarning('This command does nothing without sub-parsers registered') cmd.do_help('cut') @@ -297,13 +297,13 @@ class LoadableBase(cmd2.CommandSet): @cmd2.with_argparser(stir_parser) def do_stir(self, cmd: cmd2.Cmd, ns: argparse.Namespace): """Stir something""" - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help - cmd.poutput('This command does nothing without sub-parsers registered') + cmd.pwarning('This command does nothing without sub-parsers registered') cmd.do_help('stir') stir_pasta_parser = cmd2.Cmd2ArgumentParser('pasta', add_help=False) @@ -312,10 +312,10 @@ class LoadableBase(cmd2.CommandSet): @cmd2.as_subcommand_to('stir', 'pasta', stir_pasta_parser) def stir_pasta(self, cmd: cmd2.Cmd, ns: argparse.Namespace): - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: cmd.poutput('Stir pasta haphazardly') @@ -327,10 +327,10 @@ class LoadableBadBase(cmd2.CommandSet): def do_cut(self, cmd: cmd2.Cmd, ns: argparse.Namespace): """Cut something""" - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help cmd.poutput('This command does nothing without sub-parsers registered') @@ -417,6 +417,9 @@ def test_subcommands(command_sets_manual): with pytest.raises(CommandSetRegistrationError): command_sets_manual._register_subcommands(fruit_cmds) + cmd_result = command_sets_manual.app_cmd('cut') + assert 'This command does nothing without sub-parsers registered' in cmd_result.stderr + # verify that command set install without problems command_sets_manual.install_command_set(fruit_cmds) command_sets_manual.install_command_set(veg_cmds) @@ -433,6 +436,9 @@ def test_subcommands(command_sets_manual): # check that the alias shows up correctly assert ['banana', 'bananer', 'bokchoy'] == command_sets_manual.completion_matches + cmd_result = command_sets_manual.app_cmd('cut banana discs') + assert 'cutting banana: discs' in cmd_result.stdout + text = '' line = 'cut bokchoy {}'.format(text) endidx = len(line) @@ -546,10 +552,10 @@ class AppWithSubCommands(cmd2.Cmd): @cmd2.with_argparser(cut_parser) def do_cut(self, ns: argparse.Namespace): """Cut something""" - func = getattr(ns, 'handler', None) - if func is not None: + handler = ns.get_handler() + if handler is not None: # Call whatever subcommand function was selected - func(ns) + handler(ns) else: # No subcommand was provided, so call help self.poutput('This command does nothing without sub-parsers registered') |