summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Lin <anselor@gmail.com>2020-08-04 11:56:57 -0400
committeranselor <anselor@gmail.com>2020-08-04 13:38:08 -0400
commit4c0bdad9acd578536436246023ae884755089040 (patch)
treecd5e9a4f296982ff870f12023e38a6edd6954d51
parent2d24953c71f8e850b5925c3923cbb596de4b0813 (diff)
downloadcmd2-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.py1
-rw-r--r--cmd2/cmd2.py15
-rw-r--r--cmd2/constants.py3
-rw-r--r--cmd2/decorators.py13
-rw-r--r--cmd2/exceptions.py1
-rw-r--r--docs/features/modular_commands.rst6
-rw-r--r--examples/modular_subcommands.py6
-rw-r--r--isolated_tests/test_commandset/test_commandset.py40
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')