docopt2 1.2.0__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.
Files changed (197) hide show
  1. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/ci.yml +33 -9
  2. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/codeql.yml +3 -3
  3. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/codspeed.yml +5 -3
  4. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/docs.yml +6 -4
  5. docopt2-1.3.0/.github/workflows/fuzz.yml +80 -0
  6. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/mutation.yml +4 -2
  7. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/nightly.yml +4 -2
  8. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/publish.yml +7 -7
  9. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/scorecard.yml +3 -3
  10. {docopt2-1.2.0 → docopt2-1.3.0}/.github/workflows/zizmor.yml +2 -2
  11. {docopt2-1.2.0 → docopt2-1.3.0}/CONTRIBUTING.md +13 -2
  12. {docopt2-1.2.0 → docopt2-1.3.0}/PKG-INFO +24 -16
  13. {docopt2-1.2.0 → docopt2-1.3.0}/README.md +22 -14
  14. docopt2-1.3.0/docs/concepts/design-boundaries.md +212 -0
  15. {docopt2-1.2.0 → docopt2-1.3.0}/docs/getting-started.md +51 -40
  16. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/check.md +138 -24
  17. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/compat.md +25 -15
  18. docopt2-1.3.0/docs/guides/completion.md +336 -0
  19. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/diagnostics.md +120 -42
  20. docopt2-1.3.0/docs/guides/dispatch.md +349 -0
  21. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/examples.md +36 -26
  22. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/fmt.md +24 -14
  23. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/help.md +28 -17
  24. docopt2-1.3.0/docs/guides/round-trip.md +104 -0
  25. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/stub.md +26 -17
  26. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/typed-results.md +99 -38
  27. {docopt2-1.2.0 → docopt2-1.3.0}/docs/guides/usage-dsl.md +73 -41
  28. docopt2-1.3.0/docs/index.md +173 -0
  29. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/check.md +2 -1
  30. docopt2-1.3.0/docs/reference/cli.md +9 -0
  31. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/completion.md +2 -1
  32. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/dispatch.md +2 -1
  33. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/docopt.md +1 -0
  34. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/exceptions.md +5 -2
  35. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/grammar.md +7 -3
  36. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/stub.md +1 -1
  37. docopt2-1.3.0/fuzz/corpus/argument_default.txt +4 -0
  38. docopt2-1.3.0/fuzz/corpus/commands_and_fallbacks.txt +6 -0
  39. docopt2-1.3.0/fuzz/corpus/counted_and_repeated.txt +1 -0
  40. docopt2-1.3.0/fuzz/corpus/either_and_separator.txt +2 -0
  41. docopt2-1.3.0/fuzz/corpus/nested_commands.txt +6 -0
  42. docopt2-1.3.0/fuzz/corpus/options_and_defaults.txt +5 -0
  43. docopt2-1.3.0/fuzz/corpus/repeated_option.txt +4 -0
  44. {docopt2-1.2.0 → docopt2-1.3.0}/fuzz/fuzz_docopt.py +43 -0
  45. {docopt2-1.2.0 → docopt2-1.3.0}/pyproject.toml +21 -9
  46. docopt2-1.3.0/pyrefly.toml +9 -0
  47. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/__init__.py +3 -1
  48. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_compat.py +8 -2
  49. docopt2-1.3.0/src/docopt2/_completion.py +984 -0
  50. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_core.py +328 -107
  51. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_diagnostics.py +33 -15
  52. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_errors.py +26 -4
  53. docopt2-1.3.0/src/docopt2/_fmt.py +73 -0
  54. docopt2-1.3.0/src/docopt2/_format.py +250 -0
  55. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_generate.py +21 -14
  56. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_help.py +1 -1
  57. docopt2-1.3.0/src/docopt2/_lint.py +430 -0
  58. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_parser.py +264 -97
  59. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_spellcheck.py +8 -5
  60. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/_stub.py +33 -16
  61. docopt2-1.3.0/src/docopt2/_typed.py +699 -0
  62. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/hypothesis.py +1 -1
  63. {docopt2-1.2.0 → docopt2-1.3.0}/tests/conftest.py +1 -2
  64. docopt2-1.3.0/tests/golden/completion_bash.txt +40 -0
  65. docopt2-1.3.0/tests/golden/completion_fish.txt +14 -0
  66. docopt2-1.3.0/tests/golden/completion_nushell.txt +19 -0
  67. {docopt2-1.2.0 → docopt2-1.3.0}/tests/golden/completion_powershell.txt +12 -2
  68. {docopt2-1.2.0 → docopt2-1.3.0}/tests/golden/completion_zsh.txt +9 -2
  69. docopt2-1.3.0/tests/test_caret_columns.py +112 -0
  70. docopt2-1.3.0/tests/test_caret_places.py +221 -0
  71. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_cli.py +8 -0
  72. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_coercion_diagnostics.py +41 -3
  73. docopt2-1.3.0/tests/test_complete_files.py +379 -0
  74. docopt2-1.3.0/tests/test_complete_reading.py +420 -0
  75. docopt2-1.3.0/tests/test_complete_refused.py +202 -0
  76. docopt2-1.3.0/tests/test_complete_values.py +541 -0
  77. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_completion_vs_docopt.py +52 -1
  78. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_config.py +42 -3
  79. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_config_template.py +8 -1
  80. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_dispatch_and_completion.py +81 -22
  81. docopt2-1.3.0/tests/test_dispatch_table.py +516 -0
  82. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_divergences.py +17 -0
  83. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_docs_blocks.py +48 -4
  84. docopt2-1.3.0/tests/test_docs_fences.py +123 -0
  85. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_env.py +31 -0
  86. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_fmt.py +90 -1
  87. docopt2-1.3.0/tests/test_format.py +358 -0
  88. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_import_cost.py +74 -2
  89. docopt2-1.3.0/tests/test_initvar_schema.py +98 -0
  90. docopt2-1.3.0/tests/test_initvar_schema_strings.py +35 -0
  91. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_lint.py +126 -22
  92. docopt2-1.3.0/tests/test_matcher_pieces.py +89 -0
  93. docopt2-1.3.0/tests/test_mistyped_command.py +337 -0
  94. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_package.py +7 -0
  95. docopt2-1.3.0/tests/test_property_caret_places.py +162 -0
  96. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_property_config_template.py +53 -1
  97. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_property_differential.py +73 -3
  98. docopt2-1.3.0/tests/test_property_schema.py +275 -0
  99. docopt2-1.3.0/tests/test_refused_value_report.py +537 -0
  100. docopt2-1.3.0/tests/test_repeated_annotation.py +99 -0
  101. docopt2-1.3.0/tests/test_robustness.py +460 -0
  102. docopt2-1.3.0/tests/test_scalar_table.py +112 -0
  103. docopt2-1.3.0/tests/test_schema_edges.py +556 -0
  104. docopt2-1.3.0/tests/test_schema_fit.py +635 -0
  105. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_spellcheck.py +6 -3
  106. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_typed_api.py +69 -0
  107. {docopt2-1.2.0 → docopt2-1.3.0}/tests/typing_surface/check_typed.py +17 -2
  108. docopt2-1.3.0/tools/shell_check.py +575 -0
  109. docopt2-1.3.0/uv.lock +2180 -0
  110. docopt2-1.2.0/.github/workflows/fuzz.yml +0 -50
  111. docopt2-1.2.0/docs/concepts/design-boundaries.md +0 -166
  112. docopt2-1.2.0/docs/guides/completion.md +0 -147
  113. docopt2-1.2.0/docs/guides/dispatch.md +0 -167
  114. docopt2-1.2.0/docs/guides/round-trip.md +0 -76
  115. docopt2-1.2.0/docs/index.md +0 -172
  116. docopt2-1.2.0/docs/reference/cli.md +0 -9
  117. docopt2-1.2.0/src/docopt2/_completion.py +0 -401
  118. docopt2-1.2.0/src/docopt2/_fmt.py +0 -36
  119. docopt2-1.2.0/src/docopt2/_format.py +0 -187
  120. docopt2-1.2.0/src/docopt2/_lint.py +0 -198
  121. docopt2-1.2.0/src/docopt2/_typed.py +0 -361
  122. docopt2-1.2.0/tests/golden/completion_bash.txt +0 -17
  123. docopt2-1.2.0/tests/golden/completion_fish.txt +0 -5
  124. docopt2-1.2.0/tests/golden/completion_nushell.txt +0 -12
  125. docopt2-1.2.0/tests/test_format.py +0 -175
  126. docopt2-1.2.0/tests/test_robustness.py +0 -175
  127. docopt2-1.2.0/tools/shell_check.py +0 -275
  128. docopt2-1.2.0/uv.lock +0 -1760
  129. {docopt2-1.2.0 → docopt2-1.3.0}/.codecov.yml +0 -0
  130. {docopt2-1.2.0 → docopt2-1.3.0}/.gitattributes +0 -0
  131. {docopt2-1.2.0 → docopt2-1.3.0}/.github/dependabot.yml +0 -0
  132. {docopt2-1.2.0 → docopt2-1.3.0}/.gitignore +0 -0
  133. {docopt2-1.2.0 → docopt2-1.3.0}/LICENSE +0 -0
  134. {docopt2-1.2.0 → docopt2-1.3.0}/NOTICE +0 -0
  135. {docopt2-1.2.0 → docopt2-1.3.0}/SECURITY.md +0 -0
  136. {docopt2-1.2.0 → docopt2-1.3.0}/benchmarks/test_perf.py +0 -0
  137. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/check.png +0 -0
  138. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/check.txt +0 -0
  139. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/coercion.png +0 -0
  140. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/coercion.txt +0 -0
  141. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/diagnostic.png +0 -0
  142. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/diagnostic.txt +0 -0
  143. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/logo-dark.png +0 -0
  144. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/logo.png +0 -0
  145. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/rich-help.png +0 -0
  146. {docopt2-1.2.0 → docopt2-1.3.0}/docs/assets/rich-help.txt +0 -0
  147. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/compat.md +0 -0
  148. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/config-templates.md +0 -0
  149. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/examples.md +0 -0
  150. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/fmt.md +0 -0
  151. {docopt2-1.2.0 → docopt2-1.3.0}/docs/reference/overview.md +0 -0
  152. {docopt2-1.2.0 → docopt2-1.3.0}/docs/stylesheets/extra.css +0 -0
  153. {docopt2-1.2.0 → docopt2-1.3.0}/examples/cli.py +0 -0
  154. {docopt2-1.2.0 → docopt2-1.3.0}/examples/completion.py +0 -0
  155. {docopt2-1.2.0 → docopt2-1.3.0}/examples/dispatch.py +0 -0
  156. {docopt2-1.2.0 → docopt2-1.3.0}/examples/layered.py +0 -0
  157. {docopt2-1.2.0 → docopt2-1.3.0}/examples/naval_fate.py +0 -0
  158. {docopt2-1.2.0 → docopt2-1.3.0}/examples/rich_help.py +0 -0
  159. {docopt2-1.2.0 → docopt2-1.3.0}/examples/round_trip.py +0 -0
  160. {docopt2-1.2.0 → docopt2-1.3.0}/examples/typed.py +0 -0
  161. {docopt2-1.2.0 → docopt2-1.3.0}/fuzz/docopt.dict +0 -0
  162. {docopt2-1.2.0 → docopt2-1.3.0}/mkdocs.yml +0 -0
  163. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/__main__.py +0 -0
  164. {docopt2-1.2.0 → docopt2-1.3.0}/src/docopt2/py.typed +0 -0
  165. {docopt2-1.2.0 → docopt2-1.3.0}/tests/_strategies.py +0 -0
  166. {docopt2-1.2.0 → docopt2-1.3.0}/tests/_vendor/docopt_original.py +0 -0
  167. {docopt2-1.2.0 → docopt2-1.3.0}/tests/corpus/awkward_but_legal.txt +0 -0
  168. {docopt2-1.2.0 → docopt2-1.3.0}/tests/corpus/layered_config.txt +0 -0
  169. {docopt2-1.2.0 → docopt2-1.3.0}/tests/corpus/many_flags.txt +0 -0
  170. {docopt2-1.2.0 → docopt2-1.3.0}/tests/corpus/naval_fate.txt +0 -0
  171. {docopt2-1.2.0 → docopt2-1.3.0}/tests/corpus/nested_subcommands.txt +0 -0
  172. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_choices.py +0 -0
  173. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_compat.py +0 -0
  174. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_core_extra.py +0 -0
  175. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_diagnostics.py +0 -0
  176. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_docopt_original.py +0 -0
  177. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_examples.py +0 -0
  178. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_extended_features.py +0 -0
  179. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_generate.py +0 -0
  180. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_getting_started.py +0 -0
  181. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_grammar_examples.py +0 -0
  182. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_help.py +0 -0
  183. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_hypothesis.py +0 -0
  184. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_language_agnostic.docopt +0 -0
  185. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_near_miss.py +0 -0
  186. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_parse_tree.py +0 -0
  187. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_property_coercion.py +0 -0
  188. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_property_config.py +0 -0
  189. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_property_extended.py +0 -0
  190. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_pydantic.py +0 -0
  191. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_readme.py +0 -0
  192. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_screenshots.py +0 -0
  193. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_source.py +0 -0
  194. {docopt2-1.2.0 → docopt2-1.3.0}/tests/test_stub.py +0 -0
  195. {docopt2-1.2.0 → docopt2-1.3.0}/tools/mutation_report.py +0 -0
  196. {docopt2-1.2.0 → docopt2-1.3.0}/tools/regolden.py +0 -0
  197. {docopt2-1.2.0 → 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
37
  with:
38
38
  persist-credentials: false
39
39
 
40
40
  - name: Install uv
41
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
21
  with:
22
22
  persist-credentials: false
23
- - uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
23
+ - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
24
24
  with:
25
25
  languages: python
26
- - uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@f99becdce5e5d51fd556489ebef684f4ecfd6286 # v4.18.5
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
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@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
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@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
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@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
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@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
20
  with:
21
21
  persist-credentials: false
22
- - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
23
  with:
24
24
  persist-credentials: false
25
- - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
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); a PR that drops below it fails. It also runs
43
- `mypy --strict` and `pyright` on the typed surface, across Python 3.10 - 3.15.
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: docopt2
3
- Version: 1.2.0
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
@@ -53,17 +53,15 @@ Description-Content-Type: text/markdown
53
53
  <a href="https://pypi.org/project/docopt2/"><img src="https://img.shields.io/pypi/pyversions/docopt2" alt="Python"></a>
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
- <a href="https://solganis.github.io/docopt2/concepts/design-boundaries/"><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 pyright"></a>
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%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`; docopt2 inverts that: **you write the usage message, and that message *is* the parser.**
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
- <h2 align="center">Features</h2>
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; the rest is positional.</td></tr>
177
+ <tr><td><code>--</code></td><td>Ends option parsing. The rest is positional.</td></tr>
180
178
  <tr><td><code>[default: &lt;val&gt;]</code></td><td>An option's default value, declared under <code>Options:</code>.</td></tr>
181
179
  <tr><td><code>[env: &lt;var&gt;]</code></td><td>An option's environment-variable fallback.</td></tr>
182
180
  <tr><td><code>[config: &lt;key&gt;]</code></td><td>An option's config-file fallback (CLI &gt; env &gt; config &gt; default).</td></tr>
183
181
  </table>
184
182
 
185
- The legend covers the essentials; precedence and edge cases are in the [full usage grammar](https://solganis.github.io/docopt2/guides/usage-dsl/).
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. The "did you mean" hint is opt-in: pass `suggest=True`.
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; Tab then narrows to exactly what is valid at the cursor -<br>
277
- commands and options, never positional values - straight from the usage:
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]`; docopt2 resolves each -<br>
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** - the `[env, config, default]` chain no other CLI library surfaces:
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; `format_argv` does the inverse - the same usage message drives *both* directions:
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]"