docopt2 1.2.1__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/ci.yml +33 -9
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/codeql.yml +3 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/codspeed.yml +5 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/docs.yml +6 -4
- docopt2-1.3.0/.github/workflows/fuzz.yml +80 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/mutation.yml +4 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/nightly.yml +4 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/publish.yml +7 -7
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/scorecard.yml +3 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/workflows/zizmor.yml +2 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/CONTRIBUTING.md +13 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/PKG-INFO +23 -15
- {docopt2-1.2.1 → docopt2-1.3.0}/README.md +21 -13
- docopt2-1.3.0/docs/concepts/design-boundaries.md +212 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/getting-started.md +51 -39
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/check.md +138 -24
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/compat.md +25 -15
- docopt2-1.3.0/docs/guides/completion.md +336 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/diagnostics.md +120 -42
- docopt2-1.3.0/docs/guides/dispatch.md +349 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/examples.md +34 -24
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/fmt.md +24 -14
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/help.md +28 -17
- docopt2-1.3.0/docs/guides/round-trip.md +104 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/stub.md +26 -17
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/typed-results.md +99 -38
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/guides/usage-dsl.md +73 -41
- docopt2-1.3.0/docs/index.md +173 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/check.md +2 -1
- docopt2-1.3.0/docs/reference/cli.md +9 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/completion.md +2 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/dispatch.md +2 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/docopt.md +1 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/exceptions.md +5 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/grammar.md +7 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/stub.md +1 -1
- docopt2-1.3.0/fuzz/corpus/argument_default.txt +4 -0
- docopt2-1.3.0/fuzz/corpus/commands_and_fallbacks.txt +6 -0
- docopt2-1.3.0/fuzz/corpus/counted_and_repeated.txt +1 -0
- docopt2-1.3.0/fuzz/corpus/either_and_separator.txt +2 -0
- docopt2-1.3.0/fuzz/corpus/nested_commands.txt +6 -0
- docopt2-1.3.0/fuzz/corpus/options_and_defaults.txt +5 -0
- docopt2-1.3.0/fuzz/corpus/repeated_option.txt +4 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/fuzz/fuzz_docopt.py +43 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/pyproject.toml +21 -9
- docopt2-1.3.0/pyrefly.toml +9 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/__init__.py +3 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_compat.py +8 -2
- docopt2-1.3.0/src/docopt2/_completion.py +984 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_core.py +328 -107
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_diagnostics.py +33 -15
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_errors.py +26 -4
- docopt2-1.3.0/src/docopt2/_fmt.py +73 -0
- docopt2-1.3.0/src/docopt2/_format.py +250 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_generate.py +21 -14
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_help.py +1 -1
- docopt2-1.3.0/src/docopt2/_lint.py +430 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_parser.py +239 -94
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_spellcheck.py +8 -5
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/_stub.py +33 -16
- docopt2-1.3.0/src/docopt2/_typed.py +699 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/hypothesis.py +1 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/conftest.py +1 -2
- docopt2-1.3.0/tests/golden/completion_bash.txt +40 -0
- docopt2-1.3.0/tests/golden/completion_fish.txt +14 -0
- docopt2-1.3.0/tests/golden/completion_nushell.txt +19 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/golden/completion_powershell.txt +12 -2
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/golden/completion_zsh.txt +9 -2
- docopt2-1.3.0/tests/test_caret_columns.py +112 -0
- docopt2-1.3.0/tests/test_caret_places.py +221 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_cli.py +8 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_coercion_diagnostics.py +41 -3
- docopt2-1.3.0/tests/test_complete_files.py +379 -0
- docopt2-1.3.0/tests/test_complete_reading.py +420 -0
- docopt2-1.3.0/tests/test_complete_refused.py +202 -0
- docopt2-1.3.0/tests/test_complete_values.py +541 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_completion_vs_docopt.py +52 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_config.py +42 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_config_template.py +8 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_dispatch_and_completion.py +81 -22
- docopt2-1.3.0/tests/test_dispatch_table.py +516 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_divergences.py +17 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_docs_blocks.py +48 -4
- docopt2-1.3.0/tests/test_docs_fences.py +123 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_env.py +31 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_fmt.py +90 -1
- docopt2-1.3.0/tests/test_format.py +358 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_import_cost.py +74 -2
- docopt2-1.3.0/tests/test_initvar_schema.py +98 -0
- docopt2-1.3.0/tests/test_initvar_schema_strings.py +35 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_lint.py +126 -22
- docopt2-1.3.0/tests/test_matcher_pieces.py +89 -0
- docopt2-1.3.0/tests/test_mistyped_command.py +337 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_package.py +7 -0
- docopt2-1.3.0/tests/test_property_caret_places.py +162 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_property_config_template.py +53 -1
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_property_differential.py +47 -3
- docopt2-1.3.0/tests/test_property_schema.py +275 -0
- docopt2-1.3.0/tests/test_refused_value_report.py +537 -0
- docopt2-1.3.0/tests/test_repeated_annotation.py +99 -0
- docopt2-1.3.0/tests/test_robustness.py +460 -0
- docopt2-1.3.0/tests/test_scalar_table.py +112 -0
- docopt2-1.3.0/tests/test_schema_edges.py +556 -0
- docopt2-1.3.0/tests/test_schema_fit.py +635 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_spellcheck.py +6 -3
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_typed_api.py +69 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/typing_surface/check_typed.py +17 -2
- docopt2-1.3.0/tools/shell_check.py +575 -0
- docopt2-1.3.0/uv.lock +2180 -0
- docopt2-1.2.1/.github/workflows/fuzz.yml +0 -50
- docopt2-1.2.1/docs/concepts/design-boundaries.md +0 -166
- docopt2-1.2.1/docs/guides/completion.md +0 -147
- docopt2-1.2.1/docs/guides/dispatch.md +0 -167
- docopt2-1.2.1/docs/guides/round-trip.md +0 -76
- docopt2-1.2.1/docs/index.md +0 -171
- docopt2-1.2.1/docs/reference/cli.md +0 -9
- docopt2-1.2.1/src/docopt2/_completion.py +0 -401
- docopt2-1.2.1/src/docopt2/_fmt.py +0 -36
- docopt2-1.2.1/src/docopt2/_format.py +0 -187
- docopt2-1.2.1/src/docopt2/_lint.py +0 -198
- docopt2-1.2.1/src/docopt2/_typed.py +0 -361
- docopt2-1.2.1/tests/golden/completion_bash.txt +0 -17
- docopt2-1.2.1/tests/golden/completion_fish.txt +0 -5
- docopt2-1.2.1/tests/golden/completion_nushell.txt +0 -12
- docopt2-1.2.1/tests/test_format.py +0 -175
- docopt2-1.2.1/tests/test_robustness.py +0 -189
- docopt2-1.2.1/tools/shell_check.py +0 -275
- docopt2-1.2.1/uv.lock +0 -1760
- {docopt2-1.2.1 → docopt2-1.3.0}/.codecov.yml +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/.gitattributes +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/.github/dependabot.yml +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/.gitignore +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/LICENSE +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/NOTICE +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/SECURITY.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/benchmarks/test_perf.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/check.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/check.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/coercion.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/coercion.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/diagnostic.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/diagnostic.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/logo-dark.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/logo.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/rich-help.png +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/assets/rich-help.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/compat.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/config-templates.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/examples.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/fmt.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/reference/overview.md +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/docs/stylesheets/extra.css +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/cli.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/completion.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/dispatch.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/layered.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/naval_fate.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/rich_help.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/round_trip.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/examples/typed.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/fuzz/docopt.dict +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/mkdocs.yml +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/__main__.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/src/docopt2/py.typed +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/_strategies.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/_vendor/docopt_original.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/corpus/awkward_but_legal.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/corpus/layered_config.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/corpus/many_flags.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/corpus/naval_fate.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/corpus/nested_subcommands.txt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_choices.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_compat.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_core_extra.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_diagnostics.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_docopt_original.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_examples.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_extended_features.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_generate.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_getting_started.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_grammar_examples.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_help.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_hypothesis.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_language_agnostic.docopt +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_near_miss.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_parse_tree.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_property_coercion.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_property_config.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_property_extended.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_pydantic.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_readme.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_screenshots.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_source.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tests/test_stub.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tools/mutation_report.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tools/regolden.py +0 -0
- {docopt2-1.2.1 → docopt2-1.3.0}/tools/screenshots.py +0 -0
|
@@ -33,15 +33,22 @@ jobs:
|
|
|
33
33
|
- os: macos-latest
|
|
34
34
|
python-version: "3.14"
|
|
35
35
|
steps:
|
|
36
|
-
- uses: actions/checkout@
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
37
|
with:
|
|
38
38
|
persist-credentials: false
|
|
39
39
|
|
|
40
40
|
- name: Install uv
|
|
41
|
-
|
|
41
|
+
# Turned off on every setup-uv step in this repo, because `enable-cache` defaults to
|
|
42
|
+
# "auto", which means ON for hosted runners. It buys nothing here: `uv sync --locked`
|
|
43
|
+
# takes about 2s in every job, so a restore/save cycle costs the same or more. It also
|
|
44
|
+
# costs posture, since restoring a previous run's artifacts is the supply-chain surface
|
|
45
|
+
# zizmor's cache-poisoning rule is about, and this repo SHA-pins its actions.
|
|
46
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
47
|
+
with:
|
|
48
|
+
enable-cache: false
|
|
42
49
|
|
|
43
50
|
- name: Set up Python ${{ matrix.python-version }}
|
|
44
|
-
uses: actions/setup-python@
|
|
51
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
45
52
|
with:
|
|
46
53
|
python-version: ${{ matrix.python-version }}
|
|
47
54
|
allow-prereleases: true
|
|
@@ -62,7 +69,7 @@ jobs:
|
|
|
62
69
|
|
|
63
70
|
- name: Upload coverage to Codecov
|
|
64
71
|
if: matrix.coverage
|
|
65
|
-
uses: codecov/codecov-action@
|
|
72
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
66
73
|
with:
|
|
67
74
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
68
75
|
files: coverage.xml
|
|
@@ -70,15 +77,17 @@ jobs:
|
|
|
70
77
|
lint:
|
|
71
78
|
runs-on: ubuntu-latest
|
|
72
79
|
steps:
|
|
73
|
-
- uses: actions/checkout@
|
|
80
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
74
81
|
with:
|
|
75
82
|
persist-credentials: false
|
|
76
83
|
|
|
77
84
|
- name: Install uv
|
|
78
|
-
uses: astral-sh/setup-uv@
|
|
85
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
86
|
+
with:
|
|
87
|
+
enable-cache: false
|
|
79
88
|
|
|
80
89
|
- name: Set up Python
|
|
81
|
-
uses: actions/setup-python@
|
|
90
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
82
91
|
with:
|
|
83
92
|
python-version: "3.14"
|
|
84
93
|
|
|
@@ -106,6 +115,19 @@ jobs:
|
|
|
106
115
|
- name: Type check (pyright, floor target 3.10)
|
|
107
116
|
run: uv run pyright --pythonversion 3.10 src/docopt2
|
|
108
117
|
|
|
118
|
+
- name: Type check (pyrefly)
|
|
119
|
+
run: uv run pyrefly check
|
|
120
|
+
|
|
121
|
+
- name: Type check (pyrefly, floor target 3.10)
|
|
122
|
+
run: uv run pyrefly check --python-version 3.10
|
|
123
|
+
|
|
124
|
+
- name: Type coverage (pyrefly, every annotation in the package)
|
|
125
|
+
run: uv run pyrefly coverage check src/docopt2
|
|
126
|
+
|
|
127
|
+
# --ignoreexternal: without it one symbol is short, `argv_strategy`, on hypothesis's own `SearchStrategy`
|
|
128
|
+
- name: Type completeness (pyright, the exported surface)
|
|
129
|
+
run: uv run pyright --verifytypes docopt2 --ignoreexternal
|
|
130
|
+
|
|
109
131
|
# The unit tests pin what the generated completion SCRIPTS say; they never run them, and that is exactly
|
|
110
132
|
# where a completion bug hides. This drives real shells through a pty and a real Tab keypress, and holds
|
|
111
133
|
# what they offer against the grammar. It is known to fail on an unfixed script, so it guards rather than
|
|
@@ -117,12 +139,14 @@ jobs:
|
|
|
117
139
|
matrix:
|
|
118
140
|
os: [ubuntu-latest, windows-latest]
|
|
119
141
|
steps:
|
|
120
|
-
- uses: actions/checkout@
|
|
142
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
121
143
|
with:
|
|
122
144
|
persist-credentials: false
|
|
123
145
|
|
|
124
146
|
- name: Install uv
|
|
125
|
-
uses: astral-sh/setup-uv@
|
|
147
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
148
|
+
with:
|
|
149
|
+
enable-cache: false
|
|
126
150
|
|
|
127
151
|
- name: Install zsh and fish
|
|
128
152
|
if: runner.os == 'Linux'
|
|
@@ -17,10 +17,10 @@ jobs:
|
|
|
17
17
|
permissions:
|
|
18
18
|
security-events: write
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
21
|
with:
|
|
22
22
|
persist-credentials: false
|
|
23
|
-
- uses: github/codeql-action/init@
|
|
23
|
+
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
24
24
|
with:
|
|
25
25
|
languages: python
|
|
26
|
-
- uses: github/codeql-action/analyze@
|
|
26
|
+
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
@@ -26,15 +26,17 @@ jobs:
|
|
|
26
26
|
contents: read
|
|
27
27
|
id-token: write
|
|
28
28
|
steps:
|
|
29
|
-
- uses: actions/checkout@
|
|
29
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
30
30
|
with:
|
|
31
31
|
persist-credentials: false
|
|
32
32
|
|
|
33
33
|
- name: Install uv
|
|
34
|
-
uses: astral-sh/setup-uv@
|
|
34
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: false
|
|
35
37
|
|
|
36
38
|
- name: Run benchmarks under CodSpeed
|
|
37
|
-
uses: CodSpeedHQ/action@
|
|
39
|
+
uses: CodSpeedHQ/action@c4fd08a3a159bd0cc208da1e0edf32b8c47d75e5 # v5.4.0
|
|
38
40
|
with:
|
|
39
41
|
mode: simulation
|
|
40
42
|
run: uv run --locked --group benchmark pytest benchmarks/ --codspeed --no-cov
|
|
@@ -17,15 +17,17 @@ jobs:
|
|
|
17
17
|
build:
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
21
|
with:
|
|
22
22
|
persist-credentials: false
|
|
23
23
|
|
|
24
24
|
- name: Install uv
|
|
25
|
-
uses: astral-sh/setup-uv@
|
|
25
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: false
|
|
26
28
|
|
|
27
29
|
- name: Set up Python
|
|
28
|
-
uses: actions/setup-python@
|
|
30
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
29
31
|
with:
|
|
30
32
|
python-version: "3.14"
|
|
31
33
|
|
|
@@ -54,4 +56,4 @@ jobs:
|
|
|
54
56
|
steps:
|
|
55
57
|
- name: Deploy to GitHub Pages
|
|
56
58
|
id: deployment
|
|
57
|
-
uses: actions/deploy-pages@
|
|
59
|
+
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: Fuzz
|
|
2
|
+
|
|
3
|
+
# Coverage-guided fuzzing of the public API with atheris, off the PR path. The invariant is the one
|
|
4
|
+
# tests/test_robustness.py states and Hypothesis checks on grammar-shaped input: no entry point may raise
|
|
5
|
+
# anything but DocoptExit / DocoptLanguageError for any input. The Nightly job explores well-formed usage
|
|
6
|
+
# with Hypothesis; this reaches the malformed byte shapes it cannot, under coverage feedback, and a
|
|
7
|
+
# per-input -timeout turns a runaway (a pathological slowdown or an outright hang) into a finding. Unlike
|
|
8
|
+
# Mutation (report-only), a crash or timeout here is a real bug: the job fails and uploads the reproducer,
|
|
9
|
+
# which becomes a committed regression test.
|
|
10
|
+
#
|
|
11
|
+
# Linux only: atheris ships no Windows wheel. Pinned to Python 3.12, which has a prebuilt manylinux wheel.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: "41 4 * * 1"
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
actions: read # the corpus of the last run is an artifact of it
|
|
21
|
+
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ${{ github.workflow }}
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
fuzz:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 25
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
|
|
35
|
+
- name: Install uv
|
|
36
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: false
|
|
39
|
+
|
|
40
|
+
# An artifact and not a cache: a cache is evicted after seven days unread, and this job comes back on the eighth.
|
|
41
|
+
- name: Start from the corpus of the last run
|
|
42
|
+
continue-on-error: true # the first run has none
|
|
43
|
+
env:
|
|
44
|
+
GH_TOKEN: ${{ github.token }}
|
|
45
|
+
run: |
|
|
46
|
+
last=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow fuzz.yml --status success --limit 1 --json databaseId --jq '.[0].databaseId')
|
|
47
|
+
gh run download "$last" --repo "$GITHUB_REPOSITORY" --name fuzz-corpus --dir corpus
|
|
48
|
+
|
|
49
|
+
# shell: bash turns pipefail on, so a finding fails the step through the tee.
|
|
50
|
+
- name: Fuzz the public API for ten minutes
|
|
51
|
+
shell: bash
|
|
52
|
+
run: |
|
|
53
|
+
mkdir -p fuzz-findings corpus
|
|
54
|
+
cp fuzz/corpus/* corpus/
|
|
55
|
+
uv run --locked --no-dev --python 3.12 --with atheris \
|
|
56
|
+
python fuzz/fuzz_docopt.py corpus -dict=fuzz/docopt.dict -max_len=512 \
|
|
57
|
+
-max_total_time=600 -timeout=30 -rss_limit_mb=4096 -artifact_prefix=fuzz-findings/ \
|
|
58
|
+
2>&1 | tee fuzz.log
|
|
59
|
+
|
|
60
|
+
# Run with no dictionary, this job covered 76 edges a week and was green: no input ever spelled "usage:".
|
|
61
|
+
- name: Hold the run to a coverage floor
|
|
62
|
+
run: |
|
|
63
|
+
covered=$(grep -oE 'DONE +cov: [0-9]+' fuzz.log | grep -oE '[0-9]+$')
|
|
64
|
+
echo "edges covered: ${covered:-none}"
|
|
65
|
+
test "${covered:-0}" -ge 1000
|
|
66
|
+
|
|
67
|
+
# Whole: libFuzzer writes an input only where it covers something new, and its -merge does not run under atheris.
|
|
68
|
+
- name: Upload the corpus for the next run
|
|
69
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
70
|
+
with:
|
|
71
|
+
name: fuzz-corpus
|
|
72
|
+
path: corpus/
|
|
73
|
+
|
|
74
|
+
- name: Upload any crash or timeout reproducer
|
|
75
|
+
if: failure()
|
|
76
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
77
|
+
with:
|
|
78
|
+
name: fuzz-findings
|
|
79
|
+
path: fuzz-findings/
|
|
80
|
+
if-no-files-found: ignore
|
|
@@ -34,12 +34,14 @@ jobs:
|
|
|
34
34
|
# more, and those 3 come back as survivors to look at. It over-reports suspects, never hides one.
|
|
35
35
|
PYTEST_ADDOPTS: "--no-cov --hypothesis-profile=mutation"
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
38
38
|
with:
|
|
39
39
|
persist-credentials: false
|
|
40
40
|
|
|
41
41
|
- name: Install uv
|
|
42
|
-
uses: astral-sh/setup-uv@
|
|
42
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
43
|
+
with:
|
|
44
|
+
enable-cache: false
|
|
43
45
|
|
|
44
46
|
- name: Baseline suite must pass before any mutant is judged
|
|
45
47
|
# A red baseline would make mutmut score every mutant killed, and the report would read as a
|
|
@@ -21,12 +21,14 @@ jobs:
|
|
|
21
21
|
explore:
|
|
22
22
|
runs-on: ubuntu-latest
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
25
|
with:
|
|
26
26
|
persist-credentials: false
|
|
27
27
|
|
|
28
28
|
- name: Install uv
|
|
29
|
-
uses: astral-sh/setup-uv@
|
|
29
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: false
|
|
30
32
|
|
|
31
33
|
- name: Property tests, wide and randomised
|
|
32
34
|
run: uv run --locked pytest --hypothesis-profile=explore -q --no-cov
|
|
@@ -16,17 +16,17 @@ jobs:
|
|
|
16
16
|
contents: write # upload artifacts to the GitHub Release
|
|
17
17
|
attestations: write # actions/attest-build-provenance
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
20
|
with:
|
|
21
21
|
persist-credentials: false
|
|
22
22
|
|
|
23
23
|
- name: Install uv
|
|
24
|
-
uses: astral-sh/setup-uv@
|
|
24
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
25
25
|
with:
|
|
26
26
|
enable-cache: false
|
|
27
27
|
|
|
28
28
|
- name: Set up Python
|
|
29
|
-
uses: actions/setup-python@
|
|
29
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
30
30
|
with:
|
|
31
31
|
python-version: "3.15"
|
|
32
32
|
allow-prereleases: true
|
|
@@ -36,13 +36,13 @@ jobs:
|
|
|
36
36
|
|
|
37
37
|
- name: Attest wheel provenance
|
|
38
38
|
id: attest-wheel
|
|
39
|
-
uses: actions/attest-build-provenance@
|
|
39
|
+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
40
40
|
with:
|
|
41
41
|
subject-path: dist/*.whl
|
|
42
42
|
|
|
43
43
|
- name: Attest sdist provenance
|
|
44
44
|
id: attest-sdist
|
|
45
|
-
uses: actions/attest-build-provenance@
|
|
45
|
+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
46
46
|
with:
|
|
47
47
|
subject-path: dist/*.tar.gz
|
|
48
48
|
|
|
@@ -59,13 +59,13 @@ jobs:
|
|
|
59
59
|
test -s provenance/provenance.intoto.jsonl
|
|
60
60
|
|
|
61
61
|
- name: Install syft
|
|
62
|
-
uses: anchore/sbom-action/download-syft@
|
|
62
|
+
uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
|
|
63
63
|
|
|
64
64
|
- name: Generate SBOM (CycloneDX)
|
|
65
65
|
run: syft scan dir:dist -o "cyclonedx-json=provenance/docopt2.sbom.cdx.json"
|
|
66
66
|
|
|
67
67
|
- name: Publish to PyPI (Trusted Publishing + PEP 740 attestations)
|
|
68
|
-
uses: pypa/gh-action-pypi-publish@
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
69
69
|
|
|
70
70
|
- name: Upload artifacts and provenance to the GitHub Release
|
|
71
71
|
env:
|
|
@@ -16,10 +16,10 @@ jobs:
|
|
|
16
16
|
security-events: write
|
|
17
17
|
id-token: write
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
20
|
with:
|
|
21
21
|
persist-credentials: false
|
|
22
|
-
- uses: ossf/scorecard-action@
|
|
22
|
+
- uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
|
23
23
|
with:
|
|
24
24
|
results_file: results.sarif
|
|
25
25
|
results_format: sarif
|
|
@@ -29,6 +29,6 @@ jobs:
|
|
|
29
29
|
name: SARIF file
|
|
30
30
|
path: results.sarif
|
|
31
31
|
retention-days: 5
|
|
32
|
-
- uses: github/codeql-action/upload-sarif@
|
|
32
|
+
- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
33
33
|
with:
|
|
34
34
|
sarif_file: results.sarif
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
permissions:
|
|
20
20
|
security-events: write
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
23
|
with:
|
|
24
24
|
persist-credentials: false
|
|
25
|
-
- uses: zizmorcore/zizmor-action@
|
|
25
|
+
- uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
|
|
@@ -39,8 +39,19 @@ uv run ty check src/docopt2
|
|
|
39
39
|
uv run pytest
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
CI requires 100% code coverage (line and branch)
|
|
43
|
-
`mypy --strict` and `
|
|
42
|
+
CI requires 100% code coverage (line and branch), and a PR that drops below it fails. It also runs
|
|
43
|
+
`mypy --strict`, `pyright` and `pyrefly` on the typed surface, across Python 3.10 - 3.15.
|
|
44
|
+
|
|
45
|
+
Type annotations are held at 100% too. Both checks need the extra checkers, installed with
|
|
46
|
+
`uv sync --group typecheck`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# every annotation in the package
|
|
50
|
+
uv run pyrefly coverage check src/docopt2
|
|
51
|
+
|
|
52
|
+
# every exported symbol has a type a checker can name
|
|
53
|
+
uv run pyright --verifytypes docopt2 --ignoreexternal
|
|
54
|
+
```
|
|
44
55
|
|
|
45
56
|
## Commit style
|
|
46
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: docopt2
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Typed successor to docopt: the usage message is the parser spec.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Solganis/docopt2
|
|
6
6
|
Project-URL: Repository, https://github.com/Solganis/docopt2
|
|
@@ -54,16 +54,14 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
<a href="https://pepy.tech/projects/docopt2"><img src="https://static.pepy.tech/badge/docopt2/month" alt="Downloads"></a>
|
|
55
55
|
<br>
|
|
56
56
|
<a href="https://solganis.github.io/docopt2/concepts/design-boundaries/#zero-runtime-dependencies"><img src="https://img.shields.io/badge/runtime%20deps-0-2ea043" alt="zero runtime dependencies, pydantic support optional"></a>
|
|
57
|
-
<a href="https://solganis.github.io/docopt2/guides/typed-results/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright-2ea043" alt="the typed API is checked by ty, mypy --strict, and
|
|
57
|
+
<a href="https://solganis.github.io/docopt2/guides/typed-results/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright%20%7C%20pyrefly-2ea043" alt="the typed API is checked by ty, mypy --strict, pyright and pyrefly"></a>
|
|
58
58
|
<a href="https://solganis.github.io/docopt2/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
|
|
59
59
|
<a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/docopt2"><img src="https://img.shields.io/ossf-scorecard/github.com/Solganis/docopt2?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
|
|
60
60
|
</p>
|
|
61
61
|
|
|
62
|
-
---
|
|
63
|
-
|
|
64
62
|
<h2 align="center">What is docopt2?</h2>
|
|
65
63
|
|
|
66
|
-
docopt2 is a command-line argument parser for Python. Most CLI libraries have you build the parser in code and generate a `--help
|
|
64
|
+
docopt2 is a command-line argument parser for Python. Most CLI libraries have you build the parser in code and generate a `--help`. docopt2 inverts that: **you write the usage message, and that message *is* the parser.**
|
|
67
65
|
|
|
68
66
|
Put the `Usage:` and `Options:` text in your docstring - the help you would write anyway - and docopt2 parses the command line against it. The help users read and the parser that runs are the same, and cannot drift.
|
|
69
67
|
|
|
@@ -71,7 +69,7 @@ Put the `Usage:` and `Options:` text in your docstring - the help you would writ
|
|
|
71
69
|
The <a href="https://solganis.github.io/docopt2/">full documentation</a> walks through <a href="https://solganis.github.io/docopt2/getting-started/">Getting started</a> and the guides.
|
|
72
70
|
</p>
|
|
73
71
|
|
|
74
|
-
<
|
|
72
|
+
<h3 align="center">Features</h3>
|
|
75
73
|
|
|
76
74
|
<table>
|
|
77
75
|
<tr>
|
|
@@ -176,13 +174,13 @@ docopt2 reads the same usage DSL as docopt - the `Usage:` and `Options:` blocks
|
|
|
176
174
|
<tr><td><code>a | b</code></td><td>Mutually exclusive: choose one.</td></tr>
|
|
177
175
|
<tr><td><code>element...</code></td><td>Repeatable: one or more.</td></tr>
|
|
178
176
|
<tr><td><code>[options]</code></td><td>Stands in for every option listed under <code>Options:</code>.</td></tr>
|
|
179
|
-
<tr><td><code>--</code></td><td>Ends option parsing
|
|
177
|
+
<tr><td><code>--</code></td><td>Ends option parsing. The rest is positional.</td></tr>
|
|
180
178
|
<tr><td><code>[default: <val>]</code></td><td>An option's default value, declared under <code>Options:</code>.</td></tr>
|
|
181
179
|
<tr><td><code>[env: <var>]</code></td><td>An option's environment-variable fallback.</td></tr>
|
|
182
180
|
<tr><td><code>[config: <key>]</code></td><td>An option's config-file fallback (CLI > env > config > default).</td></tr>
|
|
183
181
|
</table>
|
|
184
182
|
|
|
185
|
-
The legend covers the essentials
|
|
183
|
+
The legend covers the essentials. Precedence and edge cases are in the [full usage grammar](https://solganis.github.io/docopt2/guides/usage-dsl/).
|
|
186
184
|
|
|
187
185
|
<a name="typed-results"></a>
|
|
188
186
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/typed-results/">Why typed docopt?</a></h2>
|
|
@@ -224,7 +222,7 @@ Usage:
|
|
|
224
222
|
git push [--force] <remote>
|
|
225
223
|
```
|
|
226
224
|
|
|
227
|
-
**docopt2** [points at it](https://solganis.github.io/docopt2/guides/diagnostics/#a-mismatch-at-parse-time) - in the argv *and* the usage that rejected it.
|
|
225
|
+
**docopt2** [points at it](https://solganis.github.io/docopt2/guides/diagnostics/#a-mismatch-at-parse-time) - in the argv *and* the usage that rejected it. A mistyped option or command also gets a "did you mean" hint.
|
|
228
226
|
|
|
229
227
|
<p align="center">
|
|
230
228
|
<img src="docs/assets/diagnostic.png" width="620" alt="A docopt2 error: 'unknown option --forcce' with a caret under the token in the argument vector and a second caret under --force in the usage, plus a 'did you mean --force?' hint">
|
|
@@ -262,6 +260,8 @@ def commit(args):
|
|
|
262
260
|
app.run() # parse argv, call the matched command's handler
|
|
263
261
|
```
|
|
264
262
|
|
|
263
|
+
[`app.check()`](https://solganis.github.io/docopt2/guides/dispatch/#checking-the-table) lints the table before any run.
|
|
264
|
+
|
|
265
265
|
<a name="shell-completion"></a>
|
|
266
266
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/completion/">Shell completion</a></h2>
|
|
267
267
|
|
|
@@ -273,8 +273,8 @@ app.run() # parse argv, call the matched command's handler
|
|
|
273
273
|
<a href="https://www.nushell.sh/"><img src="https://img.shields.io/badge/nushell-4E9A06?logo=nushell&logoColor=white" alt="nushell"></a>
|
|
274
274
|
</p>
|
|
275
275
|
|
|
276
|
-
Generate the completion script for your shell
|
|
277
|
-
|
|
276
|
+
Generate the completion script for your shell. Tab then narrows to exactly what is valid at the cursor,<br>
|
|
277
|
+
straight from the usage:
|
|
278
278
|
|
|
279
279
|
```console
|
|
280
280
|
$ naval <TAB>
|
|
@@ -285,6 +285,10 @@ $ naval ship titanic move 1 2 <TAB>
|
|
|
285
285
|
--speed
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
+
With a schema, Tab also [completes the values](https://solganis.github.io/docopt2/guides/completion/#completing-values) of a `Literal` or an `Enum` field and the [file names](https://solganis.github.io/docopt2/guides/completion/#completing-file-names) of a `Path` field.
|
|
289
|
+
|
|
290
|
+
A program [prints its own script](https://solganis.github.io/docopt2/guides/completion/#installing-it): `eval "$(_DOCOPT2_COMPLETE=bash naval)"`.
|
|
291
|
+
|
|
288
292
|
<a name="schema-codegen"></a>
|
|
289
293
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/stub/">Generate the schema from the usage</a></h2>
|
|
290
294
|
|
|
@@ -313,7 +317,7 @@ Widen a field by hand (`speed: int`) and the coercion is automatic.
|
|
|
313
317
|
<a name="layered-fallback"></a>
|
|
314
318
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/usage-dsl/#environment-and-config-fallback">Layered value resolution</a></h2>
|
|
315
319
|
|
|
316
|
-
Declare an option's fallback sources in the usage with `[env: VAR]` and `[config: key]
|
|
320
|
+
Declare an option's fallback sources in the usage with `[env: VAR]` and `[config: key]`. docopt2 resolves each -<br>
|
|
317
321
|
the command-line argument first, then the environment, then a config mapping you pass, then the `[default: ...]`:
|
|
318
322
|
|
|
319
323
|
```python
|
|
@@ -344,7 +348,7 @@ port = 80 # --port, env APP_PORT
|
|
|
344
348
|
<a name="rich-help"></a>
|
|
345
349
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/help/">Self-documenting <code>--help</code></a></h2>
|
|
346
350
|
|
|
347
|
-
`--help` prints your usage verbatim by default. Opt into `help_style="rich"` for an aligned, colored screen that documents **where each value resolves from
|
|
351
|
+
`--help` prints your usage verbatim by default. Opt into `help_style="rich"` for an aligned, colored screen that documents **where each value resolves from**, reading the `[env, config, default]` chain straight out of the usage you already wrote:
|
|
348
352
|
|
|
349
353
|
<p align="center">
|
|
350
354
|
<img src="docs/assets/rich-help.png" width="878" alt="A docopt2 rich --help: a bold Usage, then Options with green option names, aligned descriptions, and a dimmed source chain per option - '[env: PORT, config: server.port, default: 8080]' - documenting the resolution order">
|
|
@@ -383,6 +387,10 @@ catching defects the parser would otherwise accept in silence:
|
|
|
383
387
|
It flags dead `[default: ...]` values, options declared but never usable,<br>
|
|
384
388
|
ambiguous variadic positionals, and redundant alternatives.
|
|
385
389
|
|
|
390
|
+
A usage that does not parse is reported as the error `docopt()` would raise, and `docopt2 check` exits 1.
|
|
391
|
+
|
|
392
|
+
With `check(doc, schema=Args)` it also reports where the [schema disagrees with the usage](https://solganis.github.io/docopt2/guides/check/#checking-a-schema).
|
|
393
|
+
|
|
386
394
|
<a name="usage-formatter"></a>
|
|
387
395
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/fmt/">Format the usage</a></h2>
|
|
388
396
|
|
|
@@ -410,7 +418,7 @@ The change is layout-only and idempotent. `docopt2 fmt` prints to stdout - pipe
|
|
|
410
418
|
<a name="round-trip"></a>
|
|
411
419
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/round-trip/">Round-trip: results back to argv</a></h2>
|
|
412
420
|
|
|
413
|
-
`docopt` parses an argv into a result
|
|
421
|
+
`docopt` parses an argv into a result. `format_argv` does the inverse - the same usage message drives *both* directions:
|
|
414
422
|
|
|
415
423
|
```python
|
|
416
424
|
doc = "Usage: prog <src> <dst> [--force]"
|
|
@@ -18,16 +18,14 @@
|
|
|
18
18
|
<a href="https://pepy.tech/projects/docopt2"><img src="https://static.pepy.tech/badge/docopt2/month" alt="Downloads"></a>
|
|
19
19
|
<br>
|
|
20
20
|
<a href="https://solganis.github.io/docopt2/concepts/design-boundaries/#zero-runtime-dependencies"><img src="https://img.shields.io/badge/runtime%20deps-0-2ea043" alt="zero runtime dependencies, pydantic support optional"></a>
|
|
21
|
-
<a href="https://solganis.github.io/docopt2/guides/typed-results/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright-2ea043" alt="the typed API is checked by ty, mypy --strict, and
|
|
21
|
+
<a href="https://solganis.github.io/docopt2/guides/typed-results/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright%20%7C%20pyrefly-2ea043" alt="the typed API is checked by ty, mypy --strict, pyright and pyrefly"></a>
|
|
22
22
|
<a href="https://solganis.github.io/docopt2/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
|
|
23
23
|
<a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/docopt2"><img src="https://img.shields.io/ossf-scorecard/github.com/Solganis/docopt2?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
-
---
|
|
27
|
-
|
|
28
26
|
<h2 align="center">What is docopt2?</h2>
|
|
29
27
|
|
|
30
|
-
docopt2 is a command-line argument parser for Python. Most CLI libraries have you build the parser in code and generate a `--help
|
|
28
|
+
docopt2 is a command-line argument parser for Python. Most CLI libraries have you build the parser in code and generate a `--help`. docopt2 inverts that: **you write the usage message, and that message *is* the parser.**
|
|
31
29
|
|
|
32
30
|
Put the `Usage:` and `Options:` text in your docstring - the help you would write anyway - and docopt2 parses the command line against it. The help users read and the parser that runs are the same, and cannot drift.
|
|
33
31
|
|
|
@@ -35,7 +33,7 @@ Put the `Usage:` and `Options:` text in your docstring - the help you would writ
|
|
|
35
33
|
The <a href="https://solganis.github.io/docopt2/">full documentation</a> walks through <a href="https://solganis.github.io/docopt2/getting-started/">Getting started</a> and the guides.
|
|
36
34
|
</p>
|
|
37
35
|
|
|
38
|
-
<
|
|
36
|
+
<h3 align="center">Features</h3>
|
|
39
37
|
|
|
40
38
|
<table>
|
|
41
39
|
<tr>
|
|
@@ -140,13 +138,13 @@ docopt2 reads the same usage DSL as docopt - the `Usage:` and `Options:` blocks
|
|
|
140
138
|
<tr><td><code>a | b</code></td><td>Mutually exclusive: choose one.</td></tr>
|
|
141
139
|
<tr><td><code>element...</code></td><td>Repeatable: one or more.</td></tr>
|
|
142
140
|
<tr><td><code>[options]</code></td><td>Stands in for every option listed under <code>Options:</code>.</td></tr>
|
|
143
|
-
<tr><td><code>--</code></td><td>Ends option parsing
|
|
141
|
+
<tr><td><code>--</code></td><td>Ends option parsing. The rest is positional.</td></tr>
|
|
144
142
|
<tr><td><code>[default: <val>]</code></td><td>An option's default value, declared under <code>Options:</code>.</td></tr>
|
|
145
143
|
<tr><td><code>[env: <var>]</code></td><td>An option's environment-variable fallback.</td></tr>
|
|
146
144
|
<tr><td><code>[config: <key>]</code></td><td>An option's config-file fallback (CLI > env > config > default).</td></tr>
|
|
147
145
|
</table>
|
|
148
146
|
|
|
149
|
-
The legend covers the essentials
|
|
147
|
+
The legend covers the essentials. Precedence and edge cases are in the [full usage grammar](https://solganis.github.io/docopt2/guides/usage-dsl/).
|
|
150
148
|
|
|
151
149
|
<a name="typed-results"></a>
|
|
152
150
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/typed-results/">Why typed docopt?</a></h2>
|
|
@@ -188,7 +186,7 @@ Usage:
|
|
|
188
186
|
git push [--force] <remote>
|
|
189
187
|
```
|
|
190
188
|
|
|
191
|
-
**docopt2** [points at it](https://solganis.github.io/docopt2/guides/diagnostics/#a-mismatch-at-parse-time) - in the argv *and* the usage that rejected it.
|
|
189
|
+
**docopt2** [points at it](https://solganis.github.io/docopt2/guides/diagnostics/#a-mismatch-at-parse-time) - in the argv *and* the usage that rejected it. A mistyped option or command also gets a "did you mean" hint.
|
|
192
190
|
|
|
193
191
|
<p align="center">
|
|
194
192
|
<img src="docs/assets/diagnostic.png" width="620" alt="A docopt2 error: 'unknown option --forcce' with a caret under the token in the argument vector and a second caret under --force in the usage, plus a 'did you mean --force?' hint">
|
|
@@ -226,6 +224,8 @@ def commit(args):
|
|
|
226
224
|
app.run() # parse argv, call the matched command's handler
|
|
227
225
|
```
|
|
228
226
|
|
|
227
|
+
[`app.check()`](https://solganis.github.io/docopt2/guides/dispatch/#checking-the-table) lints the table before any run.
|
|
228
|
+
|
|
229
229
|
<a name="shell-completion"></a>
|
|
230
230
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/completion/">Shell completion</a></h2>
|
|
231
231
|
|
|
@@ -237,8 +237,8 @@ app.run() # parse argv, call the matched command's handler
|
|
|
237
237
|
<a href="https://www.nushell.sh/"><img src="https://img.shields.io/badge/nushell-4E9A06?logo=nushell&logoColor=white" alt="nushell"></a>
|
|
238
238
|
</p>
|
|
239
239
|
|
|
240
|
-
Generate the completion script for your shell
|
|
241
|
-
|
|
240
|
+
Generate the completion script for your shell. Tab then narrows to exactly what is valid at the cursor,<br>
|
|
241
|
+
straight from the usage:
|
|
242
242
|
|
|
243
243
|
```console
|
|
244
244
|
$ naval <TAB>
|
|
@@ -249,6 +249,10 @@ $ naval ship titanic move 1 2 <TAB>
|
|
|
249
249
|
--speed
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
With a schema, Tab also [completes the values](https://solganis.github.io/docopt2/guides/completion/#completing-values) of a `Literal` or an `Enum` field and the [file names](https://solganis.github.io/docopt2/guides/completion/#completing-file-names) of a `Path` field.
|
|
253
|
+
|
|
254
|
+
A program [prints its own script](https://solganis.github.io/docopt2/guides/completion/#installing-it): `eval "$(_DOCOPT2_COMPLETE=bash naval)"`.
|
|
255
|
+
|
|
252
256
|
<a name="schema-codegen"></a>
|
|
253
257
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/stub/">Generate the schema from the usage</a></h2>
|
|
254
258
|
|
|
@@ -277,7 +281,7 @@ Widen a field by hand (`speed: int`) and the coercion is automatic.
|
|
|
277
281
|
<a name="layered-fallback"></a>
|
|
278
282
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/usage-dsl/#environment-and-config-fallback">Layered value resolution</a></h2>
|
|
279
283
|
|
|
280
|
-
Declare an option's fallback sources in the usage with `[env: VAR]` and `[config: key]
|
|
284
|
+
Declare an option's fallback sources in the usage with `[env: VAR]` and `[config: key]`. docopt2 resolves each -<br>
|
|
281
285
|
the command-line argument first, then the environment, then a config mapping you pass, then the `[default: ...]`:
|
|
282
286
|
|
|
283
287
|
```python
|
|
@@ -308,7 +312,7 @@ port = 80 # --port, env APP_PORT
|
|
|
308
312
|
<a name="rich-help"></a>
|
|
309
313
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/help/">Self-documenting <code>--help</code></a></h2>
|
|
310
314
|
|
|
311
|
-
`--help` prints your usage verbatim by default. Opt into `help_style="rich"` for an aligned, colored screen that documents **where each value resolves from
|
|
315
|
+
`--help` prints your usage verbatim by default. Opt into `help_style="rich"` for an aligned, colored screen that documents **where each value resolves from**, reading the `[env, config, default]` chain straight out of the usage you already wrote:
|
|
312
316
|
|
|
313
317
|
<p align="center">
|
|
314
318
|
<img src="docs/assets/rich-help.png" width="878" alt="A docopt2 rich --help: a bold Usage, then Options with green option names, aligned descriptions, and a dimmed source chain per option - '[env: PORT, config: server.port, default: 8080]' - documenting the resolution order">
|
|
@@ -347,6 +351,10 @@ catching defects the parser would otherwise accept in silence:
|
|
|
347
351
|
It flags dead `[default: ...]` values, options declared but never usable,<br>
|
|
348
352
|
ambiguous variadic positionals, and redundant alternatives.
|
|
349
353
|
|
|
354
|
+
A usage that does not parse is reported as the error `docopt()` would raise, and `docopt2 check` exits 1.
|
|
355
|
+
|
|
356
|
+
With `check(doc, schema=Args)` it also reports where the [schema disagrees with the usage](https://solganis.github.io/docopt2/guides/check/#checking-a-schema).
|
|
357
|
+
|
|
350
358
|
<a name="usage-formatter"></a>
|
|
351
359
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/fmt/">Format the usage</a></h2>
|
|
352
360
|
|
|
@@ -374,7 +382,7 @@ The change is layout-only and idempotent. `docopt2 fmt` prints to stdout - pipe
|
|
|
374
382
|
<a name="round-trip"></a>
|
|
375
383
|
<h2 align="center"><a href="https://solganis.github.io/docopt2/guides/round-trip/">Round-trip: results back to argv</a></h2>
|
|
376
384
|
|
|
377
|
-
`docopt` parses an argv into a result
|
|
385
|
+
`docopt` parses an argv into a result. `format_argv` does the inverse - the same usage message drives *both* directions:
|
|
378
386
|
|
|
379
387
|
```python
|
|
380
388
|
doc = "Usage: prog <src> <dst> [--force]"
|