Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 65 additions & 22 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,42 +222,43 @@ def get_choices(self) -> Choices:
more details on these arguments.

``argparse.ArgumentParser._get_nargs_pattern`` - adds support for nargs ranges.
See _get_nargs_pattern_wrapper for more details.
See ``_get_nargs_pattern_wrapper`` for more details.

``argparse.ArgumentParser._match_argument`` - adds support for nargs ranges.
See _match_argument_wrapper for more details.

``argparse._SubParsersAction.remove_parser`` - new function which removes a
sub-parser from a sub-parsers group. See _SubParsersAction_remove_parser for
more details.
See ``_match_argument_wrapper`` for more details.

**Added accessor methods**

cmd2 has patched ``argparse.Action`` to include the following accessor methods
for cases in which you need to manually access the cmd2-specific attributes.

- ``argparse.Action.get_choices_callable()`` - See `action_get_choices_callable` for more details.
- ``argparse.Action.set_choices_provider()`` - See `_action_set_choices_provider` for more details.
- ``argparse.Action.set_completer()`` - See `_action_set_completer` for more details.
- ``argparse.Action.get_table_columns()`` - See `_action_get_table_columns` for more details.
- ``argparse.Action.set_table_columns()`` - See `_action_set_table_columns` for more details.
- ``argparse.Action.get_nargs_range()`` - See `_action_get_nargs_range` for more details.
- ``argparse.Action.set_nargs_range()`` - See `_action_set_nargs_range` for more details.
- ``argparse.Action.get_suppress_tab_hint()`` - See `_action_get_suppress_tab_hint` for more details.
- ``argparse.Action.set_suppress_tab_hint()`` - See `_action_set_suppress_tab_hint` for more details.
- ``argparse.Action.get_choices_callable()`` - See ``action_get_choices_callable`` for more details.
- ``argparse.Action.set_choices_provider()`` - See ``_action_set_choices_provider`` for more details.
- ``argparse.Action.set_completer()`` - See ``_action_set_completer`` for more details.
- ``argparse.Action.get_table_columns()`` - See ``_action_get_table_columns`` for more details.
- ``argparse.Action.set_table_columns()`` - See ``_action_set_table_columns`` for more details.
- ``argparse.Action.get_nargs_range()`` - See ``_action_get_nargs_range`` for more details.
- ``argparse.Action.set_nargs_range()`` - See ``_action_set_nargs_range`` for more details.
- ``argparse.Action.get_suppress_tab_hint()`` - See ``_action_get_suppress_tab_hint`` for more details.
- ``argparse.Action.set_suppress_tab_hint()`` - See ``_action_set_suppress_tab_hint`` for more details.

cmd2 has patched ``argparse.ArgumentParser`` to include the following accessor methods

- ``argparse.ArgumentParser.get_ap_completer_type()`` - See `_ArgumentParser_get_ap_completer_type` for more details.
- ``argparse.Action.set_ap_completer_type()`` - See `_ArgumentParser_set_ap_completer_type` for more details.
- ``argparse.ArgumentParser.get_ap_completer_type()`` - See ``_ArgumentParser_get_ap_completer_type`` for more details.
- ``argparse.Action.set_ap_completer_type()`` - See ``_ArgumentParser_set_ap_completer_type`` for more details.

**Subcommand removal**
**Subcommand Manipulation**

cmd2 has patched ``argparse._SubParsersAction`` to include a ``remove_parser()``
method which can be used to remove a subcommand.
cmd2 has patched ``argparse._SubParsersAction`` with new functions to better facilitate the
addition and removal of subcommand parsers.

