Usage linting¶
A usage message can be wrong in ways docopt accepts in silence. Describe --verbose under Options: and
forget to put it on a usage line, and your help text advertises a flag the parser then rejects with
unexpected argument. Nothing complains until a user tries it.
check (and the docopt2 check CLI) lints the grammar itself, before any argv is parsed. It never raises
and never touches parsing: it reads the Usage:, Options: and Arguments: blocks, and reports what it
finds as a list of warnings.
So you can wire it into a test or a CI step, and fail the build on a grammar mistake instead of shipping it.
From code¶
check(doc) takes the help string and returns a list[Diagnostic], every entry at "warning"
level. An empty list means the grammar is clean.
from docopt2 import check
doc = "Usage: prog\n\nOptions:\n --verbose Extra logging."
warnings = check(doc)
len(warnings) # 1
warnings[0].level # 'warning'
print(warnings[0].render())
Each entry is the same kind of diagnostic the runtime uses for parse errors.
warning.render() returns the block as text, and warning.render(color=True) produces the ANSI color shown
in the blocks below.
The list is purely informational. check does not mutate doc, and does not change how docopt() later
parses it.
Note
A usage message too malformed to parse (an unbalanced group, say) returns no warnings - that error
is not a lint, it surfaces at parse time when you call docopt(). check only reports grammar that
parses but reads wrong.
What it flags¶
check applies six rules. Each catches a construct the parser tolerates but that almost always means
the usage message says something you did not intend.
| Warning | Fires when |
|---|---|
| unusable option | an option in Options: appears in no usage line and no [options] shortcut is present to absorb it |
| dead default (option) | a [default: ...] sits on a value-taking option that every usage pattern requires, so it can never be applied |
| dead default (argument) | a [default: ...] in an Arguments: section sits on an always-required positional |
| ambiguous variadics | two ... positionals share one sequence, leaving the token split between them undefined |
| redundant alternative | two branches of one alternation group are identical, so one can never be reached |
empty [options] |
a usage line uses [options] but no options are described |
Every warning is a Diagnostic. The unusable-option case, rendered, looks like this - the caret points
at the exact token in your source:
The other rules render the same way. Each example below pairs the smallest usage that triggers the
rule with the exact warning check returns.
Unusable options¶
An option described in Options: but named on no usage line can never be set, unless a [options] shortcut
is there to accept it. Three ways to fix it:
- add the option to a usage line
- add
[options], which accepts every described option and suppresses the warning - delete the description
Dead defaults¶
A [default: ...] only applies when the element is absent, so if every usage pattern requires the element,
the default is unreachable.
It fires on options declared in Options::
doc = "Usage: prog --port=<n>\n\nOptions:\n --port=<n> Port [default: 80]."
print(check(doc)[0].render(color=True))
and, identically, on positionals documented in an Arguments: section:
doc = "Usage: prog <host>\n\nArguments:\n <host> Host name [default: localhost]."
print(check(doc)[0].render(color=True))
The fix is either to wrap the element in [ ... ] so it becomes optional (and the default can fill
in), or to drop the default.
Ambiguous variadics¶
Two variadic positionals in the same sequence have no defined boundary. Given prog a b c, docopt2 cannot
know where <a>... stops and <b>... starts.
This warning carries no caret, since the defect is the pair, not one token.
It counts each ... over a positional as one unit along a single path. So both of these are fine:
(<a> <b>)...is one repeat with a fixed interleaving, not two competing ones.- Two variadics in separate
|branches never meet on the same path.
Redundant alternatives¶
Two identical branches inside one alternation group mean one of them can never match. Usually it is a copy-paste slip or a typo in what was meant to be a distinct branch.
Empty [options]¶
A usage line uses the [options] shortcut, but there is no Options: section for it to expand, so it
matches nothing.
From the command line¶
docopt2 check <source> runs the same lint over a usage message read from <source>, which is one of:
- a
.pyfile - its module docstring is read (without importing the module), - a text file of raw usage,
-for standard input.
Warnings are written to standard error. The command exits 0 when the grammar is clean and 1 when
any warning was reported, so it drops into a pre-commit hook or a CI job as a gate:
The cli.py above is the module whose docstring is:
Usage: greet --name=<who>
Options:
--name=<who> Who to greet [default: world].
--shout Upper-case the greeting.
Tip
Because a warning sets exit status 1, docopt2 check src/app.py in CI fails the build on a
grammar defect - the same guarantee the check function gives a test,
without importing your program.
See also¶
checkin the API reference.- Diagnostics for the runtime (mismatch) counterpart.
- Usage DSL - the grammar
checklints. - Generate a stub - the other
docopt2subcommand.