1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
|
Argument Processing
===================
``cmd2`` makes it easy to add sophisticated argument processing to your
commands using the `argparse
<https://docs.python.org/3/library/argparse.html>`_ python module. ``cmd2``
handles the following for you:
1. Parsing input and quoted strings like the Unix shell
2. Parse the resulting argument list using an instance of
``argparse.ArgumentParser`` that you provide
3. Passes the resulting ``argparse.Namespace`` object to your command function.
The ``Namespace`` includes the ``Statement`` object that was created when
parsing the command line. It can be retrieved by calling
``cmd2_statement.get()`` on the ``Namespace``.
4. Adds the usage message from the argument parser to your command.
5. Checks if the ``-h/--help`` option is present, and if so, display the help
message for the command
These features are all provided by the ``@with_argparser`` decorator which is
importable from ``cmd2``.
See the either the argprint_ or decorator_ example to learn more about how to
use the various ``cmd2`` argument processing decorators in your ``cmd2``
applications.
.. _argprint: https://github.com/python-cmd2/cmd2/blob/master/examples/arg_print.py
.. _decorator: https://github.com/python-cmd2/cmd2/blob/master/examples/decorator_example.py
``cmd2`` provides the following decorators for assisting with parsing arguments
passed to commands:
* :func:`cmd2.decorators.with_argparser`
* :func:`cmd2.decorators.with_argument_list`
All of these decorators accept an optional **preserve_quotes** argument which
defaults to ``False``. Setting this argument to ``True`` is useful for cases
where you are passing the arguments to another command which might have its own
argument parsing.
Argument Parsing
----------------
For each command in the ``cmd2`` subclass which requires argument parsing,
create a unique instance of ``argparse.ArgumentParser()`` which can parse the
input appropriately for the command. Then decorate the command method with the
``@with_argparser`` decorator, passing the argument parser as the first
parameter to the decorator. This changes the second argument to the command
method, which will contain the results of ``ArgumentParser.parse_args()``.
Here's what it looks like::
import argparse
from cmd2 import with_argparser
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')
argparser.add_argument('word', nargs='?', help='word to say')
@with_argparser(argparser)
def do_speak(self, opts)
"""Repeats what you tell me to."""
arg = opts.word
if opts.piglatin:
arg = '%s%say' % (arg[1:], arg[0])
if opts.shout:
arg = arg.upper()
repetitions = opts.repeat or 1
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. This limitation is due
to bugs in CPython prior to Python 3.7 which make it impossible to make a
deep copy of an instance of a ``argparse.ArgumentParser``.
See the table_display_ example for a work-around that demonstrates how to
create a function which returns a unique instance of the parser you want.
.. note::
The ``@with_argparser`` decorator sets the ``prog`` variable in the argument
parser based on the name of the method it is decorating. This will override
anything you specify in ``prog`` variable when creating the argument parser.
.. _table_display: https://github.com/python-cmd2/cmd2/blob/master/examples/table_display.py
Help Messages
-------------
By default, ``cmd2`` uses the docstring of the command method when a user asks
for help on the command. When you use the ``@with_argparser`` decorator, the
docstring for the ``do_*`` method is used to set the description for the
``argparse.ArgumentParser``.
With this code::
import argparse
from cmd2 import with_argparser
argparser = argparse.ArgumentParser()
argparser.add_argument('tag', help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argparser(argparser)
def do_tag(self, args):
"""create a html tag"""
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
self.stdout.write('\n')
the ``help tag`` command displays:
.. code-block:: text
usage: tag [-h] tag content [content ...]
create a html tag
positional arguments:
tag tag
content content to surround with tag
optional arguments:
-h, --help show this help message and exit
If you would prefer you can set the ``description`` while instantiating the
``argparse.ArgumentParser`` and leave the docstring on your method empty::
import argparse
from cmd2 import with_argparser
argparser = argparse.ArgumentParser(description='create an html tag')
argparser.add_argument('tag', help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argparser(argparser)
def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
self.stdout.write('\n')
Now when the user enters ``help tag`` they see:
.. code-block:: text
usage: tag [-h] tag content [content ...]
create an html tag
positional arguments:
tag tag
content content to surround with tag
optional arguments:
-h, --help show this help message and exit
To add additional text to the end of the generated help message, use the ``epilog`` variable::
import argparse
from cmd2 import with_argparser
argparser = argparse.ArgumentParser(description='create an html tag',
epilog='This command can not generate tags with no content, like <br/>.')
argparser.add_argument('tag', help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argparser(argparser)
def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
self.stdout.write('\n')
Which yields:
.. code-block:: text
usage: tag [-h] tag content [content ...]
create an html tag
positional arguments:
tag tag
content content to surround with tag
optional arguments:
-h, --help show this help message and exit
This command can not generate tags with no content, like <br/>
.. warning::
If a command **foo** is decorated with one of cmd2's argparse decorators,
then **help_foo** will not be invoked when ``help foo`` is called. The
argparse_ module provides a rich API which can be used to tweak every
aspect of the displayed help and we encourage ``cmd2`` developers to
utilize that.
.. _argparse: https://docs.python.org/3/library/argparse.html
Argument List
-------------
The default behavior of ``cmd2`` is to pass the user input directly to your
``do_*`` methods as a string. The object passed to your method is actually a
``Statement`` object, which has additional attributes that may be helpful,
including ``arg_list`` and ``argv``::
class CmdLineApp(cmd2.Cmd):
""" Example cmd2 application. """
def do_say(self, statement):
# statement contains a string
self.poutput(statement)
def do_speak(self, statement):
# statement also has a list of arguments
# quoted arguments remain quoted
for arg in statement.arg_list:
self.poutput(arg)
def do_articulate(self, statement):
# statement.argv contains the command
# and the arguments, which have had quotes
# stripped
for arg in statement.argv:
self.poutput(arg)
If you don't want to access the additional attributes on the string passed to
you``do_*`` method you can still have ``cmd2`` apply shell parsing rules to the
user input and pass you a list of arguments instead of a string. Apply the
``@with_argument_list`` decorator to those methods that should receive an
argument list instead of a string::
from cmd2 import with_argument_list
class CmdLineApp(cmd2.Cmd):
""" Example cmd2 application. """
def do_say(self, cmdline):
# cmdline contains a string
pass
@with_argument_list
def do_speak(self, arglist):
# arglist contains a list of arguments
pass
Unknown Positional Arguments
----------------------------
If you want all unknown arguments to be passed to your command as a list of
strings, then decorate the command method with the
``@with_argparser(..., with_unknown_args=True)`` decorator.
Here's what it looks like::
import argparse
from cmd2 import with_argparser
dir_parser = argparse.ArgumentParser()
dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line")
@with_argparser(dir_parser, with_unknown_args=True)
def do_dir(self, args, unknown):
"""List contents of current directory."""
# No arguments for this command
if unknown:
self.perror("dir does not take any positional arguments:")
self.do_help('dir')
self.last_result = CommandResult('', 'Bad arguments')
return
# Get the contents as a list
contents = os.listdir(self.cwd)
...
Using A Custom Namespace
------------------------
In some cases, it may be necessary to write custom ``argparse`` code that is
dependent on state data of your application. To support this ability while
still allowing use of the decorators, ``@with_argparser`` has an optional
argument called ``ns_provider``.
``ns_provider`` is a Callable that accepts a ``cmd2.Cmd`` object as an argument
and returns an ``argparse.Namespace``::
Callable[[cmd2.Cmd], argparse.Namespace]
For example::
def settings_ns_provider(self) -> argparse.Namespace:
"""Populate an argparse Namespace with current settings"""
ns = argparse.Namespace()
ns.app_settings = self.settings
return ns
To use this function with the argparse decorators, do the following::
@with_argparser(my_parser, ns_provider=settings_ns_provider)
The Namespace is passed by the decorators to the ``argparse`` parsing functions
which gives your custom code access to the state data it needs for its parsing
logic.
Subcommands
------------
Subcommands are supported for commands using the ``@with_argparser`` decorator.
The syntax is based on argparse sub-parsers.
You may add multiple layers of subcommands for your command. ``cmd2`` will
automatically traverse and tab complete subcommands for all commands using
argparse.
See the subcommands_ example to learn more about how to
use subcommands in your ``cmd2`` application.
.. _subcommands: https://github.com/python-cmd2/cmd2/blob/master/examples/subcommands.py
Argparse Extensions
-------------------
``cmd2`` augments the standard ``argparse.nargs`` with range tuple capability:
- ``nargs=(5,)`` - accept 5 or more items
- ``nargs=(8, 12)`` - accept 8 to 12 items
``cmd2`` also provides the :class:`cmd2.argparse_custom.Cmd2ArgumentParser`
class which inherits from ``argparse.ArgumentParser`` and improves error and
help output.
Decorator Order
---------------
If you are using custom decorators in combination with
``@cmd2.with_argparser``, then the
order of your custom decorator(s) relative to the ``cmd2`` decorator matters
when it comes to runtime behavior and ``argparse`` errors. There is nothing
``cmd2``-specific here, this is just a side-effect of how decorators work in
Python. To learn more about how decorators work, see decorator_primer_.
If you want your custom decorator's runtime behavior to occur in the case of
an ``argparse`` error, then that decorator needs to go **after** the
``argparse`` one, e.g.::
@cmd2.with_argparser(foo_parser)
@my_decorator
def do_foo(self, args: argparse.Namespace) -> None:
"""foo docs"""
pass
However, if you do NOT want the custom decorator runtime behavior to occur
even in the case of an `argparse` error, then that decorator needs to go
**before** the ``arpgarse`` one, e.g.::
@my_decorator
@cmd2.with_argparser(bar_parser)
def do_bar(self, args: argparse.Namespace) -> None:
"""bar docs"""
pass
The help_categories_ example demonstrates both above cases in a concrete
fashion.
.. _decorator_primer: https://realpython.com/primer-on-python-decorators
.. _help_categories: https://github.com/python-cmd2/cmd2/blob/master/examples/help_categories.py
Reserved Argument Names
-----------------------
``cmd2`` argparse decorators add the following attributes to argparse
Namespaces. To avoid naming collisions, do not use any of the names for your
argparse arguments.
- ``cmd2_statement`` - ``cmd2.Cmd2AttributeWrapper`` object containing
``cmd2.Statement`` object that was created when parsing the command line.
- ``cmd2_handler`` - ``cmd2.Cmd2AttributeWrapper`` object containing
a subcommand handler function or ``None`` if one was not set.
- ``__subcmd_handler__`` - used by cmd2 to identify the handler for a
subcommand created with ``@cmd2.as_subcommand_to`` decorator.
|