From 457ee75d83e35c5dae397b8c001b74600e286eac Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 30 Sep 2020 20:57:02 -0400 Subject: [PATCH] Fixed issue where instantiating more than one cmd2-based class which uses the @as_subcommand_to decorator resulted in duplicated help text in the base command the subcommands belong to. --- CHANGELOG.md | 2 ++ cmd2/argparse_custom.py | 24 +++++++++++++----------- cmd2/cmd2.py | 8 ++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ada12f0..1b41df315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Bug Fixes * Fixed issue where quoted redirectors and terminators in aliases and macros were not being restored when read from a startup script. + * Fixed issue where instantiating more than one cmd2-based class which uses the `@as_subcommand_to` + decorator resulted in duplicated help text in the base command the subcommands belong to. ## 1.3.10 (September 17, 2020) * Enhancements diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 45abe6b2d..d773f851a 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -550,6 +550,7 @@ def _match_argument_wrapper(self, action, arg_strings_pattern) -> int: # Patch argparse._SubParsersAction to add remove_parser function ############################################################################################################ +# noinspection PyPep8Naming def _SubParsersAction_remove_parser(self, name: str): """ Removes a sub-parser from a sub-parsers group @@ -558,23 +559,23 @@ def _SubParsersAction_remove_parser(self, name: str): class so cmd2 can remove subcommands from a parser. :param self: instance of the _SubParsersAction being edited - :param name: name of the sub-parser to remove + :param name: name of the subcommand for the sub-parser to remove """ + # Remove this subcommand from its base command's help text for choice_action in self._choices_actions: if choice_action.dest == name: self._choices_actions.remove(choice_action) break - subparser = self._name_parser_map[name] - to_remove = [] - for name, parser in self._name_parser_map.items(): - if parser is subparser: - to_remove.append(name) - for name in to_remove: - del self._name_parser_map[name] - - if name in self.choices: - del self.choices[name] + # Remove this subcommand and all its aliases from the base command + subparser = self._name_parser_map.get(name) + if subparser is not None: + to_remove = [] + for cur_name, cur_parser in self._name_parser_map.items(): + if cur_parser is subparser: + to_remove.append(cur_name) + for cur_name in to_remove: + del self._name_parser_map[cur_name] # noinspection PyProtectedMember @@ -733,6 +734,7 @@ def _format_action_invocation(self, action) -> str: return ', '.join(action.option_strings) + ' ' + args_string # End cmd2 customization + # noinspection PyMethodMayBeStatic def _determine_metavar(self, action, default_metavar) -> Union[str, Tuple]: """Custom method to determine what to use as the metavar value of an action""" if action.metavar is not None: diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 8810025a4..ea4e01de2 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -692,6 +692,14 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> for action in target_parser._actions: if isinstance(action, argparse._SubParsersAction): + # Temporary workaround for avoiding subcommand help text repeatedly getting added to + # action._choices_actions. Until we have instance-specific parser objects, we will remove + # any existing subcommand which has the same name before replacing it. This problem is + # exercised when more than one cmd2.Cmd-based object is created and the same subcommands + # get added each time. Argparse overwrites the previous subcommand but keeps growing the help + # text which is shown by running something like 'alias -h'. + action.remove_parser(subcommand_name) + # Get the kwargs for add_parser() add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {})