cmd2 2.4.2__py3-none-any.whl → 2.5.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@
2
2
  """
3
3
  Supports the definition of commands in separate classes to be composed into cmd2.Cmd
4
4
  """
5
+
5
6
  from typing import (
6
7
  TYPE_CHECKING,
7
8
  Callable,
@@ -9,6 +10,7 @@ from typing import (
9
10
  Mapping,
10
11
  Optional,
11
12
  Type,
13
+ TypeVar,
12
14
  )
13
15
 
14
16
  from .constants import (
@@ -29,8 +31,10 @@ if TYPE_CHECKING: # pragma: no cover
29
31
  #: Further refinements are needed to define the input parameters
30
32
  CommandFunc = Callable[..., Optional[bool]]
31
33
 
34
+ CommandSetType = TypeVar('CommandSetType', bound=Type['CommandSet'])
35
+
32
36
 
33
- def with_default_category(category: str, *, heritable: bool = True) -> Callable[[Type['CommandSet']], Type['CommandSet']]:
37
+ def with_default_category(category: str, *, heritable: bool = True) -> Callable[[CommandSetType], CommandSetType]:
34
38
  """
35
39
  Decorator that applies a category to all ``do_*`` command methods in a class that do not already
36
40
  have a category specified.
@@ -41,7 +45,7 @@ def with_default_category(category: str, *, heritable: bool = True) -> Callable[
41
45
  override the default category.
42
46
 
43
47
  If `heritable` is set to False, then only the commands declared locally to this CommandSet will be placed in the
44
- specified category. Dynamically created commands, and commands declared in sub-classes will not receive this
48
+ specified category. Dynamically created commands and commands declared in sub-classes will not receive this
45
49
  category.
46
50
 
47
51
  :param category: category to put all uncategorized commands in
@@ -49,7 +53,7 @@ def with_default_category(category: str, *, heritable: bool = True) -> Callable[
49
53
  :return: decorator function
50
54
  """
51
55
 
52
- def decorate_class(cls: Type[CommandSet]) -> Type[CommandSet]:
56
+ def decorate_class(cls: CommandSetType) -> CommandSetType:
53
57
  if heritable:
54
58
  setattr(cls, CLASS_ATTR_DEFAULT_HELP_CATEGORY, category)
55
59
 
@@ -91,10 +95,31 @@ class CommandSet(object):
91
95
  """
92
96
 
93
97
  def __init__(self) -> None:
94
- self._cmd: Optional[cmd2.Cmd] = None
98
+ # Private reference to the CLI instance in which this CommandSet running.
99
+ # This will be set when the CommandSet is registered and it should be
100
+ # accessed by child classes using the self._cmd property.
101
+ self.__cmd_internal: Optional[cmd2.Cmd] = None
102
+
95
103
  self._settables: Dict[str, Settable] = {}
96
104
  self._settable_prefix = self.__class__.__name__
97
105
 
106
+ @property
107
+ def _cmd(self) -> 'cmd2.Cmd':
108
+ """
109
+ Property for child classes to access self.__cmd_internal.
110
+
111
+ Using this property ensures that self.__cmd_internal has been set
112
+ and it tells type checkers that it's no longer a None type.
113
+
114
+ Override this property if you need to change its return type to a
115
+ child class of Cmd.
116
+
117
+ :raises: CommandSetRegistrationError if CommandSet is not registered.
118
+ """
119
+ if self.__cmd_internal is None:
120
+ raise CommandSetRegistrationError('This CommandSet is not registered')
121
+ return self.__cmd_internal
122
+
98
123
  def on_register(self, cmd: 'cmd2.Cmd') -> None:
99
124
  """
100
125
  Called by cmd2.Cmd as the first step to registering a CommandSet. The commands defined in this class have
@@ -102,9 +127,10 @@ class CommandSet(object):
102
127
  requiring access to the Cmd object (e.g. configure commands and their parsers based on CLI state data).
103
128
 
104
129
  :param cmd: The cmd2 main application
130
+ :raises: CommandSetRegistrationError if CommandSet is already registered.
105
131
  """
106
- if self._cmd is None:
107
- self._cmd = cmd
132
+ if self.__cmd_internal is None:
133
+ self.__cmd_internal = cmd
108
134
  else:
109
135
  raise CommandSetRegistrationError('This CommandSet has already been registered')
110
136
 
@@ -128,7 +154,7 @@ class CommandSet(object):
128
154
  Called by ``cmd2.Cmd`` after a CommandSet has been unregistered and all its commands removed from the CLI.
129
155
  Subclasses can override this to perform remaining cleanup steps.
130
156
  """
131
- self._cmd = None
157
+ self.__cmd_internal = None
132
158
 
133
159
  @property
134
160
  def settable_prefix(self) -> str:
@@ -144,7 +170,7 @@ class CommandSet(object):
144
170
 
145
171
  :param settable: Settable object being added
146
172
  """
147
- if self._cmd:
173
+ if self.__cmd_internal is not None:
148
174
  if not self._cmd.always_prefix_settables:
149
175
  if settable.name in self._cmd.settables.keys() and settable.name not in self._settables.keys():
150
176
  raise KeyError(f'Duplicate settable: {settable.name}')
@@ -165,3 +191,12 @@ class CommandSet(object):
165
191
  del self._settables[name]
166
192
  except KeyError:
167
193
  raise KeyError(name + " is not a settable parameter")
194
+
195
+ def sigint_handler(self) -> bool:
196
+ """
197
+ Handle a SIGINT that occurred for a command in this CommandSet.
198
+
199
+ :return: True if this completes the interrupt handling and no KeyboardInterrupt will be raised.
200
+ False to raise a KeyboardInterrupt.
201
+ """
202
+ return False
cmd2/constants.py CHANGED
@@ -57,3 +57,6 @@ PARSER_ATTR_COMMANDSET = 'command_set'
57
57
 
58
58
  # custom attributes added to argparse Namespaces
59
59
  NS_ATTR_SUBCMD_HANDLER = '__subcmd_handler__'
60
+
61
+ # For cases prior to Python 3.11 when shutil.get_terminal_size().columns can return 0.
62
+ DEFAULT_TERMINAL_WIDTH = 80
cmd2/decorators.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding=utf-8
2
2
  """Decorators for ``cmd2`` commands"""
3
+
3
4
  import argparse
4
5
  from typing import (
5
6
  TYPE_CHECKING,
@@ -10,6 +11,8 @@ from typing import (
10
11
  Optional,
11
12
  Sequence,
12
13
  Tuple,
14
+ Type,
15
+ TypeVar,
13
16
  Union,
14
17
  )
15
18
 
@@ -29,9 +32,6 @@ from .exceptions import (
29
32
  from .parsing import (
30
33
  Statement,
31
34
  )
32
- from .utils import (
33
- strip_doc_annotations,
34
- )
35
35
 
36
36
  if TYPE_CHECKING: # pragma: no cover
37
37
  import cmd2
@@ -43,15 +43,17 @@ def with_category(category: str) -> Callable[[CommandFunc], CommandFunc]:
43
43
  :param category: the name of the category in which this command should
44
44
  be grouped when displaying the list of commands.
45
45
 
46
- :Example:
46
+ Example:
47
47
 
48
- >>> class MyApp(cmd2.Cmd):
49
- >>> @cmd2.with_category('Text Functions')
50
- >>> def do_echo(self, args)
51
- >>> self.poutput(args)
48
+ ```py
49
+ class MyApp(cmd2.Cmd):
50
+ @cmd2.with_category('Text Functions')
51
+ def do_echo(self, args)
52
+ self.poutput(args)
53
+ ```
52
54
 
53
55
  For an alternative approach to categorizing commands using a function, see
54
- :func:`~cmd2.utils.categorize`
56
+ [cmd2.utils.categorize][]
55
57
  """
56
58
 
57
59
  def cat_decorator(func: CommandFunc) -> CommandFunc:
@@ -65,16 +67,18 @@ def with_category(category: str) -> Callable[[CommandFunc], CommandFunc]:
65
67
  return cat_decorator
66
68
 
67
69
 
70
+ CommandParent = TypeVar('CommandParent', bound=Union['cmd2.Cmd', CommandSet])
71
+ CommandParentType = TypeVar('CommandParentType', bound=Union[Type['cmd2.Cmd'], Type[CommandSet]])
72
+
73
+
74
+ RawCommandFuncOptionalBoolReturn = Callable[[CommandParent, Union[Statement, str]], Optional[bool]]
75
+
76
+
68
77
  ##########################
69
78
  # The _parse_positionals and _arg_swap functions allow for additional positional args to be preserved
70
79
  # in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be
71
80
  # found we can swap out the statement with each decorator's specific parameters
72
81
  ##########################
73
-
74
-
75
- RawCommandFuncOptionalBoolReturn = Callable[[Union[CommandSet, 'cmd2.Cmd'], Union[Statement, str]], Optional[bool]]
76
-
77
-
78
82
  def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Statement, str]]:
79
83
  """
80
84
  Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found
@@ -87,7 +91,7 @@ def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Stateme
87
91
  Cmd,
88
92
  )
89
93
 
90
- if (isinstance(arg, Cmd) or isinstance(arg, CommandSet)) and len(args) > pos:
94
+ if isinstance(arg, (Cmd, CommandSet)) and len(args) > pos + 1:
91
95
  if isinstance(arg, CommandSet):
92
96
  arg = arg._cmd
93
97
  next_arg = args[pos + 1]
@@ -96,7 +100,7 @@ def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Stateme
96
100
 
97
101
  # This shouldn't happen unless we forget to pass statement in `Cmd.onecmd` or
98
102
  # somehow call the unbound class method.
99
- raise TypeError('Expected arguments: cmd: cmd2.Cmd, statement: Union[Statement, str] Not found') # pragma: no cover
103
+ raise TypeError('Expected arguments: cmd: cmd2.Cmd, statement: Union[Statement, str] Not found')
100
104
 
101
105
 
102
106
  def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> List[Any]:
@@ -114,54 +118,53 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) ->
114
118
  return args_list
115
119
 
116
120
 
117
- #: Function signature for an Command Function that accepts a pre-processed argument list from user input
121
+ #: Function signature for a command function that accepts a pre-processed argument list from user input
118
122
  #: and optionally returns a boolean
119
- ArgListCommandFuncOptionalBoolReturn = Union[
120
- Callable[['cmd2.Cmd', List[str]], Optional[bool]],
121
- Callable[[CommandSet, List[str]], Optional[bool]],
122
- ]
123
- #: Function signature for an Command Function that accepts a pre-processed argument list from user input
123
+ ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, List[str]], Optional[bool]]
124
+ #: Function signature for a command function that accepts a pre-processed argument list from user input
124
125
  #: and returns a boolean
125
- ArgListCommandFuncBoolReturn = Union[
126
- Callable[['cmd2.Cmd', List[str]], bool],
127
- Callable[[CommandSet, List[str]], bool],
128
- ]
129
- #: Function signature for an Command Function that accepts a pre-processed argument list from user input
126
+ ArgListCommandFuncBoolReturn = Callable[[CommandParent, List[str]], bool]
127
+ #: Function signature for a command function that accepts a pre-processed argument list from user input
130
128
  #: and returns Nothing
131
- ArgListCommandFuncNoneReturn = Union[
132
- Callable[['cmd2.Cmd', List[str]], None],
133
- Callable[[CommandSet, List[str]], None],
134
- ]
129
+ ArgListCommandFuncNoneReturn = Callable[[CommandParent, List[str]], None]
135
130
 
136
- #: Aggregate of all accepted function signatures for Command Functions that accept a pre-processed argument list
137
- ArgListCommandFunc = Union[ArgListCommandFuncOptionalBoolReturn, ArgListCommandFuncBoolReturn, ArgListCommandFuncNoneReturn]
131
+ #: Aggregate of all accepted function signatures for command functions that accept a pre-processed argument list
132
+ ArgListCommandFunc = Union[
133
+ ArgListCommandFuncOptionalBoolReturn[CommandParent],
134
+ ArgListCommandFuncBoolReturn[CommandParent],
135
+ ArgListCommandFuncNoneReturn[CommandParent],
136
+ ]
138
137
 
139
138
 
140
139
  def with_argument_list(
141
- func_arg: Optional[ArgListCommandFunc] = None,
140
+ func_arg: Optional[ArgListCommandFunc[CommandParent]] = None,
142
141
  *,
143
142
  preserve_quotes: bool = False,
144
- ) -> Union[RawCommandFuncOptionalBoolReturn, Callable[[ArgListCommandFunc], RawCommandFuncOptionalBoolReturn]]:
143
+ ) -> Union[
144
+ RawCommandFuncOptionalBoolReturn[CommandParent],
145
+ Callable[[ArgListCommandFunc[CommandParent]], RawCommandFuncOptionalBoolReturn[CommandParent]],
146
+ ]:
145
147
  """
