diff --git a/cmd2/__init__.py b/cmd2/__init__.py index 9b3fdbc3..8f1f030e 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -5,12 +5,8 @@ import sys -# For python 3.8 and later -if sys.version_info >= (3, 8): - import importlib.metadata as importlib_metadata -else: - # For everyone else - import importlib_metadata +import importlib.metadata as importlib_metadata + try: __version__ = importlib_metadata.version(__name__) except importlib_metadata.PackageNotFoundError: # pragma: no cover diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index a7f5b64d..5f95b2ab 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -700,10 +700,7 @@ def _build_parser( elif callable(parser_builder): parser = parser_builder() elif isinstance(parser_builder, argparse.ArgumentParser): - if sys.version_info >= (3, 6, 4): - parser = copy.deepcopy(parser_builder) - else: # pragma: no cover - parser = parser_builder + parser = copy.deepcopy(parser_builder) return parser def _register_command_parser(self, command: str, command_method: Callable[..., Any]) -> None: @@ -778,9 +775,9 @@ def unregister_command_set(self, cmdset: CommandSet) -> None: cmdset.on_unregister() self._unregister_subcommands(cmdset) - methods = inspect.getmembers( + methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers( cmdset, - predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type, var-annotated] + predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX), ) @@ -809,9 +806,9 @@ def unregister_command_set(self, cmdset: CommandSet) -> None: self._installed_command_sets.remove(cmdset) def _check_uninstallable(self, cmdset: CommandSet) -> None: - methods = inspect.getmembers( + methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers( cmdset, - predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type, var-annotated] + predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX), ) @@ -3328,19 +3325,14 @@ def _cmdloop(self) -> None: ############################################################# # Top-level parser for alias - @staticmethod - def _build_alias_parser() -> argparse.ArgumentParser: - alias_description = ( - "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string." - ) - alias_epilog = "See also:\n" " macro" - alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog) - alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') - alias_subparsers.required = True - return alias_parser + alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string." + alias_epilog = "See also:\n" " macro" + alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog) + alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') + alias_subparsers.required = True # Preserve quotes since we are passing strings to other commands - @with_argparser(_build_alias_parser, preserve_quotes=True) + @with_argparser(alias_parser, preserve_quotes=True) def do_alias(self, args: argparse.Namespace) -> None: """Manage aliases""" # Call handler for whatever subcommand was selected diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 361b71ac..3aca8f0a 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -209,6 +209,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: """ # Set the prog value for this parser parser.prog = prog + req_args: List[str] = [] # Set the prog value for the parser's subcommands for action in parser._actions: @@ -233,13 +234,20 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: if subcmd_parser in processed_parsers: continue - subcmd_prog = parser.prog + ' ' + subcmd_name + subcmd_prog = parser.prog + if req_args: + subcmd_prog += " " + " ".join(req_args) + subcmd_prog += " " + subcmd_name _set_parser_prog(subcmd_parser, subcmd_prog) processed_parsers.append(subcmd_parser) # We can break since argparse only allows 1 group of subcommands per level break + # Need to save required args so they can be prepended to the subcommand usage + elif action.required: + req_args.append(action.dest) + #: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input #: and optionally returns a boolean diff --git a/docs/features/argument_processing.rst b/docs/features/argument_processing.rst index 16031200..d6090465 100644 --- a/docs/features/argument_processing.rst +++ b/docs/features/argument_processing.rst @@ -75,13 +75,6 @@ Here's what it looks like:: for i in range(min(repetitions, self.maxrepeats)): self.poutput(arg) -.. warning:: - - It is important that each command which uses the ``@with_argparser`` - decorator be passed a unique instance of a parser since command-specific - changes could be made to it. - - .. note:: The ``@with_argparser`` decorator sets the ``prog`` variable in the argument