From 6661b8ce4300121f728afe7790a22df2ef711d99 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 29 Aug 2025 17:39:33 -0400 Subject: [PATCH 1/2] 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. --- cmd2/cmd2.py | 50 +++++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index fd04f89e5..7ccc64b35 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -987,46 +987,34 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> target_parser = find_subcommand(command_parser, subcommand_names) + # Create the subcommand parser and configure it subcmd_parser = self._build_parser(cmdset, subcmd_parser_builder, f'{command_name} {subcommand_name}') if subcmd_parser.description is None and method.__doc__: subcmd_parser.description = strip_doc_annotations(method.__doc__) + # Set the subcommand handler + defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method} + subcmd_parser.set_defaults(**defaults) + + # Set what instance the handler is bound to + setattr(subcmd_parser, constants.PARSER_ATTR_COMMANDSET, cmdset) + + # Find the argparse action that handles subcommands for action in target_parser._actions: if isinstance(action, argparse._SubParsersAction): # Get the kwargs for add_parser() add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {}) - # Set subcmd_parser as the parent to the parser we're creating to get its arguments - add_parser_kwargs['parents'] = [subcmd_parser] - - # argparse only copies actions from a parent and not the following settings. - # To retain these settings, we will copy them from subcmd_parser and pass them - # as ArgumentParser constructor arguments to add_parser(). - add_parser_kwargs['prog'] = subcmd_parser.prog - add_parser_kwargs['usage'] = subcmd_parser.usage - add_parser_kwargs['description'] = subcmd_parser.description - add_parser_kwargs['epilog'] = subcmd_parser.epilog - add_parser_kwargs['formatter_class'] = subcmd_parser.formatter_class - add_parser_kwargs['prefix_chars'] = subcmd_parser.prefix_chars - add_parser_kwargs['fromfile_prefix_chars'] = subcmd_parser.fromfile_prefix_chars - add_parser_kwargs['argument_default'] = subcmd_parser.argument_default - add_parser_kwargs['conflict_handler'] = subcmd_parser.conflict_handler - add_parser_kwargs['allow_abbrev'] = subcmd_parser.allow_abbrev - - # Set add_help to False and use whatever help option subcmd_parser already has - add_parser_kwargs['add_help'] = False - - attached_parser = action.add_parser(subcommand_name, **add_parser_kwargs) - - # Set the subcommand handler - defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method} - attached_parser.set_defaults(**defaults) - - # Copy value for custom ArgparseCompleter type, which will be None if not present on subcmd_parser - attached_parser.set_ap_completer_type(subcmd_parser.get_ap_completer_type()) # type: ignore[attr-defined] - - # Set what instance the handler is bound to - setattr(attached_parser, constants.PARSER_ATTR_COMMANDSET, cmdset) + # 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 + break def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: From efa05068d6033ebcd9337840c7eb05d7a8ee6ba9 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 29 Aug 2025 17:40:54 -0400 Subject: [PATCH 2/2] Improved docstring for TextGroup.__rich__(). --- cmd2/argparse_custom.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index bdf94da46..f7ba4c4ca 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1375,7 +1375,11 @@ def __init__( self.formatter_creator = formatter_creator def __rich__(self) -> Group: - """Perform custom rendering.""" + """Return a renderable Rich Group object for the class instance. + + This method formats the title and indents the text to match argparse + group styling, making the object displayable by a Rich console. + """ formatter = self.formatter_creator() styled_title = Text(