146
148
  A decorator to alter the arguments passed to a ``do_*`` method. Default
147
149
  passes a string of whatever the user typed. With this decorator, the
148
150
  decorated method will receive a list of arguments parsed from user input.
149
151
 
150
- :param func_arg: Single-element positional argument list containing ``do_*`` method
152
+ :param func_arg: Single-element positional argument list containing ``doi_*`` method
151
153
  this decorator is wrapping
152
154
  :param preserve_quotes: if ``True``, then argument quotes will not be stripped
153
155
  :return: function that gets passed a list of argument strings
154
156
 
155
- :Example:
156
-
157
- >>> class MyApp(cmd2.Cmd):
158
- >>> @cmd2.with_argument_list
159
- >>> def do_echo(self, arglist):
160
- >>> self.poutput(' '.join(arglist)
157
+ Example:
158
+ ```py
159
+ class MyApp(cmd2.Cmd):
160
+ @cmd2.with_argument_list
161
+ def do_echo(self, arglist):
162
+ self.poutput(' '.join(arglist)
163
+ ```
161
164
  """
162
165
  import functools
163
166
 
164
- def arg_decorator(func: ArgListCommandFunc) -> RawCommandFuncOptionalBoolReturn:
167
+ def arg_decorator(func: ArgListCommandFunc[CommandParent]) -> RawCommandFuncOptionalBoolReturn[CommandParent]:
165
168
  """
166
169
  Decorator function that ingests an Argument List function and returns a raw command function.
167
170
  The returned function will process the raw input into an argument list to be passed to the wrapped function.
@@ -191,14 +194,11 @@ def with_argument_list(
191
194
  return cmd_wrapper
192
195
 
193
196
  if callable(func_arg):
194
- # noinspection PyTypeChecker
195
197
  return arg_decorator(func_arg)
196
198
  else:
197
- # noinspection PyTypeChecker
198
199
  return arg_decorator
199
200
 
200
201
 
201
- # noinspection PyProtectedMember
202
202
  def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
203
203
  """
204
204
  Recursively set prog attribute of a parser and all of its subparsers so that the root command
@@ -209,6 +209,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
209
209
  """
210
210
  # Set the prog value for this parser
211
211
  parser.prog = prog
212
+ req_args: List[str] = []
212
213
 
213
214
  # Set the prog value for the parser's subcommands
214
215
  for action in parser._actions:
@@ -233,94 +234,107 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
233
234
  if subcmd_parser in processed_parsers:
234
235
  continue
235
236
 
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
237
241
  _set_parser_prog(subcmd_parser, subcmd_prog)
238
242
  processed_parsers.append(subcmd_parser)
239
243
 
240
244
  # We can break since argparse only allows 1 group of subcommands per level
241
245
  break
242
246
 
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)
243
250
 
244
- #: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input
245
- #: and optionally returns a boolean
246
- ArgparseCommandFuncOptionalBoolReturn = Union[
247
- Callable[['cmd2.Cmd', argparse.Namespace], Optional[bool]],
248
- Callable[[CommandSet, argparse.Namespace], Optional[bool]],
249
- ]
250
- #: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input
251
- #: and returns a boolean
252
- ArgparseCommandFuncBoolReturn = Union[
253
- Callable[['cmd2.Cmd', argparse.Namespace], bool],
254
- Callable[[CommandSet, argparse.Namespace], bool],
255
- ]
256
- #: Function signature for an Command Function that uses an argparse.ArgumentParser to process user input
257
- #: and returns nothing
258
- ArgparseCommandFuncNoneReturn = Union[
259
- Callable[['cmd2.Cmd', argparse.Namespace], None],
260
- Callable[[CommandSet, argparse.Namespace], None],
261
- ]
262
251
 
263
- #: Aggregate of all accepted function signatures for an argparse Command Function
252
+ #: Function signatures for command functions that use an argparse.ArgumentParser to process user input
253
+ #: and optionally return a boolean
254
+ ArgparseCommandFuncOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace], Optional[bool]]
255
+ ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], Optional[bool]]
256
+
257
+ #: Function signatures for command functions that use an argparse.ArgumentParser to process user input
258
+ #: and return a boolean
259
+ ArgparseCommandFuncBoolReturn = Callable[[CommandParent, argparse.Namespace], bool]
260
+ ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], bool]
261
+
262
+ #: Function signatures for command functions that use an argparse.ArgumentParser to process user input
263
+ #: and return nothing
264
+ ArgparseCommandFuncNoneReturn = Callable[[CommandParent, argparse.Namespace], None]
265
+ ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, List[str]], None]
266
+
267
+ #: Aggregate of all accepted function signatures for an argparse command function
264
268
  ArgparseCommandFunc = Union[
265
- ArgparseCommandFuncOptionalBoolReturn,
266
- ArgparseCommandFuncBoolReturn,
267
- ArgparseCommandFuncNoneReturn,
269
+ ArgparseCommandFuncOptionalBoolReturn[CommandParent],
270
+ ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn[CommandParent],
271
+ ArgparseCommandFuncBoolReturn[CommandParent],
272
+ ArgparseCommandFuncWithUnknownArgsBoolReturn[CommandParent],
273
+ ArgparseCommandFuncNoneReturn[CommandParent],
274
+ ArgparseCommandFuncWithUnknownArgsNoneReturn[CommandParent],
268
275
  ]
