Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add required args to subcommand program #1293

Merged
merged 7 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading