strictcli 0.20.0__tar.gz → 0.20.1__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 (169) hide show
  1. strictcli-0.20.1/.rlsbl/changes/.validated +1 -0
  2. strictcli-0.20.1/.rlsbl/changes/0.15.0.md +18 -0
  3. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.15.1.md +9 -0
  4. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.16.0.md +9 -0
  5. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.16.1.md +2 -0
  6. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.17.0.md +2 -0
  7. strictcli-0.20.1/.rlsbl/changes/0.18.0.md +14 -0
  8. strictcli-0.20.1/.rlsbl/changes/0.20.0.md +19 -0
  9. strictcli-0.20.1/.rlsbl/changes/0.20.1.jsonl +3 -0
  10. strictcli-0.20.1/.rlsbl/changes/0.20.1.md +14 -0
  11. strictcli-0.20.1/.rlsbl/releases/v0.20.1.toml +8 -0
  12. {strictcli-0.20.0 → strictcli-0.20.1}/CHANGELOG.md +15 -0
  13. {strictcli-0.20.0 → strictcli-0.20.1}/PKG-INFO +1 -1
  14. {strictcli-0.20.0 → strictcli-0.20.1}/package-lock.json +2 -2
  15. {strictcli-0.20.0 → strictcli-0.20.1}/package.json +1 -1
  16. {strictcli-0.20.0 → strictcli-0.20.1}/pyproject.toml +1 -1
  17. {strictcli-0.20.0 → strictcli-0.20.1}/strictcli/__init__.py +11 -3
  18. strictcli-0.20.1/tests/test_keyword_flags.py +117 -0
  19. strictcli-0.20.1/todo/keyword-collision-in-flag-param-name.md +18 -0
  20. {strictcli-0.20.0 → strictcli-0.20.1}/uv.lock +1 -1
  21. strictcli-0.20.0/.rlsbl/changes/.validated +0 -1
  22. strictcli-0.20.0/.rlsbl/changes/0.15.0.md +0 -9
  23. strictcli-0.20.0/.rlsbl/changes/0.18.0.md +0 -5
  24. {strictcli-0.20.0 → strictcli-0.20.1}/.claude/settings.json +0 -0
  25. {strictcli-0.20.0 → strictcli-0.20.1}/.github/workflows/ci.yml +0 -0
  26. {strictcli-0.20.0 → strictcli-0.20.1}/.github/workflows/publish.yml +0 -0
  27. {strictcli-0.20.0 → strictcli-0.20.1}/.gitignore +0 -0
  28. {strictcli-0.20.0 → strictcli-0.20.1}/.npmignore +0 -0
  29. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.claude/settings.json +0 -0
  30. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
  31. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
  32. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.gitignore +0 -0
  33. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.npmignore +0 -0
  34. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
  35. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
  36. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
  37. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
  38. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
  39. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/CHANGELOG.md +0 -0
  40. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/CLAUDE.md +0 -0
  41. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/bases/LICENSE +0 -0
  42. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.10.0.jsonl +0 -0
  43. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.10.0.md +0 -0
  44. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.11.0.jsonl +0 -0
  45. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.11.0.md +0 -0
  46. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.12.0.jsonl +0 -0
  47. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.12.0.md +0 -0
  48. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.13.0.jsonl +0 -0
  49. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.13.0.md +0 -0
  50. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.14.0.jsonl +0 -0
  51. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.14.0.md +0 -0
  52. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.15.0.jsonl +0 -0
  53. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.15.1.jsonl +0 -0
  54. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.16.0.jsonl +0 -0
  55. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.16.1.jsonl +0 -0
  56. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.17.0.jsonl +0 -0
  57. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.18.0.jsonl +0 -0
  58. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.19.0.jsonl +0 -0
  59. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.19.0.md +0 -0
  60. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.20.0.jsonl +0 -0
  61. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.4.0.jsonl +0 -0
  62. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.4.0.md +0 -0
  63. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.4.1.jsonl +0 -0
  64. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.4.1.md +0 -0
  65. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.5.0.jsonl +0 -0
  66. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.5.0.md +0 -0
  67. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.6.0.jsonl +0 -0
  68. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.6.0.md +0 -0
  69. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.6.1.jsonl +0 -0
  70. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.6.1.md +0 -0
  71. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.7.0.jsonl +0 -0
  72. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.7.0.md +0 -0
  73. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.7.1.jsonl +0 -0
  74. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.7.1.md +0 -0
  75. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.0.jsonl +0 -0
  76. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.0.md +0 -0
  77. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.1.jsonl +0 -0
  78. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.1.md +0 -0
  79. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.2.jsonl +0 -0
  80. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.2.md +0 -0
  81. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.3.jsonl +0 -0
  82. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.3.md +0 -0
  83. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.4.jsonl +0 -0
  84. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.4.md +0 -0
  85. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.5.jsonl +0 -0
  86. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.5.md +0 -0
  87. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.6.jsonl +0 -0
  88. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.6.md +0 -0
  89. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.7.jsonl +0 -0
  90. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.8.7.md +0 -0
  91. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.9.0.jsonl +0 -0
  92. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.9.0.md +0 -0
  93. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.9.1.jsonl +0 -0
  94. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/0.9.1.md +0 -0
  95. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/changes/unreleased.jsonl +0 -0
  96. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/config.json +0 -0
  97. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/hashes.json +0 -0
  98. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/hooks/post-release.sh +0 -0
  99. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/hooks/pre-checks.sh +0 -0
  100. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/hooks/pre-release.sh +0 -0
  101. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/lint/npm.toml +0 -0
  102. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/lint/python.toml +0 -0
  103. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/unreleased.toml +0 -0
  104. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.10.0.toml +0 -0
  105. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.11.0.toml +0 -0
  106. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.12.0.toml +0 -0
  107. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.13.0.toml +0 -0
  108. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.15.0.toml +0 -0
  109. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.15.1.toml +0 -0
  110. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.16.0.toml +0 -0
  111. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.16.1.toml +0 -0
  112. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.17.0.toml +0 -0
  113. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.18.0.toml +0 -0
  114. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.19.0.toml +0 -0
  115. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.20.0.toml +0 -0
  116. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.8.5.toml +0 -0
  117. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.8.6.toml +0 -0
  118. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.8.7.toml +0 -0
  119. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.9.0.toml +0 -0
  120. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/releases/v0.9.1.toml +0 -0
  121. {strictcli-0.20.0 → strictcli-0.20.1}/.rlsbl/version +0 -0
  122. {strictcli-0.20.0 → strictcli-0.20.1}/.strictcli/schema.json +0 -0
  123. {strictcli-0.20.0 → strictcli-0.20.1}/CLAUDE.md +0 -0
  124. {strictcli-0.20.0 → strictcli-0.20.1}/LICENSE +0 -0
  125. {strictcli-0.20.0 → strictcli-0.20.1}/README.md +0 -0
  126. {strictcli-0.20.0 → strictcli-0.20.1}/index.js +0 -0
  127. {strictcli-0.20.0 → strictcli-0.20.1}/postinstall.js +0 -0
  128. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_arg_default.py +0 -0
  129. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_at_prefix.py +0 -0
  130. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_auto_version.py +0 -0
  131. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_command.py +0 -0
  132. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_discovery.py +0 -0
  133. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_public_api.py +0 -0
  134. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_runner.py +0 -0
  135. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_schema.py +0 -0
  136. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_check_types.py +0 -0
  137. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_choices.py +0 -0
  138. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_command_help_suggestion.py +0 -0
  139. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_command_tags.py +0 -0
  140. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_config.py +0 -0
  141. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_config_file_path.py +0 -0
  142. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_config_set_bugs.py +0 -0
  143. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_deep_nesting.py +0 -0
  144. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_dependencies.py +0 -0
  145. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_deprecated.py +0 -0
  146. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_dump_schema.py +0 -0
  147. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_e2e.py +0 -0
  148. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_env.py +0 -0
  149. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_exit_codes.py +0 -0
  150. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_flag_sets.py +0 -0
  151. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_float_type.py +0 -0
  152. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_global_flags.py +0 -0
  153. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_help.py +0 -0
  154. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_int_type.py +0 -0
  155. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_mutex.py +0 -0
  156. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_nesting.py +0 -0
  157. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_parser.py +0 -0
  158. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_passthrough.py +0 -0
  159. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_registration.py +0 -0
  160. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_repeatable.py +0 -0
  161. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_tagdsl.py +0 -0
  162. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_toml_loading.py +0 -0
  163. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_unique.py +0 -0
  164. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_utilities.py +0 -0
  165. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_validate.py +0 -0
  166. {strictcli-0.20.0 → strictcli-0.20.1}/tests/test_variadic.py +0 -0
  167. {strictcli-0.20.0 → strictcli-0.20.1}/todo/.defer/deferred.md +0 -0
  168. {strictcli-0.20.0 → strictcli-0.20.1}/todo/.done/original-idea.md +0 -0
  169. {strictcli-0.20.0 → strictcli-0.20.1}/todo/.done/public-check-runner-api.md +0 -0