269
276
 
270
277
 
271
278
  def with_argparser(
272
- parser: argparse.ArgumentParser,
279
+ parser: Union[
280
+ argparse.ArgumentParser, # existing parser
281
+ Callable[[], argparse.ArgumentParser], # function or staticmethod
282
+ Callable[[CommandParentType], argparse.ArgumentParser], # Cmd or CommandSet classmethod
283
+ ],
273
284
  *,
274
285
  ns_provider: Optional[Callable[..., argparse.Namespace]] = None,
275
286
  preserve_quotes: bool = False,
276
287
  with_unknown_args: bool = False,
277
- ) -> Callable[[ArgparseCommandFunc], RawCommandFuncOptionalBoolReturn]:
288
+ ) -> Callable[[ArgparseCommandFunc[CommandParent]], RawCommandFuncOptionalBoolReturn[CommandParent]]:
278
289
  """A decorator to alter a cmd2 method to populate its ``args`` argument by parsing arguments
279
290
  with the given instance of argparse.ArgumentParser.
280
291
 
281
- :param parser: unique instance of ArgumentParser
292
+ :param parser: unique instance of ArgumentParser or a callable that returns an ArgumentParser
282
293
  :param ns_provider: An optional function that accepts a cmd2.Cmd or cmd2.CommandSet object as an argument and returns an
283
294
  argparse.Namespace. This is useful if the Namespace needs to be prepopulated with state data that
284
295
  affects parsing.
285
296
  :param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes
286
297
  :param with_unknown_args: if true, then capture unknown args
287
298
  :return: function that gets passed argparse-parsed args in a ``Namespace``
288
- A :class:`cmd2.argparse_custom.Cmd2AttributeWrapper` called ``cmd2_statement`` is included
289
- in the ``Namespace`` to provide access to the :class:`cmd2.Statement` object that was created when
299
+ A [cmd2.argparse_custom.Cmd2AttributeWrapper][] called ``cmd2_statement`` is included
300
+ in the ``Namespace`` to provide access to the [cmd2.Statement][] object that was created when
290
301
  parsing the command line. This can be useful if the command function needs to know the command line.
291
302
 
292
- :Example:
293
-
294
- >>> parser = cmd2.Cmd2ArgumentParser()
295
- >>> parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
296
- >>> parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
297
- >>> parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
298
- >>> parser.add_argument('words', nargs='+', help='words to print')
299
- >>>
300
- >>> class MyApp(cmd2.Cmd):
301
- >>> @cmd2.with_argparser(parser, preserve_quotes=True)
302
- >>> def do_argprint(self, args):
303
- >>> "Print the options and argument list this options command was called with."
304
- >>> self.poutput(f'args: {args!r}')
305
-
306
- :Example with unknown args:
307
-
308
- >>> parser = cmd2.Cmd2ArgumentParser()
309
- >>> parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
310
- >>> parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
311
- >>> parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
312
- >>>
313
- >>> class MyApp(cmd2.Cmd):
314
- >>> @cmd2.with_argparser(parser, with_unknown_args=True)
315
- >>> def do_argprint(self, args, unknown):
316
- >>> "Print the options and argument list this options command was called with."
317
- >>> self.poutput(f'args: {args!r}')
318
- >>> self.poutput(f'unknowns: {unknown}')
319
-
303
+ Example:
304
+
305
+ ```py
306
+ parser = cmd2.Cmd2ArgumentParser()
307
+ parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
308
+ parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
309
+ parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
310
+ parser.add_argument('words', nargs='+', help='words to print')
311
+
312
+ class MyApp(cmd2.Cmd):
313
+ @cmd2.with_argparser(parser, preserve_quotes=True)
314
+ def do_argprint(self, args):
315
+ "Print the options and argument list this options command was called with."
316
+ self.poutput(f'args: {args!r}')
317
+ ```
318
+
319
+ Example with unknown args:
320
+
321
+ ```py
322
+ parser = cmd2.Cmd2ArgumentParser()
323
+ parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
324
+ parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
325
+ parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
326
+
327
+ class MyApp(cmd2.Cmd):
328
+ @cmd2.with_argparser(parser, with_unknown_args=True)
329
+ def do_argprint(self, args, unknown):
330
+ "Print the options and argument list this options command was called with."
331
+ self.poutput(f'args: {args!r}')
332
+ self.poutput(f'unknowns: {unknown}')
333
+ ```
320
334
  """
