How Docopt Parses CLI Arguments in Python
The Docopt library fundamentally rethinks command-line interface
(CLI) design by using the help message itself as the formal interface
specification. Instead of requiring developers to write procedural code
to define flags, options, and positional arguments—which then generates
a help screen—Docopt parses a POSIX-compliant docstring using regular
expressions and a formal grammar parser. It converts this human-readable
text into an internal abstract syntax tree (AST), matches runtime
arguments (sys.argv) against that tree, and outputs a clean
Python dictionary containing the parsed values.
The Inverted CLI Paradigm
In traditional CLI libraries such as Python’s built-in
argparse or click, the developer writes code
to define flags, types, and defaults, and the library derives the help
string. Docopt reverses this flow:
- You write the interface definition inside the module's docstring using standard POSIX/GNU usage conventions.
- Docopt analyzes the text structure and tokenizes the usage patterns and option descriptions.
- Docopt compiles these tokens into formal pattern objects.
- At runtime, input arguments are matched against these pattern objects to extract values.
1. Section Identification and Text Preprocessing
When docopt(__doc__) is invoked, the library first
extracts the docstring and scans it for key standardized sections using
regular expressions:
Usage:pattern: Docopt looks for the keywordusage:(case-insensitive) and reads everything up to the next empty line. This block defines the exact structural grammar of commands, subcommands, options, and positional arguments.Options:block: Docopt looks for sections detailing option flags (e.g.,-h, --help,-o <file>, --output=<file>). This section is parsed to determine default values, aliases, and whether an option expects an argument.
2. Formal Grammar and Tokenization
Docopt uses a specialized mini-parser to process the
Usage: section. It tokenizes the syntax elements based on
standard conventions:
<argument>orUPPERCASE: Positional arguments.-oor--option: Flags and options.[ ... ]: Optional elements.( ... ): Required elements.|: Mutual exclusion (either/or)....: Repeating elements (one or more).
Each token is categorized and transformed into an internal class
representation. Docopt defines structural classes such as
Required, Optional, Either, and
OneOrMore, alongside leaf classes like Option,
Argument, and Command.
3. Building the Abstract Syntax Tree (AST)
Docopt constructs an AST that models the legal combinations of arguments:
- Mutual exclusions (
|) becomeEithernodes containing alternative branches. - Brackets (
[...]) becomeOptionalnodes. - Parentheses (
(...)) becomeRequirednodes.
Simultaneously, the Options: section is parsed to enrich
the AST. For example, if the usage pattern contains -f, but
the options section defines -f, --file FILE, Docopt
recognizes that -f requires a value (FILE) and
that --file is an alias for the same parameter.
4. Matching
sys.argv Against the Tree
Once the pattern tree is compiled, Docopt parses the runtime
arguments provided via sys.argv[1:]:
- Argument Normalization: Short options combined
together (like
-xzvf) are unpacked, and--option=valuesyntax is separated into key-value pairs. - Backtracking Match Algorithm: Docopt evaluates the
input tokens against the AST. It attempts to traverse the branches of
the tree, consuming tokens that match the expected leaf nodes
(
Option,Argument, orCommand). If a branch fails (e.g., a required positional argument is missing or an unknown flag is encountered), it backtracks and evaluates alternate branches. - Validation: If the tokens cannot satisfy any valid path through the AST, or if unparsed tokens remain after reaching a terminal state, Docopt exits and prints the usage message.
5. Returning the Result Dictionary
If the runtime tokens successfully match a path through the AST, Docopt creates and returns a standard Python dictionary:
"""Naval Fate.
Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> [--speed=<kn>]
naval_fate -h | --help
Options:
-h --help Show this screen.
--speed=<kn> Speed in knots [default: 10].
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__)
print(arguments)Running
python naval_fate.py ship Enterprise move 10 20 --speed=15
outputs:
{
'--help': False,
'--speed': '15',
'<name>': ['Enterprise'],
'<x>': '10',
'<y>': '20',
'move': True,
'new': False,
'ship': True
}Every element from the usage patterns becomes a dictionary key,
mapped to booleans (for flags and commands), strings (for single
values), lists (for repeating parameters), or None (for
unspecified optional parameters).