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
|
# 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
1. Parse the resulting argument list using an instance of `argparse.ArgumentParser` that you provide
1. 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`.
1. Adds the usage message from the argument parser to your command.
1. 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](https://github.com/python-cmd2/cmd2/blob/master/examples/arg_print.py) or [decorator](https://github.com/python-cmd2/cmd2/blob/master/examples/decorator_example.py) example to learn more about how to use the various `cmd2` argument processing decorators in your `cmd2` applications.
`cmd2` provides the following [decorators](../api/decorators.md) for assisting with parsing arguments passed to commands:
- `cmd2.decorators.with_argparser`
- `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:
```py
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser()
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)
```
!!! 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.
## 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:
```py
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser()
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:
```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:
```py
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser(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:
```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:
```py
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser(description='create an html tag',
epilog='This command cannot 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:
```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 cannot 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](https://docs.python.org/3/library/argparse.html) 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.
## 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`:
```py
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:
```py
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:
```py
from cmd2 import Cmd2ArgumentParser, with_argparser
dir_parser = Cmd2ArgumentParser()
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 = '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`:
```py
Callable[[cmd2.Cmd], argparse.Namespace]
```
For example:
```py
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:
```py
@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](https://github.com/python-cmd2/cmd2/blob/master/examples/subcommands.py) example to learn more about how to use subcommands in your `cmd2` application.
## 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 `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](https://realpython.com/primer-on-python-decorators).
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.:
```py
@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.:
```py
@my_decorator
@cmd2.with_argparser(bar_parser)
def do_bar(self, args: argparse.Namespace) -> None:
"""bar docs"""
pass
```
The [help_categories](https://github.com/python-cmd2/cmd2/blob/master/examples/help_categories.py) example demonstrates both above cases in a concrete fashion.
## 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.
|