@@ -0,0 +1 @@
1
+ 5b2b40dead26752424a198c76f3634593b1cdb9b
@@ -0,0 +1,18 @@
1
+ ## 0.15.0
2
+
3
+ Config set type coercion and key validation
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ config set now validates keys against registered flags and coerces string values to the flag's declared type before writing. This is a breaking change: unknown keys are rejected.
9
+
10
+ </details>
11
+
12
+ ### Breaking
13
+
14
+ - **Breaking.** `config set` now validates keys against registered flags and rejects unknown keys.
15
+
16
+ ### Features
17
+
18
+ - **New feature.** `config set` coerces values to the flag's declared type (int, bool, float) before writing to config.
@@ -1,5 +1,14 @@
1
1
  ## 0.15.1
2
2
 
3
+ Config show bool parity and negative values in config set
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ config show now outputs lowercase true/false matching Go. config set accepts negative numeric values.
9
+
10
+ </details>
11
+
3
12
  ### Breaking
4
13
 
5
14
  - **Breaking.** Config set now accepts negative numeric values. Unknown single-dash tokens produce 'unexpected argument' instead of 'unknown flag'.
@@ -1,5 +1,14 @@
1
1
  ## 0.16.0
2
2
 
3
+ unique and env_separator flag fields, config array coercion, config set repeatable support
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ Breaking: config show now requires --plain or --json. New features: unique field enforces no duplicate values on repeatable flags, env_separator controls how env vars are split into arrays, config arrays are coerced to declared types, config set supports repeatable flags with --clear and --default.
9
+
10
+ </details>
11
+
3
12
  ### Breaking