321
335
  import functools
322
336
 
323
- def arg_decorator(func: ArgparseCommandFunc) -> RawCommandFuncOptionalBoolReturn:
337
+ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOptionalBoolReturn[CommandParent]:
324
338
  """
325
339
  Decorator function that ingests an Argparse Command Function and returns a raw command function.
326
340
  The returned function will process the raw input into an argparse Namespace to be passed to the wrapped function.
@@ -347,6 +361,12 @@ def with_argparser(
347
361
  command_name, statement_arg, preserve_quotes
348
362
  )
349
363
 
364
+ # Pass cmd_wrapper instead of func, since it contains the parser info.
365
+ arg_parser = cmd2_app._command_parsers.get(cmd_wrapper)
366
+ if arg_parser is None:
367
+ # This shouldn't be possible to reach
368
+ raise ValueError(f'No argument parser found for {command_name}') # pragma: no cover
369
+
350
370
  if ns_provider is None:
351
371
  namespace = None
352
372
  else:
@@ -359,9 +379,9 @@ def with_argparser(
359
379
  try:
360
380
  new_args: Union[Tuple[argparse.Namespace], Tuple[argparse.Namespace, List[str]]]
361
381
  if with_unknown_args:
362
- new_args = parser.parse_known_args(parsed_arglist, namespace)
382
+ new_args = arg_parser.parse_known_args(parsed_arglist, namespace)
363
383
  else:
364
- new_args = (parser.parse_args(parsed_arglist, namespace),)
384
+ new_args = (arg_parser.parse_args(parsed_arglist, namespace),)
365
385
  ns = new_args[0]
366
386
  except SystemExit:
367
387
  raise Cmd2ArgparseError
@@ -381,16 +401,7 @@ def with_argparser(
381
401
  args_list = _arg_swap(args, statement_arg, *new_args)
382
402
  return func(*args_list, **kwargs) # type: ignore[call-arg]
383
403
 
384
- # argparser defaults the program name to sys.argv[0], but we want it to be the name of our command
385
404
  command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX) :]
386
- _set_parser_prog(parser, command_name)
387
-
388
- # If the description has not been set, then use the method docstring if one exists
389
- if parser.description is None and func.__doc__:
390
- parser.description = strip_doc_annotations(func.__doc__)
391
-
392
- # Set the command's help text as argparser.description (which can be None)
393
- cmd_wrapper.__doc__ = parser.description
394
405
 
395
406
  # Set some custom attributes for this command
396
407
  setattr(cmd_wrapper, constants.CMD_ATTR_ARGPARSER, parser)
@@ -398,18 +409,21 @@ def with_argparser(
398
409
 
399
410
  return cmd_wrapper
400
411
 
401
- # noinspection PyTypeChecker
402
412
  return arg_decorator
403
413
 
404
414
 
405
415
  def as_subcommand_to(
406
416
  command: str,
407
417
  subcommand: str,
408
- parser: argparse.ArgumentParser,
418
+ parser: Union[
419
+ argparse.ArgumentParser, # existing parser
420
+ Callable[[], argparse.ArgumentParser], # function or staticmethod
421
+ Callable[[CommandParentType], argparse.ArgumentParser], # Cmd or CommandSet classmethod
422
+ ],
409
423
  *,
410
424
  help: Optional[str] = None,
411
425
  aliases: Optional[List[str]] = None,
412
- ) -> Callable[[ArgparseCommandFunc], ArgparseCommandFunc]:
426
+ ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]:
413
427
  """
