Skip to content

Commit b8ce4fe

Browse files
committed
Renamed add_existing_parser() to attach_parser().
Renamed remove_parser() to detach_parser().
1 parent f9157fd commit b8ce4fe

3 files changed

Lines changed: 98 additions & 52 deletions

File tree

cmd2/argparse_custom.py

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,13 @@ def get_choices(self) -> Choices:
252252
cmd2 has patched ``argparse._SubParsersAction`` with new functions to better facilitate the
253253
addition and removal of subcommand parsers.
254254
255-
``argparse._SubParsersAction.remove_parser`` - new function which removes a
256-
sub-parser from a sub-parsers group. See ``_SubParsersAction_remove_parser`` for
257-
more details.
258-
259-
``argparse._SubParsersAction.add_existing_parser`` - new function which allows you to attach
260-
an existing ArgumentParser to a sub-parsers group. See ``_SubParsersAction_add_existing_parser``
255+
``argparse._SubParsersAction.attach_parser`` - new function to attach
256+
an existing ArgumentParser to a subparsers action. See ``_SubParsersAction_attach_parser``
261257
for more details.
258+
259+
``argparse._SubParsersAction.detach_parser`` - new function to detach a
260+
parser from a subparsers action. See ``_SubParsersAction_detach_parser`` for
261+
more details.
262262
"""
263263

264264
import argparse
@@ -945,62 +945,25 @@ def _ArgumentParser_check_value(_self: argparse.ArgumentParser, action: argparse
945945

946946

947947
############################################################################################################
948-
# Patch argparse._SubParsersAction to add remove_parser function
949-
############################################################################################################
950-
951-
952-
def _SubParsersAction_remove_parser( # noqa: N802
953-
self: argparse._SubParsersAction, # type: ignore[type-arg]
954-
name: str,
955-
) -> None:
956-
"""Remove a sub-parser from a sub-parsers group. Used to remove subcommands from a parser.
957-
958-
This function is added by cmd2 as a method called ``remove_parser()`` to ``argparse._SubParsersAction`` class.
959-
960-
To call: ``action.remove_parser(name)``
961-
962-
:param self: instance of the _SubParsersAction being edited
963-
:param name: name of the subcommand for the sub-parser to remove
964-
"""
965-
# Remove this subcommand from its base command's help text
966-
for choice_action in self._choices_actions:
967-
if choice_action.dest == name:
968-
self._choices_actions.remove(choice_action)
969-
break
970-
971-
# Remove this subcommand and all its aliases from the base command
972-
subparser = self._name_parser_map.get(name)
973-
if subparser is not None:
974-
to_remove = []
975-
for cur_name, cur_parser in self._name_parser_map.items():
976-
if cur_parser is subparser:
977-
to_remove.append(cur_name)
978-
for cur_name in to_remove:
979-
del self._name_parser_map[cur_name]
980-
981-
982-
setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_parser)
983-
984-
############################################################################################################
985-
# Patch argparse._SubParsersAction to add add_existing_parser function
948+
# Patch argparse._SubParsersAction to add attach_parser function
986949
############################################################################################################
987950

988951

989-
def _SubParsersAction_add_existing_parser( # noqa: N802
952+
def _SubParsersAction_attach_parser( # noqa: N802
990953
self: argparse._SubParsersAction, # type: ignore[type-arg]
991954
name: str,
992955
subcmd_parser: argparse.ArgumentParser,
993956
**add_parser_kwargs: Any,
994957
) -> None:
995-
"""Attach an existing ArgumentParser to a sub-parsers group.
958+
"""Attach an existing ArgumentParser to a subparsers action.
996959
997960
This is useful when a parser is pre-configured (e.g. by cmd2's subcommand decorator)
998961
and needs to be attached to a parent parser.
999962
1000-
This function is added by cmd2 as a method called ``add_existing_parser()``
963+
This function is added by cmd2 as a method called ``attach_parser()``
1001964
to ``argparse._SubParsersAction`` class.
1002965
1003-
To call: ``action.add_existing_parser(name, subcmd_parser, **add_parser_kwargs)``
966+
To call: ``action.attach_parser(name, subcmd_parser, **add_parser_kwargs)``
1004967
1005968
:param self: instance of the _SubParsersAction being edited
1006969
:param name: name of the subcommand to add
@@ -1019,7 +982,48 @@ def _SubParsersAction_add_existing_parser( # noqa: N802
1019982
self._name_parser_map[alias] = subcmd_parser
1020983

1021984

1022-
setattr(argparse._SubParsersAction, 'add_existing_parser', _SubParsersAction_add_existing_parser)
985+
setattr(argparse._SubParsersAction, 'attach_parser', _SubParsersAction_attach_parser)
986+
987+
############################################################################################################
988+
# Patch argparse._SubParsersAction to add detach_parser function
989+
############################################################################################################
990+
991+
992+
def _SubParsersAction_detach_parser( # noqa: N802
993+
self: argparse._SubParsersAction, # type: ignore[type-arg]
994+
name: str,
995+
) -> argparse.ArgumentParser | None:
996+
"""Detach a parser from a subparsers action and return it.
997+
998+
This function is added by cmd2 as a method called ``detach_parser()`` to ``argparse._SubParsersAction`` class.
999+
1000+
To call: ``action.detach_parser(name)``
1001+
1002+
:param self: instance of the _SubParsersAction being edited
1003+
:param name: name of the subcommand for the parser to detach
1004+
:return: the parser which was detached or None if the subcommand doesn't exist
1005+
"""
1006+
subparser = self._name_parser_map.get(name)
1007+
1008+
if subparser is not None:
1009+
# Remove this subcommand and all its aliases from the base command
1010+
to_remove = []
1011+
for cur_name, cur_parser in self._name_parser_map.items():
1012+
if cur_parser is subparser:
1013+
to_remove.append(cur_name)
1014+
for cur_name in to_remove:
1015+
del self._name_parser_map[cur_name]
1016+
1017+
# Remove this subcommand from its base command's help text
1018+
for choice_action in self._choices_actions:
1019+
if choice_action.dest == name:
1020+
self._choices_actions.remove(choice_action)
1021+
break
1022+
1023+
return subparser
1024+
1025+
1026+
setattr(argparse._SubParsersAction, 'detach_parser', _SubParsersAction_detach_parser)
10231027

10241028
############################################################################################################
10251029
# Unless otherwise noted, everything below this point are copied from Python's

cmd2/cmd2.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,8 +1132,8 @@ def find_subcommand(
11321132
# Get add_parser() kwargs (aliases, help, etc.) defined by the decorator
11331133
add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {})
11341134

1135-
# Add the existing parser as a subcommand
1136-
action.add_existing_parser( # type: ignore[attr-defined]
1135+
# Attach existing parser as a subcommand
1136+
action.attach_parser( # type: ignore[attr-defined]
11371137
subcommand_name,
11381138
subcmd_parser,
11391139
**add_parser_kwargs,
@@ -1184,7 +1184,7 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
11841184

11851185
for action in command_parser._actions:
11861186
if isinstance(action, argparse._SubParsersAction):
1187-
action.remove_parser(subcommand_name) # type: ignore[attr-defined]
1187+
action.detach_parser(subcommand_name) # type: ignore[attr-defined]
11881188
break
11891189

11901190
@property

tests/test_argparse_custom.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,48 @@ def test_cmd2_attribute_wrapper() -> None:
308308
assert wrapper.get() == new_val
309309

310310

311+
def test_parser_attachment() -> None:
312+
# Attach a parser as a subcommand
313+
root_parser = Cmd2ArgumentParser(description="root command")
314+
root_subparsers = root_parser.add_subparsers()
315+
316+
child_parser = Cmd2ArgumentParser(description="child command")
317+
root_subparsers.attach_parser( # type: ignore[attr-defined]
318+
"child",
319+
child_parser,
320+
help="a child command",
321+
aliases=["child_alias"],
322+
)
323+
324+
# Verify the same parser instance was used
325+
assert root_subparsers._name_parser_map["child"] is child_parser
326+
assert root_subparsers._name_parser_map["child_alias"] is child_parser
327+
328+
# Verify an action with the help text exists
329+
child_action = None
330+
for action in root_subparsers._choices_actions:
331+
if action.dest == "child":
332+
child_action = action
333+
break
334+
assert child_action is not None
335+
assert child_action.help == "a child command"
336+
337+
# Detatch the subcommand
338+
detached_parser = root_subparsers.detach_parser("child") # type: ignore[attr-defined]
339+
340+
# Verify subcommand and its aliases were removed
341+
assert detached_parser is child_parser
342+
assert "child" not in root_subparsers._name_parser_map
343+
assert "child_alias" not in root_subparsers._name_parser_map
344+
345+
# Verify the help text action was removed
346+
choices_actions = [action.dest for action in root_subparsers._choices_actions]
347+
assert "child" not in choices_actions
348+
349+
# Verify it returns None when subcommand does not exist
350+
assert root_subparsers.detach_parser("fake") is None # type: ignore[attr-defined]
351+
352+
311353
def test_completion_items_as_choices(capsys) -> None:
312354
"""Test cmd2's patch to Argparse._check_value() which supports CompletionItems as choices.
313355
Choices are compared to CompletionItems.orig_value instead of the CompletionItem instance.

0 commit comments

Comments
 (0)