Skip to content

Commit 16c2dbe

Browse files
authored
Merge pull request #1293 from rdhammond15/fix/subcmd-usage
Add required args to subcommand program
2 parents a9fd1bf + ecbe084 commit 16c2dbe

File tree

4 files changed

+22
-33
lines changed

4 files changed

+22
-33
lines changed

cmd2/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,8 @@
55

66
import sys
77

8-
# For python 3.8 and later
9-
if sys.version_info >= (3, 8):
10-
import importlib.metadata as importlib_metadata
11-
else:
12-
# For everyone else
13-
import importlib_metadata
8+
import importlib.metadata as importlib_metadata
9+
1410
try:
1511
__version__ = importlib_metadata.version(__name__)
1612
except importlib_metadata.PackageNotFoundError: # pragma: no cover

cmd2/cmd2.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -700,10 +700,7 @@ def _build_parser(
700700
elif callable(parser_builder):
701701
parser = parser_builder()
702702
elif isinstance(parser_builder, argparse.ArgumentParser):
703-
if sys.version_info >= (3, 6, 4):
704-
parser = copy.deepcopy(parser_builder)
705-
else: # pragma: no cover
706-
parser = parser_builder
703+
parser = copy.deepcopy(parser_builder)
707704
return parser
708705

709706
def _register_command_parser(self, command: str, command_method: Callable[..., Any]) -> None:
@@ -778,9 +775,9 @@ def unregister_command_set(self, cmdset: CommandSet) -> None:
778775
cmdset.on_unregister()
779776
self._unregister_subcommands(cmdset)
780777

781-
methods = inspect.getmembers(
778+
methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers(
782779
cmdset,
783-
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type, var-annotated]
780+
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
784781
and hasattr(meth, '__name__')
785782
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
786783
)
@@ -809,9 +806,9 @@ def unregister_command_set(self, cmdset: CommandSet) -> None:
809806
self._installed_command_sets.remove(cmdset)
810807

811808
def _check_uninstallable(self, cmdset: CommandSet) -> None:
812-
methods = inspect.getmembers(
809+
methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers(
813810
cmdset,
814-
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type, var-annotated]
811+
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
815812
and hasattr(meth, '__name__')
816813
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
817814
)
@@ -3328,19 +3325,14 @@ def _cmdloop(self) -> None:
33283325
#############################################################
33293326

33303327
# Top-level parser for alias
3331-
@staticmethod
3332-
def _build_alias_parser() -> argparse.ArgumentParser:
3333-
alias_description = (
3334-
"Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string."
3335-
)
3336-
alias_epilog = "See also:\n" " macro"
3337-
alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog)
3338-
alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND')
3339-
alias_subparsers.required = True
3340-
return alias_parser
3328+
alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string."
3329+
alias_epilog = "See also:\n" " macro"
3330+
alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog)
3331+
alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND')
3332+
alias_subparsers.required = True
33413333

33423334
# Preserve quotes since we are passing strings to other commands
3343-
@with_argparser(_build_alias_parser, preserve_quotes=True)
3335+
@with_argparser(alias_parser, preserve_quotes=True)
33443336
def do_alias(self, args: argparse.Namespace) -> None:
33453337
"""Manage aliases"""
33463338
# Call handler for whatever subcommand was selected

cmd2/decorators.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
209209
"""
210210
# Set the prog value for this parser
211211
parser.prog = prog
212+
req_args: List[str] = []
212213

213214
# Set the prog value for the parser's subcommands
214215
for action in parser._actions:
@@ -233,13 +234,20 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
233234
if subcmd_parser in processed_parsers:
234235
continue
235236

236-
subcmd_prog = parser.prog + ' ' + subcmd_name
237+
subcmd_prog = parser.prog
238+
if req_args:
239+
subcmd_prog += " " + " ".join(req_args)
240+
subcmd_prog += " " + subcmd_name
237241
_set_parser_prog(subcmd_parser, subcmd_prog)
238242
processed_parsers.append(subcmd_parser)
239243

240244
# We can break since argparse only allows 1 group of subcommands per level
241245
break
242246

247+
# Need to save required args so they can be prepended to the subcommand usage
248+
elif action.required:
249+
req_args.append(action.dest)
250+
243251

244252
#: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input
245253
#: and optionally returns a boolean

docs/features/argument_processing.rst

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,6 @@ Here's what it looks like::
7575
for i in range(min(repetitions, self.maxrepeats)):
7676
self.poutput(arg)
7777

78-
.. warning::
79-
80-
It is important that each command which uses the ``@with_argparser``
81-
decorator be passed a unique instance of a parser since command-specific
82-
changes could be made to it.
83-
84-
8578
.. note::
8679

8780
The ``@with_argparser`` decorator sets the ``prog`` variable in the argument

0 commit comments

Comments
 (0)