``argparse._SubParsersAction.remove_parser`` - new function which removes a
sub-parser from a sub-parsers group. See _SubParsersAction_remove_parser` for more details.
sub-parser from a sub-parsers group. See ``_SubParsersAction_remove_parser`` for
more details.

``argparse._SubParsersAction.add_existing_parser`` - new function which allows you to attach
an existing ArgumentParser to a sub-parsers group. See ``_SubParsersAction_add_existing_parser``
for more details.
"""

import argparse
Expand Down Expand Up @@ -948,7 +949,10 @@ def _ArgumentParser_check_value(_self: argparse.ArgumentParser, action: argparse
############################################################################################################


def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore[type-arg] # noqa: N802
def _SubParsersAction_remove_parser( # noqa: N802
self: argparse._SubParsersAction, # type: ignore[type-arg]
name: str,
) -> None:
"""Remove a sub-parser from a sub-parsers group. Used to remove subcommands from a parser.

This function is added by cmd2 as a method called ``remove_parser()`` to ``argparse._SubParsersAction`` class.
Expand Down Expand Up @@ -977,6 +981,45 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str)

setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_parser)

############################################################################################################
# Patch argparse._SubParsersAction to add add_existing_parser function
############################################################################################################


def _SubParsersAction_add_existing_parser( # noqa: N802
self: argparse._SubParsersAction, # type: ignore[type-arg]
name: str,
subcmd_parser: argparse.ArgumentParser,
**add_parser_kwargs: Any,
) -> None:
"""Attach an existing ArgumentParser to a sub-parsers group.

This is useful when a parser is pre-configured (e.g. by cmd2's subcommand decorator)
and needs to be attached to a parent parser.

This function is added by cmd2 as a method called ``add_existing_parser()``
to ``argparse._SubParsersAction`` class.

To call: ``action.add_existing_parser(name, subcmd_parser, **add_parser_kwargs)``

:param self: instance of the _SubParsersAction being edited
:param name: name of the subcommand to add
:param subcmd_parser: the parser for this new subcommand
:param add_parser_kwargs: registration-specific kwargs for add_parser()
(e.g. help, aliases, deprecated [Python 3.13+])
"""
# Use add_parser to register the subcommand name and any aliases
self.add_parser(name, **add_parser_kwargs)

# Replace the parser created by add_parser() with our pre-configured one
self._name_parser_map[name] = subcmd_parser

# Remap any aliases to our pre-configured parser
for alias in add_parser_kwargs.get("aliases", ()):
self._name_parser_map[alias] = subcmd_parser


setattr(argparse._SubParsersAction, 'add_existing_parser', _SubParsersAction_add_existing_parser)

############################################################################################################
# Unless otherwise noted, everything below this point are copied from Python's
Expand Down
18 changes: 7 additions & 11 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,19 +1129,15 @@ def find_subcommand(
# Find the argparse action that handles subcommands
for action in target_parser._actions:
if isinstance(action, argparse._SubParsersAction):
# Get the kwargs for add_parser()
# Get add_parser() kwargs (aliases, help, etc.) defined by the decorator
add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {})

# Use add_parser to register the subcommand name and any aliases
action.add_parser(subcommand_name, **add_parser_kwargs)

# Replace the parser created by add_parser() with our pre-configured one
action._name_parser_map[subcommand_name] = subcmd_parser

# Also remap any aliases to our pre-configured parser
for alias in add_parser_kwargs.get("aliases", []):
action._name_parser_map[alias] = subcmd_parser

# Add the existing parser as a subcommand
action.add_existing_parser( # type: ignore[attr-defined]
subcommand_name,
subcmd_parser,
**add_parser_kwargs,
)
break

def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
Expand Down
11 changes: 7 additions & 4 deletions cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ def as_subcommand_to(
*,
help: str | None = None, # noqa: A002
aliases: Sequence[str] | None = None,
**add_parser_kwargs: Any,
) -> Callable[[ArgparseCommandFunc[CmdOrSet]], ArgparseCommandFunc[CmdOrSet]]:
"""Tag this method as a subcommand to an existing argparse decorated command.

Expand All @@ -363,6 +364,8 @@ def as_subcommand_to(
This is passed as the help argument to subparsers.add_parser().
:param aliases: Alternative names for this subcommand. This is passed as the alias argument to
subparsers.add_parser().
:param add_parser_kwargs: other registration-specific kwargs for add_parser()
(e.g. deprecated [Python 3.13+])
:return: Wrapper function that can receive an argparse.Namespace
"""

Expand All @@ -373,13 +376,13 @@ def arg_decorator(func: ArgparseCommandFunc[CmdOrSet]) -> ArgparseCommandFunc[Cm
setattr(func, constants.SUBCMD_ATTR_NAME, subcommand)

# Keyword arguments for subparsers.add_parser()
add_parser_kwargs: dict[str, Any] = {}
final_kwargs: dict[str, Any] = dict(add_parser_kwargs)
if help is not None:
add_parser_kwargs['help'] = help
final_kwargs['help'] = help
if aliases:
add_parser_kwargs['aliases'] = aliases[:]
final_kwargs['aliases'] = tuple(aliases)

setattr(func, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, add_parser_kwargs)
setattr(func, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, final_kwargs)

return func

Expand Down
Loading