Releases: Solganis/docopt2
Release list
1.2.1
Bug fixes
- Matching: a long honest argv (many repeated options + a big
<files>...glob) is no longer falsely rejected (regressed in 1.1.3)
Performance
Eithermatches greedy-first instead of building its whole outcome fan - matching is linear in the argv again
Internal
- New tests: a deep-argv differential vs vanilla, and an exponential-pattern time bound
1.2.0
Features
- Shell completion now supports Nushell - a fifth shell beside bash, zsh, fish and PowerShell. Tab completes subcommands and flags alike, each with its description
Bug fixes
- Matcher: the whole
docopt()call now shares one global match budget, so a degenerate usage that combinatorially explodes fails fast instead of hanging for minutes (the old per-Eithercap alone still compounded)
Internal
- An atheris fuzz harness exercises the public API weekly in CI, uploading a reproducer on any crash
- CodSpeed gates the hot paths against commit-over-commit performance regressions
- A failing nightly run now opens or updates a tracking issue instead of passing unnoticed
1.1.3
Bug fixes
docopt(__doc__): a flush-left usage block, as Python 3.13 leaves an inline docstring, now parses like an indented one- Shell completion: resolving candidates is now linear in the option count, so a Tab press on a many-flag
[options]CLI no longer stalls - Shell completion: a command a typed option has ruled out (it belongs to another usage line) is no longer offered
- Typed results: an
int/float-valuedLiteralorIntEnumnow coerces the argv string to the choice's type instead of rejecting it - Typed results: a leading-underscore field (from
<_x>) now binds, like it already does on a dataclass allow_extra: a surplus valued option keeps its value inextra(--log out.txt, not a bare--log)- Layered fallback: an
[env:]/[config:]value on a repeating counted flag stays an int count, not a bool config-template: a numeric default like1.10is quoted so it round-trips instead of loading back as1.1config-template: a[config:]key with an empty segment (a stray dot) is rejected, not emitted as invalid TOMLcheck_compat: a subcommand generalized to a positional is no longer a false "removed" break (the new usage accepts a superset)format_usage: a whitespace-only spacer line inOptions:is left intact, so fmt stays semantics-preserving and idempotent- Rich
--help: a clustered short like[-hso]now lists-h/-s/-oinstead of hiding them - Rich
--help: a scoped[options]lists only the options that command accepts, not another line's - Rich
--help: a flag's[default:], which docopt ignores, is no longer shown - Subcommand dispatch: a schema coercion failure now carries the usage block and the caller's
exit_code - CLI: a
.pyor text source with a UTF-8 BOM (common on Windows) is now read, not rejected
Internal
- A corpus of real-world usage messages drives every public entry point in the suite
- New property tests pin two invariants: every entry point raises only its own errors, and completion stays linear in the option count
1.1.2
Bug fixes
- Repetition: matching a
...is now iterative, soprog <files>...over a few thousand files parses instead of exhausting the stack - Diagnostics: each caret is drawn under its own source line (and clamped to the line end), not all under the first
- Error output: a parse error under
pythonw(wheresys.stderr is None) or a closed stream no longer crashes with anAttributeError - Subcommand dispatch:
run(schema=...)is refused with aTypeErrornamingon(..., schema=...), instead of failing deep inside
Performance
import docopt2no longer pulls indataclasses,inspect,typing_extensions,pathlib,decimal,uuidordatetime: the typed machinery builds on first use. About 11 ms off every run
Internal
Diagnostic,SnippetandCaretare plain classes, not dataclasses (that decorator draggedinspectinto every import).reprand equality unchanged- Mutation testing runs weekly in CI, report-only
1.1.1
Bug fixes
format_argv: an[env:]/[config:]flag read as off is no longer emitted, so the call returns an argv instead of raising (its name would re-parse toTrue)fmt: an option line is a dash and a non-space, so- fast, quickin a wrapped description keeps its commascheck_compat: adding a long alias to a short-only option (-vto-v --verbose) is no longer reported as a breakcheck: a malformed option line defers to the errordocopt()raises, not a misleading[options] accepts nothingargv_strategy: the documented recipe now showshelp=False, so a usage declaring-hno longer exits the property testexamples/completion.pynow callsdocopt(), so sourcing the generated script actually offers completions
1.1.0
Bug fixes
fmt: onlyoptions:sections are re-aligned, so a prose bullet elsewhere in the doc keeps its commascheck: the dead-default rules fire on a multi-line usage - the multi-pattern case their description namescheck: a redundant(add | add)inside a usage line is caught, not just the outermost alternationcheck:-h | --helpis one option under two spellings, not a redundant alternativegenerate_examples: an invalid example is verified against the parser, not assumed (a--helpor[--]usage yielded argvs it accepts)generate_stub: a positional every usage line requires is typedstr, notstr | Noneformat_argv: an env- or config-resolved value is written into the argv, so a persisted command reproduces the run without that environment- Diagnostics: a single-line usage carets its unmet element - the caret was gated off below two usage lines
Documentation
- The design-boundaries page no longer denies
help_style="rich": the boundary is against a second source of truth, not presentation - The coercion table lists every type the code coerces, held to the code by a test
- The guides are grouped by what they act on.
mkdocsfails the build on a dead anchor
1.0.2
Bug fixes
- Config: a key that lands on a table, a list or an opaque object is an error with a caret and the leaf keys that would work, not
str()of its repr - Config:
<object object at 0x...>can no longer reach an option. Scalars, and thedate/datetime/timea TOML loader returns, still pass through as written
Features
- Schema: a field annotated
datetime.timecoerces -tomllibreadsat = 10:30:00into a type no schema could name
Internal
- The package is classified
Production/Stable - CI:
--strict-markerswas dropped on every test cell but one, so a mistyped marker went unnoticed - CI: a stale lockfile is an error, not a silent re-resolve
- The config-template round-trip runs on the 3.10 floor too, so no cell skips a test
1.0.1
Bug fixes
- Closest usage line: caret the required group (
(up|down)), not the element after it - Closest usage line: fire on a partial subcommand (
git remote) and on a missing repetition (git add) - bash: glue back the words bash splits at
COMP_WORDBREAKS, so completion survives--opt=value - zsh: complete on the first Tab when autoloaded from
$fpath - zsh: drop the stray
--beside candidates that carry no description - PowerShell: honour
$cursorPosition, unquote tokens, and treat a*in the typed word as a literal - Diagnostics:
note:is context andhelp:proposes a fix. Four messages carried the wrong label
Performance
- Load
__version__and the tooling on first use rather than at import:import docopt2drops from ~38.7 ms to ~19.3 ms
Testing
- CI drives bash, zsh and fish in a pseudo-terminal and presses a real Tab. The check fails on the unfixed scripts
1.0.0
docopt2 is a typed, zero-dependency, maintained successor to docopt. The usage message is the parser: you write the Usage: and Options: text you'd write anyway, and docopt2 parses the command line against it. Every argument vector the original docopt accepts, docopt2 accepts identically, so switching over is a one-line import change. A differential property test against a vendored copy of the original keeps that promise honest.
This is the first public release. It is also, in a specific sense, a release that was promised nine years ago and never shipped.
"That last 1%"
docopt appeared in 2012, was widely loved for turning a help message into a parser, and then went quiet - the last release, 0.6.2, is from 2014. In 2017, on the issue thread "Is docopt maintained?", the original author Vladimir Keleshev laid out exactly what he wanted to finish first:
I really want to go that extra mile, that 1 last percent, before releasing version 1.0.0. I'm really
aiming for 1.0.0 to be the last Docopt version. That last 1% will probably include:
- Formal grammar,
- Gorgeous, detailed error-messages, both for argv parsing and docstring parsing,
- More powerful matching with non-finite automaton, (will handle cases like
<args>... <last>),- Remove
ARGconvention in favor of<arg>,- Remove "unambiguous partial matching" rule (e.g.
--vermatching--version),- Handle
--specially even if it is absent from the docstring,- Disallow dropping
=for options with arguments in docstring (but not when parsing argv).As you might have noticed, in 99% of cases the above changes do not matter, so you can happily
continue using docopt as it is.
That 1.0.0 never came - the docopt.ml experiment stalled, and docopt stayed at 0.6.2.
docopt2 wasn't built from that list. It was built to fix docopt's rough edges and make it typed and maintained - and only then did the result turn out to line up with his comment, almost point for point. That convergence is the frame for this release: the docopt 1.0 Keleshev sketched, reached by another road - and then some.
The roadmap, delivered
| Keleshev's goal | docopt2 1.0.0 |
|---|---|
| 1. Formal grammar | Done. The usage DSL - docstring structure, the Usage: pattern language, the Options: section, argv - is specified and snapshot-tested. See Usage DSL. |
| 2. Gorgeous, detailed errors | Done. Every error path - a mismatch, an unknown or mistyped option, a value that won't coerce, a malformed usage - renders in one rustc-style shape, in color on a terminal. See Diagnostics. |
3. Powerful matching (<args>... <last>) |
Done. The matcher is a greedy-first backtracking generator, so patterns the old greedy matcher starved (<src>... <dest>, [<a>] <b>, [-i X...] <out>) now resolve. See Repetition. |
4. Remove the ARG convention |
Declined by default. Erroring on NAME-style positionals helps no one. ARG stays a valid synonym for <arg>. See Positional arguments. |
5. Remove partial matching (--ver -> --version) |
Opt-in. allow_abbrev=False turns off long-option de-abbreviation (argparse's spelling). The default stays on, for compatibility. See allow_abbrev. |
6. Handle -- even if absent |
Done. -- is dropped as the POSIX separator instead of leaking in as a positional value. See End of options. |
7. Disallow --opt <x> (no =) |
Not needed. docopt2 already parses --opt <x> in the usage correctly, so forbidding it would break a form that works. See Options with a value. |
Goals 4 and 5 are the breaking ones - the two that would change what existing usages accept. Keleshev himself flagged that "in 99% of cases the above changes do not matter," so docopt2's first commitment is drop-in compatibility: it ships the non-breaking goals, keeps ARG (#4) as a synonym, and makes removing partial matching (#5) an opt-in flag rather than a forced break. You get the 1.0 that was designed, without the migration it would have cost.
Everything docopt never had
docopt handed you a dict of strings and reprinted the whole usage on any mistake.
docopt2 wraps the same parser in a modern toolkit - every piece opt-in, layered around the parser rather than baked into it:
- Diagnostics that point at the mistake - rustc-style, and in color on a terminal: a caret under the offending token in your argv, cross-referenced to the exact spot in the usage that rejected it, a "did you mean" on a mistyped option, and the closest usage line on a near-miss. This is Keleshev's goal 2, shipped in full - and the thing you notice first.
- Typed results - pass a dataclass,
TypedDict,Clibase class, or pydantic model asschema=and get a typed object back, values coerced to their field types, validatedLiteral/Enumchoices included. - Schema codegen -
docopt2 stubgenerates that schema from your usage, in three styles. - Subcommand dispatch -
Dispatchroutes each command path to its own handler. - Shell completion - context-aware completion for bash, zsh, fish, and PowerShell, derived from the grammar.
- Layered configuration -
[env: VAR]and[config: key]fallbacks (CLI > env > config > default), withargs.source()reporting where each value actually came from. - Self-documenting
--help-help_style="rich"renders an aligned, colored help screen that shows each value's resolution chain. - Usage linter -
docopt2 checkflags dead defaults and unusable options before they ship. - Usage formatter -
docopt2 fmtaligns theOptions:block, the format half of a lint-and-format pair for your usage text. - Compatibility checking -
docopt2 compat old newreports the changes that would break callers. - Example generation -
docopt2 examplessamples the argvs your usage accepts, and doubles as a shrinking Hypothesis strategy. - Round-trip codec -
format_argvis the inverse ofdocopt: a parsed result back to a canonical argv.
Install
pip install docopt2 # then: from docopt2 import docoptStart with Getting started. The full documentation is at solganis.github.io/docopt2.
Thanks
To Vladimir Keleshev for docopt, and for the 2017 comment that put into words what its 1.0 should be. docopt2 got there by its own road, but his sketch is the map it turned out to match. And to the docopt-ng contributors, from whom two concrete improvements are ported (long-option spellcheck, and the collected/left parse view on DocoptExit).
Full attribution is in NOTICE.