Skip to content

Releases: Solganis/docopt2

1.2.1

Choose a tag to compare

@Solganis Solganis released this 15 Jul 16:58
f4251c0

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

  • Either matches 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

Choose a tag to compare

@Solganis Solganis released this 15 Jul 11:43
49d33ba

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-Either cap 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

Choose a tag to compare

@Solganis Solganis released this 15 Jul 04:55
dad2ddd

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-valued Literal or IntEnum now 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 in extra (--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 like 1.10 is quoted so it round-trips instead of loading back as 1.1
  • config-template: a [config:] key with an empty segment (a stray dot) is rejected, not emitted as invalid TOML
  • check_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 in Options: is left intact, so fmt stays semantics-preserving and idempotent
  • Rich --help: a clustered short like [-hso] now lists -h/-s/-o instead 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 .py or 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

Choose a tag to compare

@Solganis Solganis released this 14 Jul 05:28
1e201e5

Bug fixes

  • Repetition: matching a ... is now iterative, so prog <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 (where sys.stderr is None) or a closed stream no longer crashes with an AttributeError
  • Subcommand dispatch: run(schema=...) is refused with a TypeError naming on(..., schema=...), instead of failing deep inside

Performance

  • import docopt2 no longer pulls in dataclasses, inspect, typing_extensions, pathlib, decimal, uuid or datetime: the typed machinery builds on first use. About 11 ms off every run

Internal

  • Diagnostic, Snippet and Caret are plain classes, not dataclasses (that decorator dragged inspect into every import). repr and equality unchanged
  • Mutation testing runs weekly in CI, report-only

1.1.1

Choose a tag to compare

@Solganis Solganis released this 14 Jul 00:54
b647652

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 to True)
  • fmt: an option line is a dash and a non-space, so - fast, quick in a wrapped description keeps its commas
  • check_compat: adding a long alias to a short-only option (-v to -v --verbose) is no longer reported as a break
  • check: a malformed option line defers to the error docopt() raises, not a misleading [options] accepts nothing
  • argv_strategy: the documented recipe now shows help=False, so a usage declaring -h no longer exits the property test
  • examples/completion.py now calls docopt(), so sourcing the generated script actually offers completions

1.1.0

Choose a tag to compare

@Solganis Solganis released this 13 Jul 18:37
057ce9a

Bug fixes

  • fmt: only options: sections are re-aligned, so a prose bullet elsewhere in the doc keeps its commas
  • check: the dead-default rules fire on a multi-line usage - the multi-pattern case their description names
  • check: a redundant (add | add) inside a usage line is caught, not just the outermost alternation
  • check: -h | --help is one option under two spellings, not a redundant alternative
  • generate_examples: an invalid example is verified against the parser, not assumed (a --help or [--] usage yielded argvs it accepts)
  • generate_stub: a positional every usage line requires is typed str, not str | None
  • format_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. mkdocs fails the build on a dead anchor

1.0.2

Choose a tag to compare

@Solganis Solganis released this 13 Jul 02:28
4aa17d2

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 the date/datetime/time a TOML loader returns, still pass through as written

Features

  • Schema: a field annotated datetime.time coerces - tomllib reads at = 10:30:00 into a type no schema could name

Internal

  • The package is classified Production/Stable
  • CI: --strict-markers was 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

Choose a tag to compare

@Solganis Solganis released this 13 Jul 01:28
3eef8fc

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 and help: proposes a fix. Four messages carried the wrong label

Performance

  • Load __version__ and the tooling on first use rather than at import: import docopt2 drops 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

Choose a tag to compare

@Solganis Solganis released this 12 Jul 18:27
ae957bc

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 ARG convention in favor of <arg>,
  • Remove "unambiguous partial matching" rule (e.g. --ver matching --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, Cli base class, or pydantic model as schema= and get a typed object back, values coerced to their field types, validated Literal/Enum choices included.
  • Schema codegen - docopt2 stub generates that schema from your usage, in three styles.
  • Subcommand dispatch - Dispatch routes 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), with args.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 check flags dead defaults and unusable options before they ship.
  • Usage formatter - docopt2 fmt aligns the Options: block, the format half of a lint-and-format pair for your usage text.
  • Compatibility checking - docopt2 compat old new reports the changes that would break callers.
  • Example generation - docopt2 examples samples the argvs your usage accepts, and doubles as a shrinking Hypothesis strategy.
  • Round-trip codec - format_argv is the inverse of docopt: a parsed result back to a canonical argv.

Install

pip install docopt2  # then: from docopt2 import docopt

Start 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.