Skip to content

Commit 6661b8c

Browse files
committed
Refactor: Use a single parser instance for subcommands
This commit refactors how subcommand parsers are created. Instead of manually copying attributes from a pre-configured parser to a new one created by argparse, the code now directly attaches a single, fully configured parser instance to the subcommand action's internal map. This eliminates fragile attribute-by-attribute copying and ensures all custom parser settings are retained.
1 parent c2dc175 commit 6661b8c

File tree

1 file changed

+19
-31
lines changed

1 file changed

+19
-31
lines changed

cmd2/cmd2.py

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -987,46 +987,34 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) ->
987987

988988
target_parser = find_subcommand(command_parser, subcommand_names)
989989

990+
# Create the subcommand parser and configure it
990991
subcmd_parser = self._build_parser(cmdset, subcmd_parser_builder, f'{command_name} {subcommand_name}')
991992
if subcmd_parser.description is None and method.__doc__:
992993
subcmd_parser.description = strip_doc_annotations(method.__doc__)
993994

995+
# Set the subcommand handler
996+
defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method}
997+
subcmd_parser.set_defaults(**defaults)
998+
999+
# Set what instance the handler is bound to
1000+
setattr(subcmd_parser, constants.PARSER_ATTR_COMMANDSET, cmdset)
1001+
1002+
# Find the argparse action that handles subcommands
9941003
for action in target_parser._actions:
9951004
if isinstance(action, argparse._SubParsersAction):
9961005
# Get the kwargs for add_parser()
9971006
add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {})
9981007

999-
# Set subcmd_parser as the parent to the parser we're creating to get its arguments
1000-
add_parser_kwargs['parents'] = [subcmd_parser]
1001-
1002-
# argparse only copies actions from a parent and not the following settings.
1003-
# To retain these settings, we will copy them from subcmd_parser and pass them
1004-
# as ArgumentParser constructor arguments to add_parser().
1005-
add_parser_kwargs['prog'] = subcmd_parser.prog
1006-
add_parser_kwargs['usage'] = subcmd_parser.usage
1007-
add_parser_kwargs['description'] = subcmd_parser.description
1008-
add_parser_kwargs['epilog'] = subcmd_parser.epilog
1009-
add_parser_kwargs['formatter_class'] = subcmd_parser.formatter_class
1010-
add_parser_kwargs['prefix_chars'] = subcmd_parser.prefix_chars
1011-
add_parser_kwargs['fromfile_prefix_chars'] = subcmd_parser.fromfile_prefix_chars
1012-
add_parser_kwargs['argument_default'] = subcmd_parser.argument_default
1013-
add_parser_kwargs['conflict_handler'] = subcmd_parser.conflict_handler
1014-
add_parser_kwargs['allow_abbrev'] = subcmd_parser.allow_abbrev
1015-
1016-
# Set add_help to False and use whatever help option subcmd_parser already has
1017-
add_parser_kwargs['add_help'] = False
1018-
1019-
attached_parser = action.add_parser(subcommand_name, **add_parser_kwargs)
1020-
1021-
# Set the subcommand handler
1022-
defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method}
1023-
attached_parser.set_defaults(**defaults)
1024-
1025-
# Copy value for custom ArgparseCompleter type, which will be None if not present on subcmd_parser
1026-
attached_parser.set_ap_completer_type(subcmd_parser.get_ap_completer_type()) # type: ignore[attr-defined]
1027-
1028-
# Set what instance the handler is bound to
1029-
setattr(attached_parser, constants.PARSER_ATTR_COMMANDSET, cmdset)
1008+
# Use add_parser to register the subcommand name and any aliases
1009+
action.add_parser(subcommand_name, **add_parser_kwargs)
1010+
1011+
# Replace the parser created by add_parser() with our pre-configured one
1012+
action._name_parser_map[subcommand_name] = subcmd_parser
1013+
1014+
# Also remap any aliases to our pre-configured parser
1015+
for alias in add_parser_kwargs.get("aliases", []):
1016+
action._name_parser_map[alias] = subcmd_parser
1017+
10301018
break
10311019

10321020
def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:

0 commit comments

Comments
 (0)