Skip to content

Commit

Permalink
Merge pull request #1293 from rdhammond15/fix/subcmd-usage
Browse files Browse the repository at this point in the history
Add required args to subcommand program
  • Loading branch information
kmvanbrunt authored Sep 13, 2024
2 parents a9fd1bf + ecbe084 commit 16c2dbe
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 33 deletions.
8 changes: 2 additions & 6 deletions cmd2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 11 additions & 19 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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),
)
Expand Down Expand Up @@ -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),
)
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
7 changes: 0 additions & 7 deletions docs/features/argument_processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 16c2dbe

Please sign in to comment.