diff options
| author | Eric Lin <anselor@gmail.com> | 2021-08-19 17:03:42 -0400 |
|---|---|---|
| committer | anselor <anselor@gmail.com> | 2021-08-23 14:17:12 -0400 |
| commit | 49805324772ec5fcd06217f4f0a241a9a8d07960 (patch) | |
| tree | a1647a18085df118bbe12dc37c67104f2817024b /cmd2 | |
| parent | 6d771e96c0507d9ef4ad3eaaf4bc83669396e591 (diff) | |
| download | cmd2-git-49805324772ec5fcd06217f4f0a241a9a8d07960.tar.gz | |
* New function `set_default_command_completer_type()` allows developer to extend and modify the
behavior of `ArgparseCompleter`.
* New function `register_argparse_argument_parameter()` allows developers to specify custom
parameters to be passed to the argparse parser's `add_argument()` method. These parameters will
become accessible in the resulting argparse Action object when modifying `ArgparseCompleter` behavior.
Diffstat (limited to 'cmd2')
| -rw-r--r-- | cmd2/__init__.py | 12 | ||||
| -rw-r--r-- | cmd2/argparse_completer.py | 45 | ||||
| -rw-r--r-- | cmd2/argparse_custom.py | 78 | ||||
| -rw-r--r-- | cmd2/cmd2.py | 66 | ||||
| -rw-r--r-- | cmd2/constants.py | 1 | ||||
| -rw-r--r-- | cmd2/decorators.py | 7 |
6 files changed, 169 insertions, 40 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py index e545f394..776e783c 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -20,7 +20,13 @@ except importlib_metadata.PackageNotFoundError: # pragma: no cover from typing import List from .ansi import style, fg, bg -from .argparse_custom import Cmd2ArgumentParser, Cmd2AttributeWrapper, CompletionItem, set_default_argument_parser +from .argparse_custom import ( + Cmd2ArgumentParser, + Cmd2AttributeWrapper, + CompletionItem, + register_argparse_argument_parameter, + set_default_argument_parser, +) # Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER import argparse @@ -31,6 +37,7 @@ if cmd2_parser_module is not None: importlib.import_module(cmd2_parser_module) +from .argparse_completer import DEFAULT_COMMAND_COMPLETER, set_default_command_completer_type # Get the current value for argparse_custom.DEFAULT_ARGUMENT_PARSER from .argparse_custom import DEFAULT_ARGUMENT_PARSER from .cmd2 import Cmd @@ -47,6 +54,7 @@ from .utils import categorize, CompletionMode, CustomCompletionSettings, Settabl __all__: List[str] = [ 'COMMAND_NAME', 'DEFAULT_ARGUMENT_PARSER', + 'DEFAULT_COMMAND_COMPLETER', 'DEFAULT_SHORTCUTS', # ANSI Style exports 'bg', @@ -56,7 +64,9 @@ __all__: List[str] = [ 'Cmd2ArgumentParser', 'Cmd2AttributeWrapper', 'CompletionItem', + 'register_argparse_argument_parameter', 'set_default_argument_parser', + 'set_default_command_completer_type', # Cmd2 'Cmd', 'CommandResult', diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index a244941c..584eb74e 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -13,18 +13,28 @@ from collections import ( deque, ) from typing import ( + TYPE_CHECKING, Dict, List, Optional, + Type, Union, cast, ) -from . import ( - ansi, - cmd2, - constants, +from .ansi import ( + style_aware_wcswidth, + widest_line, ) +from .constants import ( + INFINITY, +) + +if TYPE_CHECKING: + from .cmd2 import ( + Cmd, + ) + from .argparse_custom import ( ChoicesCallable, ChoicesProviderFuncWithTokens, @@ -124,10 +134,10 @@ class _ArgumentState: self.max = 1 elif self.action.nargs == argparse.ZERO_OR_MORE or self.action.nargs == argparse.REMAINDER: self.min = 0 - self.max = constants.INFINITY + self.max = INFINITY elif self.action.nargs == argparse.ONE_OR_MORE: self.min = 1 - self.max = constants.INFINITY + self.max = INFINITY else: self.min = self.action.nargs self.max = self.action.nargs @@ -165,7 +175,7 @@ class ArgparseCompleter: """Automatic command line tab completion based on argparse parameters""" def __init__( - self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *, parent_tokens: Optional[Dict[str, List[str]]] = None + self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[Dict[str, List[str]]] = None ) -> None: """ Create an ArgparseCompleter @@ -564,15 +574,15 @@ class ArgparseCompleter: desc_header = desc_header.replace('\t', four_spaces) # Calculate needed widths for the token and description columns of the table - token_width = ansi.style_aware_wcswidth(destination) - desc_width = ansi.widest_line(desc_header) + token_width = style_aware_wcswidth(destination) + desc_width = widest_line(desc_header) for item in completion_items: - token_width = max(ansi.style_aware_wcswidth(item), token_width) + token_width = max(style_aware_wcswidth(item), token_width) # Replace tabs with 4 spaces so we can calculate width item.description = item.description.replace('\t', four_spaces) - desc_width = max(ansi.widest_line(item.description), desc_width) + desc_width = max(widest_line(item.description), desc_width) cols = list() cols.append(Column(destination.upper(), width=token_width)) @@ -728,3 +738,16 @@ class ArgparseCompleter: return [] return self._format_completions(arg_state, results) + + +DEFAULT_COMMAND_COMPLETER: Type[ArgparseCompleter] = ArgparseCompleter + + +def set_default_command_completer_type(completer_type: Type[ArgparseCompleter]) -> None: + """ + Set the default command completer type. It must be a sub-class of the ArgparseCompleter. + + :param completer_type: Type that is a subclass of ArgparseCompleter. + """ + global DEFAULT_COMMAND_COMPLETER + DEFAULT_COMMAND_COMPLETER = completer_type diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 770b1e5c..2bcb0af9 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -238,6 +238,7 @@ from typing import ( NoReturn, Optional, Sequence, + Set, Tuple, Type, Union, @@ -639,9 +640,73 @@ setattr(argparse.Action, 'set_suppress_tab_hint', _action_set_suppress_tab_hint) ############################################################################################################ +# Allow developers to add custom action attributes +############################################################################################################ + +CUSTOM_ACTION_ATTRIBS: Set[str] = set() +_CUSTOM_ATTRIB_PFX = '_attr_' + + +def register_argparse_argument_parameter(param_name: str, param_type: Optional[Type[Any]]) -> None: + """ + Registers a custom argparse argument parameter. + + The registered name will then be a recognized keyword parameter to the parser's `add_argument()` function. + + An accessor functions will be added to the parameter's Action object in the form of: ``get_{param_name}()`` + and ``set_{param_name}(value)``. + + :param param_name: Name of the parameter to add. + """ + attr_name = f'{_CUSTOM_ATTRIB_PFX}{param_name}' + if param_name in CUSTOM_ACTION_ATTRIBS or hasattr(argparse.Action, attr_name): + raise KeyError(f'Custom parameter {param_name} already exists') + if not re.search('^[A-Za-z_][A-Za-z0-9_]*$', param_name): + raise KeyError(f'Invalid parameter name {param_name} - cannot be used as a python identifier') + + getter_name = f'get_{param_name}' + + def _action_get_custom_parameter(self: argparse.Action) -> Any: + f""" + Get the custom {param_name} attribute of an argparse Action. + + This function is added by cmd2 as a method called ``{getter_name}()`` to ``argparse.Action`` class. + + To call: ``action.{getter_name}()`` + + :param self: argparse Action being queried + :return: The value of {param_name} or None if attribute does not exist + """ + return getattr(self, attr_name, None) + + setattr(argparse.Action, getter_name, _action_get_custom_parameter) + + setter_name = f'set_{param_name}' + + def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None: + f""" + Set the custom {param_name} attribute of an argparse Action. + + This function is added by cmd2 as a method called ``{setter_name}()`` to ``argparse.Action`` class. + + To call: ``action.{setter_name}({param_name})`` + + :param self: argparse Action being updated + :param value: value being assigned + """ + if param_type and not isinstance(value, param_type): + raise TypeError(f'{param_name} must be of type {param_type}, got: {value} ({type(value)})') + setattr(self, attr_name, value) + + setattr(argparse.Action, setter_name, _action_set_custom_parameter) + + CUSTOM_ACTION_ATTRIBS.add(param_name) + +############################################################################################################ # Patch _ActionsContainer.add_argument with our wrapper to support more arguments ############################################################################################################ + # Save original _ActionsContainer.add_argument so we can call it in our wrapper # noinspection PyProtectedMember orig_actions_container_add_argument = argparse._ActionsContainer.add_argument @@ -751,6 +816,14 @@ def _add_argument_wrapper( # Add the argparse-recognized version of nargs to kwargs kwargs['nargs'] = nargs_adjusted + # Extract registered custom keyword arguments + custom_attribs: Dict[str, Any] = {} + for keyword, value in kwargs.items(): + if keyword in CUSTOM_ACTION_ATTRIBS: + custom_attribs[keyword] = value + for keyword in custom_attribs: + del kwargs[keyword] + # Create the argument using the original add_argument function new_arg = orig_actions_container_add_argument(self, *args, **kwargs) @@ -765,6 +838,11 @@ def _add_argument_wrapper( new_arg.set_suppress_tab_hint(suppress_tab_hint) # type: ignore[attr-defined] new_arg.set_descriptive_header(descriptive_header) # type: ignore[attr-defined] + for keyword, value in custom_attribs.items(): + attr_setter = getattr(new_arg, f'set_{keyword}', None) + if attr_setter is not None: + attr_setter(value) + return new_arg diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index df4d1dc2..a1707aee 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -71,12 +71,13 @@ from typing import ( from . import ( ansi, + argparse_completer, + argparse_custom, constants, plugin, utils, ) from .argparse_custom import ( - DEFAULT_ARGUMENT_PARSER, ChoicesProviderFunc, CompleterFunc, CompletionItem, @@ -405,7 +406,7 @@ class Cmd(cmd.Cmd): # Check for command line args if allow_cli_args: - parser = DEFAULT_ARGUMENT_PARSER() + parser = argparse_custom.DEFAULT_ARGUMENT_PARSER() parser.add_argument('-t', '--test', action="store_true", help='Test against transcript(s) in FILE (wildcards OK)') callopts, callargs = parser.parse_known_args() @@ -1900,10 +1901,16 @@ class Cmd(cmd.Cmd): # There's no completer function, next see if the command uses argparse func = self.cmd_func(command) argparser = getattr(func, constants.CMD_ATTR_ARGPARSER, None) + completer_type = getattr(func, constants.CMD_ATTR_COMPLETER, argparse_completer.DEFAULT_COMMAND_COMPLETER) + if completer_type is None: + completer_type = argparse_completer.DEFAULT_COMMAND_COMPLETER if func is not None and argparser is not None: cmd_set = self._cmd_to_command_sets[command] if command in self._cmd_to_command_sets else None - completer = ArgparseCompleter(argparser, self) + if completer_type is not None: + completer = completer_type(argparser, self) + else: + completer = ArgparseCompleter(argparser, self) preserve_quotes = getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES) completer_func = functools.partial( @@ -2072,7 +2079,7 @@ class Cmd(cmd.Cmd): break else: # No shortcut was found. Complete the command token. - parser = DEFAULT_ARGUMENT_PARSER(add_help=False) + parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(add_help=False) parser.add_argument( 'command', metavar="COMMAND", @@ -2879,7 +2886,7 @@ class Cmd(cmd.Cmd): # Set custom completion settings else: if parser is None: - parser = DEFAULT_ARGUMENT_PARSER(add_help=False) + parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(add_help=False) parser.add_argument( 'arg', suppress_tab_hint=True, @@ -3081,7 +3088,7 @@ class Cmd(cmd.Cmd): # Top-level parser for alias alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string." alias_epilog = "See also:\n" " macro" - alias_parser = DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog) + alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog) alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') alias_subparsers.required = True @@ -3110,7 +3117,8 @@ class Cmd(cmd.Cmd): " alias create save_results print_results \">\" out.txt\n" ) - alias_create_parser = DEFAULT_ARGUMENT_PARSER(description=alias_create_description, epilog=alias_create_epilog) + alias_create_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_create_description, + epilog=alias_create_epilog) alias_create_parser.add_argument('name', help='name of this alias') alias_create_parser.add_argument( 'command', help='what the alias resolves to', choices_provider=_get_commands_aliases_and_macros_for_completion @@ -3156,7 +3164,7 @@ class Cmd(cmd.Cmd): alias_delete_help = "delete aliases" alias_delete_description = "Delete specified aliases or all aliases if --all is used" - alias_delete_parser = DEFAULT_ARGUMENT_PARSER(description=alias_delete_description) + alias_delete_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_delete_description) alias_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all aliases") alias_delete_parser.add_argument( 'names', @@ -3191,7 +3199,7 @@ class Cmd(cmd.Cmd): "Without arguments, all aliases will be listed." ) - alias_list_parser = DEFAULT_ARGUMENT_PARSER(description=alias_list_description) + alias_list_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_list_description) alias_list_parser.add_argument( 'names', nargs=argparse.ZERO_OR_MORE, @@ -3239,7 +3247,7 @@ class Cmd(cmd.Cmd): # Top-level parser for macro macro_description = "Manage macros\n" "\n" "A macro is similar to an alias, but it can contain argument placeholders." macro_epilog = "See also:\n" " alias" - macro_parser = DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog) + macro_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog) macro_subparsers = macro_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') macro_subparsers.required = True @@ -3292,7 +3300,8 @@ class Cmd(cmd.Cmd): " will only complete paths while typing a macro." ) - macro_create_parser = DEFAULT_ARGUMENT_PARSER(description=macro_create_description, epilog=macro_create_epilog) + macro_create_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_create_description, + epilog=macro_create_epilog) macro_create_parser.add_argument('name', help='name of this macro') macro_create_parser.add_argument( 'command', help='what the macro resolves to', choices_provider=_get_commands_aliases_and_macros_for_completion @@ -3382,7 +3391,7 @@ class Cmd(cmd.Cmd): # macro -> delete macro_delete_help = "delete macros" macro_delete_description = "Delete specified macros or all macros if --all is used" - macro_delete_parser = DEFAULT_ARGUMENT_PARSER(description=macro_delete_description) + macro_delete_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_delete_description) macro_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all macros") macro_delete_parser.add_argument( 'names', @@ -3417,7 +3426,7 @@ class Cmd(cmd.Cmd): "Without arguments, all macros will be listed." ) - macro_list_parser = DEFAULT_ARGUMENT_PARSER(description=macro_list_description) + macro_list_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_list_description) macro_list_parser.add_argument( 'names', nargs=argparse.ZERO_OR_MORE, @@ -3490,7 +3499,7 @@ class Cmd(cmd.Cmd): completer = ArgparseCompleter(argparser, self) return completer.complete_subcommand_help(text, line, begidx, endidx, arg_tokens['subcommands']) - help_parser = DEFAULT_ARGUMENT_PARSER( + help_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER( description="List available commands or provide " "detailed help for a specific command" ) help_parser.add_argument( @@ -3670,7 +3679,7 @@ class Cmd(cmd.Cmd): command = '' self.poutput() - shortcuts_parser = DEFAULT_ARGUMENT_PARSER(description="List available shortcuts") + shortcuts_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="List available shortcuts") @with_argparser(shortcuts_parser) def do_shortcuts(self, _: argparse.Namespace) -> None: @@ -3680,7 +3689,8 @@ class Cmd(cmd.Cmd): result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts) self.poutput(f"Shortcuts for other commands:\n{result}") - eof_parser = DEFAULT_ARGUMENT_PARSER(description="Called when Ctrl-D is pressed", epilog=INTERNAL_COMMAND_EPILOG) + eof_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Called when Ctrl-D is pressed", + epilog=INTERNAL_COMMAND_EPILOG) @with_argparser(eof_parser) def do_eof(self, _: argparse.Namespace) -> Optional[bool]: @@ -3693,7 +3703,7 @@ class Cmd(cmd.Cmd): # noinspection PyTypeChecker return self.do_quit('') - quit_parser = DEFAULT_ARGUMENT_PARSER(description="Exit this application") + quit_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Exit this application") @with_argparser(quit_parser) def do_quit(self, _: argparse.Namespace) -> Optional[bool]: @@ -3761,7 +3771,7 @@ class Cmd(cmd.Cmd): raise CompletionError(param + " is not a settable parameter") # Create a parser with a value field based on this settable - settable_parser = DEFAULT_ARGUMENT_PARSER(parents=[Cmd.set_parser_parent]) + settable_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(parents=[Cmd.set_parser_parent]) # Settables with choices list the values of those choices instead of the arg name # in help text and this shows in tab completion hints. Set metavar to avoid this. @@ -3792,7 +3802,7 @@ class Cmd(cmd.Cmd): "Call without arguments for a list of all settable parameters with their values.\n" "Call with just param to view that parameter's value." ) - set_parser_parent = DEFAULT_ARGUMENT_PARSER(description=set_description, add_help=False) + set_parser_parent = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=set_description, add_help=False) set_parser_parent.add_argument( '-v', '--verbose', action='store_true', help='include description of parameters when viewing' ) @@ -3805,7 +3815,7 @@ class Cmd(cmd.Cmd): ) # Create the parser for the set command - set_parser = DEFAULT_ARGUMENT_PARSER(parents=[set_parser_parent]) + set_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(parents=[set_parser_parent]) set_parser.add_argument( 'value', nargs=argparse.OPTIONAL, help='new value for settable', completer=complete_set_value, suppress_tab_hint=True ) @@ -3859,7 +3869,7 @@ class Cmd(cmd.Cmd): else: self.poutput(result_str) - shell_parser = DEFAULT_ARGUMENT_PARSER(description="Execute a command as if at the OS prompt") + shell_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Execute a command as if at the OS prompt") shell_parser.add_argument('command', help='the command to run', completer=shell_cmd_complete) shell_parser.add_argument( 'command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer=path_complete @@ -4167,7 +4177,7 @@ class Cmd(cmd.Cmd): return py_bridge.stop - py_parser = DEFAULT_ARGUMENT_PARSER(description="Run an interactive Python shell") + py_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Run an interactive Python shell") @with_argparser(py_parser) def do_py(self, _: argparse.Namespace) -> Optional[bool]: @@ -4177,7 +4187,7 @@ class Cmd(cmd.Cmd): """ return self._run_python() - run_pyscript_parser = DEFAULT_ARGUMENT_PARSER(description="Run a Python script file inside the console") + run_pyscript_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Run a Python script file inside the console") run_pyscript_parser.add_argument('script_path', help='path to the script file', completer=path_complete) run_pyscript_parser.add_argument( 'script_arguments', nargs=argparse.REMAINDER, help='arguments to pass to script', completer=path_complete @@ -4214,7 +4224,7 @@ class Cmd(cmd.Cmd): return py_return - ipython_parser = DEFAULT_ARGUMENT_PARSER(description="Run an interactive IPython shell") + ipython_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Run an interactive IPython shell") # noinspection PyPackageRequirements @with_argparser(ipython_parser) @@ -4284,7 +4294,7 @@ class Cmd(cmd.Cmd): history_description = "View, run, edit, save, or clear previously entered commands" - history_parser = DEFAULT_ARGUMENT_PARSER(description=history_description) + history_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=history_description) history_action_group = history_parser.add_mutually_exclusive_group() history_action_group.add_argument('-r', '--run', action='store_true', help='run selected history items') history_action_group.add_argument('-e', '--edit', action='store_true', help='edit and then run selected history items') @@ -4621,7 +4631,7 @@ class Cmd(cmd.Cmd): " set editor (program-name)" ) - edit_parser = DEFAULT_ARGUMENT_PARSER(description=edit_description) + edit_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=edit_description) edit_parser.add_argument( 'file_path', nargs=argparse.OPTIONAL, help="optional path to a file to open in editor", completer=path_complete ) @@ -4666,7 +4676,7 @@ class Cmd(cmd.Cmd): "the output of the script commands to a transcript for testing purposes.\n" ) - run_script_parser = DEFAULT_ARGUMENT_PARSER(description=run_script_description) + run_script_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=run_script_description) run_script_parser.add_argument( '-t', '--transcript', @@ -4735,7 +4745,7 @@ class Cmd(cmd.Cmd): relative_run_script_epilog = "Notes:\n" " This command is intended to only be used within text file scripts." - relative_run_script_parser = DEFAULT_ARGUMENT_PARSER( + relative_run_script_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER( description=relative_run_script_description, epilog=relative_run_script_epilog ) relative_run_script_parser.add_argument('file_path', help='a file path pointing to a script') diff --git a/cmd2/constants.py b/cmd2/constants.py index 9f29be86..7656ae58 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -43,6 +43,7 @@ CLASS_ATTR_DEFAULT_HELP_CATEGORY = 'cmd2_default_help_category' # The argparse parser for the command CMD_ATTR_ARGPARSER = 'argparser' +CMD_ATTR_COMPLETER = 'command_completer' # Whether or not tokens are unquoted before sending to argparse CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes' diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 1ff0bdbe..644a8add 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -10,12 +10,16 @@ from typing import ( Optional, Sequence, Tuple, + Type, Union, ) from . import ( constants, ) +from .argparse_completer import ( + ArgparseCompleter, +) from .argparse_custom import ( Cmd2AttributeWrapper, ) @@ -271,6 +275,7 @@ def with_argparser( ns_provider: Optional[Callable[..., argparse.Namespace]] = None, preserve_quotes: bool = False, with_unknown_args: bool = False, + completer: Optional[Type[ArgparseCompleter]] = None, ) -> Callable[[ArgparseCommandFunc], RawCommandFuncOptionalBoolReturn]: """A decorator to alter a cmd2 method to populate its ``args`` argument by parsing arguments with the given instance of argparse.ArgumentParser. @@ -281,6 +286,7 @@ def with_argparser( state data that affects parsing. :param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes :param with_unknown_args: if true, then capture unknown args + :param completer: CommandCompleter type. Defaults to ArgparseCompleter if unspecified. :return: function that gets passed argparse-parsed args in a ``Namespace`` A :class:`cmd2.argparse_custom.Cmd2AttributeWrapper` called ``cmd2_statement`` is included in the ``Namespace`` to provide access to the :class:`cmd2.Statement` object that was created when @@ -391,6 +397,7 @@ def with_argparser( # Set some custom attributes for this command setattr(cmd_wrapper, constants.CMD_ATTR_ARGPARSER, parser) + setattr(cmd_wrapper, constants.CMD_ATTR_COMPLETER, completer) setattr(cmd_wrapper, constants.CMD_ATTR_PRESERVE_QUOTES, preserve_quotes) return cmd_wrapper |
