# DryParse, a don't-repeat-yourself command-line parser

DryParse is a command-line parser for Python 3. It maps command-line "commands" to Python callables, transforming the command-line arguments into Python values and passing them in as arguments.

DryParse's goal is to minimize repetition.

## Terminology

DryParse uses the following terms for things one might find on a command-line:

% ./program.py --debug foo --xyz 123
^       ^   ^     ^
|       |   |     |
|       |   |     argument
|       |   |
|       |   option
|       |
|       command
|
global option


A "command" is the first thing on the command-line (after the program) that doesn't start with a dash. This is how many modern programs operate, for example "svn", "hg", and "git"; for the command-line "hg clone foo bar", "clone" is a "command".

"Options" are things that start with '-'. Single-character options always use a single dash, and can be grouped together ('-a -b -c' and '-abc' are equivalent). Multiple-character options must use a double dash and must be specified separately.

An option can itself take an argument. This can be of the form '-n 3' or '-n=3' or '-n3'. You can specify a single-letter option that takes an argument as part of a multiple option group ('-abc'), but it must be specified last ('-abcn5').

There are two kinds of options: "global options", which apply to the program itself (or to all commands), and "command options" (also just called "options") which are specific to each command. Options specified on the command-line before the command are global options; options specified on the command-line after the command are command options.

"Arguments" are things that don't start with a dash that come after the command. They are always positional.

You don't have to use commands if you don't want them. In that case:

• All options are global options.
• Everything on the command-line that doesn't start with a dash is an argument (a "global argument").

## API

DryParse maps Python callables to "commands", and the parameters of those callables become the options and arguments for that command. (DryParse also accepts a special callable that handles global options.)

Positional-or-keyword (POK) parameters in your Python function map to "arguments". By default, the arguments from the command-line are passed in to the POK parameters of your function as strings. POK parameters that have no default are required.

If your function takes a *args parameter, it will accept an arbitrary number of additional positional parameters.

Keyword-only (KO) parameters in your Python function map to "options". The name of the parameter is used as the name of the option. Options are always optional; therefore, if your function specifies a KO parameter with no default, a default is inferred. By default, options are boolean, and default to False, and specifying them on the command-line toggles them between True and False.

If a parameter to your callable has no default value, it is required, and therefore the option or argument it maps to is also required.

If the parameter does have a default value, then naturally it's optional. But also, the type of the default value is used to "cast" the string from the command-line before passing it in to your callable.

The parameters to your callable can optionally specify an "annotation" using Python 3's function annotation syntax. If specified, the annotation should be a set. Values in this annotation set are interpreted as follows:

• If the value is a string, and it does not start with a dash, it's documentation for this option or argument.

• If the value is a callable, it will be used to "cast" a value from the command-line to a native Python type. The callable should behave as like a scalar type constructor (str, bool, int):

• If called with no arguments, it should return a reasonable 'default' value, preferably a false value.
• If called with a single positional argument (which in DryParse will always be a string), it should return a value. The value should theoretically represent the value of the command-line parameter, converted to some other type.
• If the value is a string starting with a dash, it's a whitespace-separated list of substrings relevant to this option.

These substrings are interpreted as follows:

• If the substring begins with a single dash followed by an isalpha() character, this argument will map to that single-character option. (This is only valid for positional-only parameters.)
• If the substring begins with two dashes followed by two or more isalpha() characters, this argument will map to that option. (This is only valid for positional-only parameters.)
• If the substring is a single asterisk ('*'), this option must take a value, and you may specify it any number of times. DryParse will pass in a list containing all the option values from the command-line in order.
• If the substring consists entirely of one or two dashes, it is ignored.

## Testing

You can execute the unit test suite as follows:

% python3 -m unittest dryparse