# 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 in a manner similar to how POSIX shells do it 1. Parse the resulting argument list using an instance of [Cmd2ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser) that you provide 1. Passes the resulting [argparse.Namespace](https://docs.python.org/3/library/argparse.html#argparse.Namespace) object to your command function. The `Namespace` includes the [Statement][cmd2.Statement] object that was created when parsing the command line. It is accessible via the `cmd2_statement` attribute on the `Namespace`. 1. Adds the usage message from the argument parser to your command's help. 1. Checks if the `-h/--help` option is present, and if so, displays the help message for the command These features are all provided by the [@with_argparser][cmd2.with_argparser] decorator which is imported from `cmd2`. See the [argparse_example](https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_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. ## with_argparser decorator The [@with_argparser][cmd2.with_argparser] decorator can accept the following for its first argument: 1. An existing instance of `Cmd2ArgumentParser` 2. A function or static method which returns an instance of `Cmd2ArgumentParser` 3. Cmd or CommandSet class method which returns an instance of `Cmd2ArgumentParser` In all cases the `@with_argparser` decorator creates a deep copy of the parser instance which it stores internally. A consequence is that parsers don't need to be unique across commands. !!! warning Since the `@with_argparser` decorator is making a deep-copy of the parser provided, if you wish to dynamically modify this parser at a later time, you need to retrieve this deep copy. This can be done using `self.command_parsers.get(self.do_commandname)`. ## Argument Parsing For each command in the `cmd2.Cmd` subclass which requires argument parsing, create an instance of `Cmd2ArgumentParser` which can parse the input appropriately for the command (or provide a function/method that returns such a parser). 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 of the command method, which will contain the results of `Cmd2ArgumentParser.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 `cmd2` 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. As of the 3.0.0 release, `cmd2` sets `prog` when the instance-specific parser is created, which is later than in previous versions. ## 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 `Cmd2ArgumentParser`. !!! tip "description and epilog fields are rich objects" While the `help` field is simply a string, both the `description` and `epilog` fields can accept any [rich](https://github.com/Textualize/rich) renderable. This allows you to include all of rich's built-in objects like `Text`, `Table`, and `Markdown`. 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 an HTML tag""" self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) self.stdout.write('\n') ``` the `help tag` command displays: ```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 ``` If you would prefer, you can set the `description` while instantiating the `Cmd2ArgumentParser` and leave the docstring on your method blank: ```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}'.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
.') 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}'.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
``` !!! warning If a command **foo** is decorated with `cmd2`'s `with_argparse` decorator, 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. ### Argparse HelpFormatter classes `cmd2` has 5 different Argparse HelpFormatter classes, all of which are based on the `RichHelpFormatter` class from [rich-argparse](https://github.com/hamdanal/rich-argparse). The benefit is that your `cmd2` applications now have more aesthetically pleasing help which includes color to make it quicker and easier to visually parse help text. This works for all supported versions of Python. - [Cmd2HelpFormatter][cmd2.argparse_utils.Cmd2HelpFormatter] - default help formatter class - [ArgumentDefaultsCmd2HelpFormatter][cmd2.argparse_utils.ArgumentDefaultsCmd2HelpFormatter] - adds default values to argument help - [MetavarTypeCmd2HelpFormatter][cmd2.argparse_utils.MetavarTypeCmd2HelpFormatter] - uses the argument 'type' as the default metavar value (instead of the argument 'dest') - [RawDescriptionCmd2HelpFormatter][cmd2.argparse_utils.RawDescriptionCmd2HelpFormatter] - retains any formatting in descriptions and epilogs - [RawTextCmd2HelpFormatter][cmd2.argparse_utils.RawTextCmd2HelpFormatter] - retains formatting of all help text The default `Cmd2HelpFormatter` class inherits from `argparse.HelpFormatter`. If you want a different behavior, then pass the desired class to the `formatter_class` argument of your argparse parser, e.g. `formatter_class=ArgumentDefaultsCmd2HelpFormatter` to your parser. ## 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][cmd2.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 your `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][cmd2.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 To pass all unknown arguments 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 your application's state data. 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 `@with_argparser` decorator, 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, giving 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 [argparse_example](https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py) example to learn more about how to use subcommands in your `cmd2` application. The [@as_subcommand_to][cmd2.as_subcommand_to] decorator makes adding subcommands easy. ## 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 [Cmd2ArgumentParser][cmd2.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 affects 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 during an `argparse` error, then that decorator needs to go **before** the `argparse` 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/main/examples/help_categories.py) example demonstrates both above cases in a concrete fashion. ## Reserved Argument Names `cmd2`'s `@with_argparser` decorator adds the following attributes to argparse Namespaces. To avoid naming collisions, do not use any of these names for your argparse arguments. - `cmd2_statement` - [cmd2.Statement][] object that was created when parsing the command line. - `cmd2_subcmd_handler` - subcommand handler function or `None` if one was not set.