414
428
  Tag this method as a subcommand to an existing argparse decorated command.
415
429
 
@@ -417,25 +431,19 @@ def as_subcommand_to(
417
431
  :param subcommand: Subcommand name
418
432
  :param parser: argparse Parser for this subcommand
419
433
  :param help: Help message for this subcommand which displays in the list of subcommands of the command we are adding to.
420
- This is passed as the help argument to ArgumentParser.add_subparser().
434
+ This is passed as the help argument to subparsers.add_parser().
421
435
  :param aliases: Alternative names for this subcommand. This is passed as the alias argument to
422
- ArgumentParser.add_subparser().
436
+ subparsers.add_parser().
423
437
  :return: Wrapper function that can receive an argparse.Namespace
424
438
  """
425
439
 
426
- def arg_decorator(func: ArgparseCommandFunc) -> ArgparseCommandFunc:
427
- _set_parser_prog(parser, command + ' ' + subcommand)
428
-
429
- # If the description has not been set, then use the method docstring if one exists
430
- if parser.description is None and func.__doc__:
431
- parser.description = func.__doc__
432
-
440
+ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> ArgparseCommandFunc[CommandParent]:
433
441
  # Set some custom attributes for this command
434
442
  setattr(func, constants.SUBCMD_ATTR_COMMAND, command)
435
443
  setattr(func, constants.CMD_ATTR_ARGPARSER, parser)
436
444
  setattr(func, constants.SUBCMD_ATTR_NAME, subcommand)
437
445
 
438
- # Keyword arguments for ArgumentParser.add_subparser()
446
+ # Keyword arguments for subparsers.add_parser()
439
447
  add_parser_kwargs: Dict[str, Any] = dict()
440
448
  if help is not None:
441
449
  add_parser_kwargs['help'] = help
@@ -446,5 +454,4 @@ def as_subcommand_to(
446
454
 
447
455
  return func
448
456
 
449
- # noinspection PyTypeChecker
450
457
  return arg_decorator
cmd2/exceptions.py CHANGED
@@ -61,7 +61,6 @@ class CompletionError(Exception):
61
61
  """
62
62
  self.apply_style = apply_style
63
63
 
64
- # noinspection PyArgumentList
65
64
  super().__init__(*args)
66
65
 
67
66