4
13
 
5
14
  - **Config show.** `config show` now requires explicit `--plain` or `--json` flag instead of defaulting to plain text.
@@ -1,5 +1,7 @@
1
1
  ## 0.16.1
2
2
 
3
+ Error message fixes and help text improvements
4
+
3
5
  ### Fixes
4
6
 
5
7
  - **Fix.** `config set` mutex error messages now include the `config set:` prefix for consistency.
@@ -1,5 +1,7 @@
1
1
  ## 0.17.0
2
2
 
3
+ Repeatable flag default validation and help text display
4
+
3
5
  ### Breaking
4
6
 
5
7
  - **Breaking.** Repeatable flag defaults are now validated at registration: must be a list, `default=[]` is rejected as redundant, and element types must match the flag type.
@@ -0,0 +1,14 @@
1
+ ## 0.18.0
2
+
3
+ Public API for running checks programmatically
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ Adds App.run_checks(), format_check_results(), format_check_results_json(), and CheckRunResult — a stable public API replacing the private _filter_checks/_resolve_check_order/_run_checks/_check_format_human functions that consumers like rlsbl were forced to use.
9
+
10
+ </details>
11
+
12
+ ### Features
13
+
14
+ - **New.** Public API for running checks programmatically: `App.run_checks()`, `format_check_results()`, `format_check_results_json()`, `CheckRunResult`.
@@ -0,0 +1,19 @@
1
+ ## 0.20.0
2
+
3
+ Frozen Command, command tags, tag contracts
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ Breaking: Command dataclass is now frozen=True with tuple fields (was mutable list). New: command tags (string labels with group inheritance) and tag contracts (registration-time validation that tagged commands have required flags).
9
+
10
+ </details>
11
+
12
+ ### Breaking
13
+
14
+ - **Breaking.** Command dataclass is now `frozen=True` with `tuple` fields instead of `list`.
15
+
16
+ ### Features
17
+
18
+ - **New.** Command tags: string labels on commands and groups with group-to-command inheritance.
19
+ - **New.** Tag contracts: registration-time validation that tagged commands have required flags.
@@ -0,0 +1,3 @@
1
+ {"commits":["88874b77625f9f096a9e7c38212cb65ab7bd4188"],"user_facing":false}
2
+ {"commits":["0e5647f165e7b5ebb4bb135bd664f072306b282a"],"user_facing":true,"description":"**Fix.** Flags named after Python keywords (`--global`, `--class`, `--import`) now produce valid parameter names with a trailing underscore (e.g., `global_`).","type":"fix"}
3
+ {"commits":["8e769b6aa17c51d30edae5a074f4a6f31c624853"],"user_facing":false}
@@ -0,0 +1,14 @@
1
+ ## 0.20.1
2
+
3
+ Fix keyword collision in flag parameter names
4
+
5
+ <details>
6
+ <summary>Context</summary>
7
+
8
+ Flags named after Python keywords (--global, --class, --import, etc.) now produce valid parameter names with a trailing underscore per PEP 8 convention.
9
+
10
+ </details>
11
+
12
+ ### Fixes
13
+
14
+ - **Fix.** Flags named after Python keywords (`--global`, `--class`, `--import`) now produce valid parameter names with a trailing underscore (e.g., `global_`).
@@ -0,0 +1,8 @@
1
+ # Version bump type: patch, minor, or major
2
+ bump = "patch"
3
+ # Short description of this release (required)
4
+ description = "Fix keyword collision in flag parameter names"
5
+ # Optional context explaining why these changes were made
6
+ context = "Flags named after Python keywords (--global, --class, --import, etc.) now produce valid parameter names with a trailing underscore per PEP 8 convention."
7
+ include = ["pypi", "npm"]
8
+ exclude = []
@@ -2,6 +2,21 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## 0.20.1
6
+
7
+ Fix keyword collision in flag parameter names
8
+
9
+ <details>
10
+ <summary>Context</summary>
11
+
12
+ Flags named after Python keywords (--global, --class, --import, etc.) now produce valid parameter names with a trailing underscore per PEP 8 convention.
13
+
14
+ </details>
15
+
16
+ ### Fixes
17
+
18
+ - **Fix.** Flags named after Python keywords (`--global`, `--class`, `--import`) now produce valid parameter names with a trailing underscore (e.g., `global_`).
19
+
5
20
  ## 0.20.0
6
21
 
7
22
  Frozen Command, command tags, tag contracts
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: A strict, zero-dependency CLI framework for Python
5
5
  Project-URL: Homepage, https://github.com/smm-h/strictcli
6
6
  Project-URL: Repository, https://github.com/smm-h/strictcli
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.20.0",
9
+ "version": "0.20.1",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "A strict, zero-dependency CLI framework for Python (npm wrapper)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "strictcli"
7
- version = "0.20.0"
7
+ version = "0.20.1"
8
8
  description = "A strict, zero-dependency CLI framework for Python"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.20.0"
5
+ __version__ = "0.20.1"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "FlagSet", "MutexGroup", "CoRequired", "Requires",
@@ -12,6 +12,7 @@ __all__ = [
12
12
  ]
13
13
 
14
14
  import contextlib
15
+ import keyword
15
16
  import fnmatch
16
17
  import importlib.metadata
17
18
  import inspect
@@ -2553,8 +2554,15 @@ def _parse_command(
2553
2554
 
2554
2555
 
2555
2556
  def _flag_param_name(flag_name: str) -> str:
2556
- """Convert a flag name like '--dry-run' to a Python parameter name 'dry_run'."""
2557
- return flag_name.lstrip("-").replace("-", "_")
2557
+ """Convert a flag name like '--dry-run' to a Python parameter name 'dry_run'.
2558
+
2559
+ If the result is a Python keyword (e.g. 'global', 'class'), appends '_'
2560
+ per PEP 8 convention (e.g. 'global_', 'class_').
2561
+ """
2562
+ name = flag_name.lstrip("-").replace("-", "_")
2563
+ if keyword.iskeyword(name):
2564
+ name += "_"
2565
+ return name
2558
2566
 
2559
2567
 
2560
2568
  def _build_and_validate_command(
@@ -0,0 +1,117 @@
1
+ """Tests for Python keyword collision handling in flag parameter names."""
2
+
3
+ import json
4
+ import os
5
+
6
+ import strictcli
7
+ from strictcli import _flag_param_name
8
+
9
+
10
+ # --- Unit tests for _flag_param_name ---
11
+
12
+
13
+ def test_non_keyword_unchanged():
14
+ """Non-keyword flag names convert normally (dashes to underscores)."""
15
+ assert _flag_param_name("--dry-run") == "dry_run"
16
+
17
+
18
+ def test_keyword_global():
19
+ """'global' is a Python keyword, should get trailing underscore."""
20
+ assert _flag_param_name("--global") == "global_"
21
+
22
+
23
+ def test_keyword_class():
24
+ """'class' is a Python keyword, should get trailing underscore."""
25
+ assert _flag_param_name("--class") == "class_"
26
+
27
+
28
+ def test_keyword_import():
29
+ """'import' is a Python keyword, should get trailing underscore."""
30
+ assert _flag_param_name("--import") == "import_"
31
+
32
+
33
+ def test_keyword_for():
34
+ """'for' is a Python keyword, should get trailing underscore."""
35
+ assert _flag_param_name("--for") == "for_"
36
+
37
+
38
+ def test_keyword_in():
39
+ """'in' is a Python keyword, should get trailing underscore."""
40
+ assert _flag_param_name("--in") == "in_"
41
+
42
+
43
+ def test_non_keyword_globally():
44
+ """'globally' is NOT a keyword, should NOT get trailing underscore."""
45
+ assert _flag_param_name("--globally") == "globally"
46
+
47
+
48
+ # --- Integration test: command with --global flag ---
49
+
50
+
51
+ def test_command_with_global_flag():
52
+ """A command with a --global bool flag works end-to-end."""
53
+ app = strictcli.App(name="testapp", version="1.0.0", help="test app")
54
+ received = {}
55
+
56
+ @app.command("cmd", help="a command")
57
+ @strictcli.flag("global", type=bool, help="apply globally")
58
+ def cmd(**kwargs):
59
+ received.update(kwargs)
60
+
61
+ r = app.test(["cmd", "--global"])
62
+ assert r.exit_code == 0
63
+ assert received["global_"] is True
64
+
65
+
66
+ def test_command_with_global_flag_default():
67
+ """When --global is not passed, its default (False) uses the global_ key."""
68
+ app = strictcli.App(name="testapp", version="1.0.0", help="test app")
69
+ received = {}
70
+
71
+ @app.command("cmd", help="a command")
72
+ @strictcli.flag("global", type=bool, help="apply globally")
73
+ def cmd(**kwargs):
74
+ received.update(kwargs)
75
+
76
+ r = app.test(["cmd"])
77
+ assert r.exit_code == 0
78
+ assert received["global_"] is False
79
+
80
+
81
+ # --- Integration test: config with keyword flag ---
82
+
83
+
84
+ def test_config_key_uses_suffixed_name(tmp_path):
85
+ """Config keys for keyword flags use the suffixed name (global_)."""
86
+ # Write a config file with the suffixed key
87
+ config_dir = tmp_path / "kwtest"
88
+ config_dir.mkdir(parents=True, exist_ok=True)
89
+ config_file = config_dir / "config.json"
90
+ config_file.write_text(json.dumps({"global_": True}) + "\n")
91
+
92
+ # Set XDG_CONFIG_HOME BEFORE creating the app, since config is loaded
93
+ # during App.__init__
94
+ old_xdg = os.environ.get("XDG_CONFIG_HOME")
95
+ os.environ["XDG_CONFIG_HOME"] = str(tmp_path)
96
+ try:
97
+ app = strictcli.App(
98
+ name="kwtest",
99
+ version="1.0.0",
100
+ help="test app",
101
+ config=True,
102
+ )
103
+ received = {}
104
+
105
+ @app.command("cmd", help="a command")
106
+ @strictcli.flag("global", type=bool, help="apply globally")
107
+ def cmd(**kwargs):
108
+ received.update(kwargs)
109
+
110
+ r = app.test(["cmd"])
111
+ assert r.exit_code == 0
112
+ assert received["global_"] is True
113
+ finally:
114
+ if old_xdg is None:
115
+ del os.environ["XDG_CONFIG_HOME"]
116
+ else:
117
+ os.environ["XDG_CONFIG_HOME"] = old_xdg
@@ -0,0 +1,18 @@
1
+ # Keyword collision in `_flag_param_name`
2
+
3
+ ## Problem
4
+
5
+ `_flag_param_name` does not handle Python keyword collisions. When a CLI flag name matches a Python keyword (e.g., `--global`), the generated parameter name is an invalid Python identifier.
6
+
7
+ rlsbl currently monkey-patches `_flag_param_name` to work around this by appending `_` for keywords like `global`.
8
+
9
+ ## Fix
10
+
11
+ The fix is 2 lines in `_flag_param_name`:
12
+
13
+ 1. Add `import keyword` at the top of the module
14
+ 2. Add `if keyword.iskeyword(name): return name + "_"` to `_flag_param_name`
15
+
16
+ ## Downstream impact
17
+
18
+ Once this ships in a strictcli release, the rlsbl monkey-patch can be removed.
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.20.0"
235
+ version = "0.20.1"
236
236
  source = { editable = "." }
237
237
 
238
238
  [package.dev-dependencies]
@@ -1 +0,0 @@
1
- 019cae063cca2c44324d0c76260c72e72334b7c8
@@ -1,9 +0,0 @@
1
- ## 0.15.0
2
-
3
- ### Breaking
4
-
5
- - **Breaking.** `config set` now validates keys against registered flags and rejects unknown keys.
6
-
7
- ### Features
8
-
9
- - **New feature.** `config set` coerces values to the flag's declared type (int, bool, float) before writing to config.
@@ -1,5 +0,0 @@
1
- ## 0.18.0
2
-
3
- ### Features
4
-
5
- - **New.** Public API for running checks programmatically: `App.run_checks()`, `format_check_results()`, `format_check_results_json()`, `CheckRunResult`.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes