strictcli 0.15.0__tar.gz → 0.15.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 (143) hide show
  1. strictcli-0.15.1/.rlsbl/changes/.validated +1 -0
  2. strictcli-0.15.1/.rlsbl/changes/0.15.0.md +9 -0
  3. strictcli-0.15.1/.rlsbl/changes/0.15.1.jsonl +6 -0
  4. strictcli-0.15.1/.rlsbl/changes/0.15.1.md +9 -0
  5. strictcli-0.15.1/.rlsbl/config.json +12 -0
  6. strictcli-0.15.1/.rlsbl/releases/v0.15.1.toml +8 -0
  7. strictcli-0.15.1/.rlsbl/version +1 -0
  8. {strictcli-0.15.0 → strictcli-0.15.1}/CHANGELOG.md +13 -3
  9. {strictcli-0.15.0 → strictcli-0.15.1}/PKG-INFO +1 -1
  10. {strictcli-0.15.0 → strictcli-0.15.1}/package-lock.json +2 -2
  11. {strictcli-0.15.0 → strictcli-0.15.1}/package.json +1 -1
  12. {strictcli-0.15.0 → strictcli-0.15.1}/pyproject.toml +1 -1
  13. {strictcli-0.15.0 → strictcli-0.15.1}/strictcli/__init__.py +65 -62
  14. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_config.py +28 -1
  15. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_config_set_bugs.py +24 -0
  16. {strictcli-0.15.0 → strictcli-0.15.1}/uv.lock +1 -1
  17. strictcli-0.15.0/.rlsbl/changes/.validated +0 -1
  18. strictcli-0.15.0/.rlsbl/config.json +0 -8
  19. strictcli-0.15.0/.rlsbl/version +0 -1
  20. {strictcli-0.15.0 → strictcli-0.15.1}/.claude/settings.json +0 -0
  21. {strictcli-0.15.0 → strictcli-0.15.1}/.github/workflows/ci.yml +0 -0
  22. {strictcli-0.15.0 → strictcli-0.15.1}/.github/workflows/publish.yml +0 -0
  23. {strictcli-0.15.0 → strictcli-0.15.1}/.gitignore +0 -0
  24. {strictcli-0.15.0 → strictcli-0.15.1}/.npmignore +0 -0
  25. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.claude/settings.json +0 -0
  26. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
  27. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
  28. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.gitignore +0 -0
  29. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.npmignore +0 -0
  30. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
  31. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
  32. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
  33. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
  34. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
  35. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
  36. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/CHANGELOG.md +0 -0
  37. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/CLAUDE.md +0 -0
  38. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/bases/LICENSE +0 -0
  39. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.10.0.jsonl +0 -0
  40. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.10.0.md +0 -0
  41. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.11.0.jsonl +0 -0
  42. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.11.0.md +0 -0
  43. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.12.0.jsonl +0 -0
  44. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.12.0.md +0 -0
  45. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.13.0.jsonl +0 -0
  46. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.13.0.md +0 -0
  47. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.14.0.jsonl +0 -0
  48. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.14.0.md +0 -0
  49. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.15.0.jsonl +0 -0
  50. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.4.0.jsonl +0 -0
  51. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.4.0.md +0 -0
  52. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.4.1.jsonl +0 -0
  53. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.4.1.md +0 -0
  54. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.5.0.jsonl +0 -0
  55. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.5.0.md +0 -0
  56. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.6.0.jsonl +0 -0
  57. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.6.0.md +0 -0
  58. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.6.1.jsonl +0 -0
  59. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.6.1.md +0 -0
  60. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.7.0.jsonl +0 -0
  61. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.7.0.md +0 -0
  62. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.7.1.jsonl +0 -0
  63. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.7.1.md +0 -0
  64. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.0.jsonl +0 -0
  65. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.0.md +0 -0
  66. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.1.jsonl +0 -0
  67. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.1.md +0 -0
  68. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.2.jsonl +0 -0
  69. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.2.md +0 -0
  70. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.3.jsonl +0 -0
  71. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.3.md +0 -0
  72. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.4.jsonl +0 -0
  73. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.4.md +0 -0
  74. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.5.jsonl +0 -0
  75. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.5.md +0 -0
  76. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.6.jsonl +0 -0
  77. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.6.md +0 -0
  78. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.7.jsonl +0 -0
  79. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.8.7.md +0 -0
  80. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.9.0.jsonl +0 -0
  81. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.9.0.md +0 -0
  82. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.9.1.jsonl +0 -0
  83. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/0.9.1.md +0 -0
  84. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/changes/unreleased.jsonl +0 -0
  85. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/hashes.json +0 -0
  86. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/hooks/post-release.sh +0 -0
  87. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/hooks/pre-checks.sh +0 -0
  88. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/hooks/pre-release.sh +0 -0
  89. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/lint/go.toml +0 -0
  90. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/lint/npm.toml +0 -0
  91. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/lint/python.toml +0 -0
  92. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/unreleased.toml +0 -0
  93. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.10.0.toml +0 -0
  94. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.11.0.toml +0 -0
  95. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.12.0.toml +0 -0
  96. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.13.0.toml +0 -0
  97. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.15.0.toml +0 -0
  98. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.8.5.toml +0 -0
  99. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.8.6.toml +0 -0
  100. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.8.7.toml +0 -0
  101. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.9.0.toml +0 -0
  102. {strictcli-0.15.0 → strictcli-0.15.1}/.rlsbl/releases/v0.9.1.toml +0 -0
  103. {strictcli-0.15.0 → strictcli-0.15.1}/.strictcli/schema.json +0 -0
  104. {strictcli-0.15.0 → strictcli-0.15.1}/CLAUDE.md +0 -0
  105. {strictcli-0.15.0 → strictcli-0.15.1}/LICENSE +0 -0
  106. {strictcli-0.15.0 → strictcli-0.15.1}/README.md +0 -0
  107. {strictcli-0.15.0 → strictcli-0.15.1}/index.js +0 -0
  108. {strictcli-0.15.0 → strictcli-0.15.1}/postinstall.js +0 -0
  109. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_arg_default.py +0 -0
  110. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_at_prefix.py +0 -0
  111. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_auto_version.py +0 -0
  112. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_check_command.py +0 -0
  113. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_check_discovery.py +0 -0
  114. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_check_runner.py +0 -0
  115. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_check_schema.py +0 -0
  116. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_check_types.py +0 -0
  117. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_choices.py +0 -0
  118. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_command_help_suggestion.py +0 -0
  119. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_config_file_path.py +0 -0
  120. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_deep_nesting.py +0 -0
  121. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_dependencies.py +0 -0
  122. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_deprecated.py +0 -0
  123. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_dump_schema.py +0 -0
  124. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_e2e.py +0 -0
  125. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_env.py +0 -0
  126. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_exit_codes.py +0 -0
  127. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_float_type.py +0 -0
  128. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_global_flags.py +0 -0
  129. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_help.py +0 -0
  130. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_int_type.py +0 -0
  131. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_mutex.py +0 -0
  132. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_nesting.py +0 -0
  133. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_parser.py +0 -0
  134. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_passthrough.py +0 -0
  135. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_registration.py +0 -0
  136. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_repeatable.py +0 -0
  137. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_tagdsl.py +0 -0
  138. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_tags.py +0 -0
  139. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_toml_loading.py +0 -0
  140. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_validate.py +0 -0
  141. {strictcli-0.15.0 → strictcli-0.15.1}/tests/test_variadic.py +0 -0
  142. {strictcli-0.15.0 → strictcli-0.15.1}/todo/.defer/deferred.md +0 -0
  143. {strictcli-0.15.0 → strictcli-0.15.1}/todo/.done/original-idea.md +0 -0
@@ -0,0 +1 @@
1
+ 4df925097e74f9a99e5ece43f3df2cd90f7b3e17
@@ -0,0 +1,9 @@
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.
@@ -0,0 +1,6 @@
1
+ {"commits":["face7b1fcf296fcd3ba1d5d7b15194c71f79ac0c"],"user_facing":true,"description":"**Breaking.** Config set now accepts negative numeric values. Unknown single-dash tokens produce 'unexpected argument' instead of 'unknown flag'.","type":"breaking"}
2
+ {"commits":["d4bbb758e89f174279d3d1a281ded985c47c958a"],"user_facing":true,"description":"**Fix.** Config show now outputs lowercase true/false for bool values, matching Go implementation.","type":"fix"}
3
+ {"commits":["b2bdcfa7388ac5904c5da98a017c4248cc2737ea"],"user_facing":false}
4
+ {"commits":["053285501d0c135dffc782f038ac4f35881bcd89"],"user_facing":false}
5
+ {"commits":["48504dbc7255e89a73c663f4745eaf7a64ec0466"],"user_facing":false}
6
+ {"commits":["ca62cef3775568ecda2e7bdadf04ee813608d9d6"],"user_facing":false}
@@ -0,0 +1,9 @@
1
+ ## 0.15.1
2
+
3
+ ### Breaking
4
+
5
+ - **Breaking.** Config set now accepts negative numeric values. Unknown single-dash tokens produce 'unexpected argument' instead of 'unknown flag'.
6
+
7
+ ### Fixes
8
+
9
+ - **Fix.** Config show now outputs lowercase true/false for bool values, matching Go implementation.
@@ -0,0 +1,12 @@
1
+ {
2
+ "targets": [
3
+ "pypi",
4
+ "npm"
5
+ ],
6
+ "private": false,
7
+ "push_timeout": 300,
8
+ "pipelines": {
9
+ "pypi": {"type": "pypi", "local": false},
10
+ "npm": {"type": "npm", "local": false}
11
+ }
12
+ }
@@ -0,0 +1,8 @@
1
+ # Version bump type: patch, minor, or major
2
+ bump = "patch"
3
+ # Short description of this release (required)
4
+ description = "Config show bool parity and negative values in config set"
5
+ # Optional context explaining why these changes were made
6
+ context = "config show now outputs lowercase true/false matching Go. config set accepts negative numeric values."
7
+ include = ["pypi", "npm"]
8
+ exclude = []
@@ -0,0 +1 @@
1
+ 0.63.0
@@ -2,19 +2,29 @@
2
2
 
3
3
  # Changelog
4
4
 
5
- ## 0.15.0
5
+ ## 0.15.1
6
6
 
7
- Config set type coercion and key validation
7
+ Config show bool parity and negative values in config set
8
8
 
9
9
  <details>
10
10
  <summary>Context</summary>
11
11
 
12
- 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.
12
+ config show now outputs lowercase true/false matching Go. config set accepts negative numeric values.
13
13
 
14
14
  </details>
15
15
 
16
16
  ### Breaking
17
17
 
18
+ - **Breaking.** Config set now accepts negative numeric values. Unknown single-dash tokens produce 'unexpected argument' instead of 'unknown flag'.
19
+
20
+ ### Fixes
21
+
22
+ - **Fix.** Config show now outputs lowercase true/false for bool values, matching Go implementation.
23
+
24
+ ## 0.15.0
25
+
26
+ ### Breaking
27
+
18
28
  - **Breaking.** `config set` now validates keys against registered flags and rejects unknown keys.
19
29
 
20
30
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.15.0
3
+ Version: 0.15.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.15.0",
3
+ "version": "0.15.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.15.0",
9
+ "version": "0.15.1",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.15.0",
3
+ "version": "0.15.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.15.0"
7
+ version = "0.15.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.15.0"
5
+ __version__ = "0.15.1"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "Tag", "MutexGroup", "CoRequired", "Requires",
@@ -125,6 +125,15 @@ def _coerce_config_value(value: object, flag: "Flag") -> object:
125
125
  raise ValueError(f"unsupported flag type {flag.type}")
126
126
 
127
127
 
128
+ def _format_config_value(value: object) -> str:
129
+ """Format a config value for display, matching Go's formatConfigValue."""
130
+ if value is None:
131
+ return "<nil>"
132
+ if isinstance(value, bool):
133
+ return "true" if value else "false"
134
+ return str(value)
135
+
136
+
128
137
  _CHECK_NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
129
138
  _CHECK_REQUIRED_FIELDS = {"tags", "severity", "fast", "pure", "needs_network", "depends_on"}
130
139
  _CHECK_VALID_SEVERITIES = {"error", "warn"}
@@ -306,12 +315,18 @@ def _strict_int(s: str) -> int:
306
315
  Python's int() silently strips whitespace; Go's strconv.Atoi does not.
307
316
  This matches Go's stricter behavior. Additionally, the result is
308
317
  range-checked to fit in a signed 64-bit integer, matching Go's int/int64.
318
+
319
+ All errors raise ValueError with the same message format as Go's
320
+ parseIntStrict: "expected integer, got '<value>'".
309
321
  """
310
322
  if s != s.strip():
311
- raise ValueError(f"invalid literal for int() with base 10: {s!r}")
312
- n = int(s)
323
+ raise ValueError(f"expected integer, got '{s}'")
324
+ try:
325
+ n = int(s)
326
+ except ValueError:
327
+ raise ValueError(f"expected integer, got '{s}'") from None
313
328
  if n < -(2**63) or n > 2**63 - 1:
314
- raise ValueError("integer out of range")
329
+ raise ValueError(f"expected integer, got '{s}'")
315
330
  return n
316
331
 
317
332
 
@@ -1020,7 +1035,7 @@ class App:
1020
1035
  else:
1021
1036
  value = None
1022
1037
  source = "default"
1023
- print(f"{param} = {value} (source: {source})")
1038
+ print(f"{param} = {_format_config_value(value)} (source: {source})")
1024
1039
 
1025
1040
  config_grp.commands["show"] = Command(
1026
1041
  name="show",
@@ -1060,10 +1075,7 @@ class App:
1060
1075
  if matched_flag.type == bool:
1061
1076
  typed_value = _strict_bool(value)
1062
1077
  elif matched_flag.type == int:
1063
- try:
1064
- typed_value = _strict_int(value)
1065
- except ValueError:
1066
- raise ValueError(f"expected integer, got '{value}'")
1078
+ typed_value = _strict_int(value)
1067
1079
  elif matched_flag.type == float:
1068
1080
  try:
1069
1081
  typed_value = _strict_float(value)
@@ -1295,10 +1307,8 @@ class App:
1295
1307
  if f.type is int:
1296
1308
  try:
1297
1309
  _store_value(f, _strict_int(value_part))
1298
- except ValueError:
1299
- raise _ParseError(
1300
- f"--{f.name}: expected integer, got {value_part!r}"
1301
- )
1310
+ except ValueError as e:
1311
+ raise _ParseError(f"--{f.name}: {e}")
1302
1312
  elif f.type is float:
1303
1313
  try:
1304
1314
  _store_value(f, _strict_float(value_part))
@@ -1339,10 +1349,8 @@ class App:
1339
1349
  if f.type is int:
1340
1350
  try:
1341
1351
  _store_value(f, _strict_int(raw))
1342
- except ValueError:
1343
- raise _ParseError(
1344
- f"--{f.name}: expected integer, got {raw!r}"
1345
- )
1352
+ except ValueError as e:
1353
+ raise _ParseError(f"--{f.name}: {e}")
1346
1354
  elif f.type is float:
1347
1355
  try:
1348
1356
  _store_value(f, _strict_float(raw))
@@ -1370,10 +1378,8 @@ class App:
1370
1378
  if f.type is int:
1371
1379
  try:
1372
1380
  _store_value(f, _strict_int(raw))
1373
- except ValueError:
1374
- raise _ParseError(
1375
- f"--{f.name}: expected integer, got {raw!r}"
1376
- )
1381
+ except ValueError as e:
1382
+ raise _ParseError(f"--{f.name}: {e}")
1377
1383
  elif f.type is float:
1378
1384
  try:
1379
1385
  _store_value(f, _strict_float(raw))
@@ -1414,10 +1420,9 @@ class App:
1414
1420
  elif f.type is int:
1415
1421
  try:
1416
1422
  coerced = _strict_int(env_val)
1417
- except ValueError:
1423
+ except ValueError as e:
1418
1424
  raise _ParseError(
1419
- f"--{f.name}: expected integer, got {env_val!r} "
1420
- f"(from env var '{f.env}')"
1425
+ f"--{f.name}: {e} (from env var '{f.env}')"
1421
1426
  )
1422
1427
  cli_set[f.name] = [coerced] if f.repeatable else coerced
1423
1428
  elif f.type is float:
@@ -1694,8 +1699,8 @@ def _parse_command(
1694
1699
  if f.type is int:
1695
1700
  try:
1696
1701
  _store_value(f, _strict_int(value_part))
1697
- except ValueError:
1698
- raise _ParseError(f"--{f.name}: expected integer, got {value_part!r}")
1702
+ except ValueError as e:
1703
+ raise _ParseError(f"--{f.name}: {e}")
1699
1704
  elif f.type is float:
1700
1705
  try:
1701
1706
  _store_value(f, _strict_float(value_part))
@@ -1736,8 +1741,8 @@ def _parse_command(
1736
1741
  if f.type is int:
1737
1742
  try:
1738
1743
  _store_value(f, _strict_int(raw))
1739
- except ValueError:
1740
- raise _ParseError(f"--{f.name}: expected integer, got {raw!r}")
1744
+ except ValueError as e:
1745
+ raise _ParseError(f"--{f.name}: {e}")
1741
1746
  elif f.type is float:
1742
1747
  try:
1743
1748
  _store_value(f, _strict_float(raw))
@@ -1756,40 +1761,39 @@ def _parse_command(
1756
1761
  continue
1757
1762
 
1758
1763
  # -x (short form)
1759
- if tok.startswith("-") and len(tok) == 2:
1760
- if tok in short_lookup:
1761
- f = short_lookup[tok]
1762
- if f.type is bool:
1763
- cli_set[f.name] = True
1764
- i += 1
1765
- else:
1766
- # str/int/float flag: consume next token as value
1767
- if i + 1 < len(tokens):
1768
- raw = tokens[i + 1]
1769
- if f.type is int:
1770
- try:
1771
- _store_value(f, _strict_int(raw))
1772
- except ValueError:
1773
- raise _ParseError(f"--{f.name}: expected integer, got {raw!r}")
1774
- elif f.type is float:
1775
- try:
1776
- _store_value(f, _strict_float(raw))
1777
- except ValueError as e:
1778
- raise _float_parse_error(f.name, raw, e)
1779
- else:
1780
- resolved, stdin_consumed_by[0] = _resolve_at_prefix(
1781
- f.name, raw, stdin_consumed_by[0],
1782
- )
1783
- _store_value(f, resolved)
1784
- i += 2
1785
- else:
1786
- raise _ParseError(f"flag '{tok}' requires a value")
1764
+ if tok.startswith("-") and len(tok) == 2 and tok in short_lookup:
1765
+ f = short_lookup[tok]
1766
+ if f.type is bool:
1767
+ cli_set[f.name] = True
1768
+ i += 1
1787
1769
  else:
1788
- raise _ParseError(f"unknown flag '{tok}'")
1770
+ # str/int/float flag: consume next token as value
1771
+ if i + 1 < len(tokens):
1772
+ raw = tokens[i + 1]
1773
+ if f.type is int:
1774
+ try:
1775
+ _store_value(f, _strict_int(raw))
1776
+ except ValueError as e:
1777
+ raise _ParseError(f"--{f.name}: {e}")
1778
+ elif f.type is float:
1779
+ try:
1780
+ _store_value(f, _strict_float(raw))
1781
+ except ValueError as e:
1782
+ raise _float_parse_error(f.name, raw, e)
1783
+ else:
1784
+ resolved, stdin_consumed_by[0] = _resolve_at_prefix(
1785
+ f.name, raw, stdin_consumed_by[0],
1786
+ )
1787
+ _store_value(f, resolved)
1788
+ i += 2
1789
+ else:
1790
+ raise _ParseError(f"flag '{tok}' requires a value")
1789
1791
  continue
1790
1792
 
1791
- # Unknown flag-like token
1792
- raise _ParseError(f"unknown flag '{tok}'")
1793
+ # Token starts with "-" but doesn't match any known flag;
1794
+ # treat as a positional arg (e.g. negative numbers like -7, -3.14)
1795
+ positionals.append(tok)
1796
+ i += 1
1793
1797
 
1794
1798
  # Step 4: resolve env vars for flags not set by CLI
1795
1799
  for f in cmd.flags:
@@ -1809,10 +1813,9 @@ def _parse_command(
1809
1813
  elif f.type is int:
1810
1814
  try:
1811
1815
  coerced = _strict_int(env_val)
1812
- except ValueError:
1816
+ except ValueError as e:
1813
1817
  raise _ParseError(
1814
- f"--{f.name}: expected integer, got {env_val!r} "
1815
- f"(from env var '{f.env}')"
1818
+ f"--{f.name}: {e} (from env var '{f.env}')"
1816
1819
  )
1817
1820
  cli_set[f.name] = [coerced] if f.repeatable else coerced
1818
1821
  elif f.type is float:
@@ -183,7 +183,34 @@ def test_config_show(tmp_path, monkeypatch):
183
183
  assert r.exit_code == 0
184
184
  assert "target = from-config (source: config)" in r.stdout
185
185
  assert "count = 1 (source: default)" in r.stdout
186
- assert "verbose = False (source: default)" in r.stdout
186
+ assert "verbose = false (source: default)" in r.stdout
187
+
188
+
189
+ # --- Test 10b: config show formats bools as lowercase (Go parity) ---
190
+
191
+ def test_config_show_lowercase_bools(tmp_path, monkeypatch):
192
+ """Config show formats bools as lowercase true/false, matching Go output."""
193
+ config_home = _write_config(tmp_path, "testapp", {"verbose": True})
194
+ monkeypatch.setenv("XDG_CONFIG_HOME", config_home)
195
+ app = _make_config_app(config=True)
196
+ r = app.test(["config", "show"])
197
+ assert r.exit_code == 0
198
+ # Bool from config must be lowercase
199
+ assert "verbose = true (source: config)" in r.stdout
200
+ # None (no default for bool) must show as <nil>
201
+ assert "verbose = True" not in r.stdout
202
+ assert "verbose = False" not in r.stdout
203
+
204
+
205
+ def test_config_show_bool_false_default_lowercase(tmp_path, monkeypatch):
206
+ """Config show formats default False bool as lowercase false."""
207
+ monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
208
+ app = _make_config_app(config=True)
209
+ r = app.test(["config", "show"])
210
+ assert r.exit_code == 0
211
+ # verbose has no explicit default, defaults to False -- must be lowercase
212
+ assert "verbose = false (source: default)" in r.stdout
213
+ assert "verbose = False" not in r.stdout
187
214
 
188
215
 
189
216
  # --- Test 11: config respects XDG_CONFIG_HOME ---
@@ -198,6 +198,30 @@ def test_config_set_unknown_key_error(tmp_path):
198
198
  assert r.stderr.strip() == "config set: unknown key 'nonexistent_flag'"
199
199
 
200
200
 
201
+ def test_config_set_negative_int(tmp_path):
202
+ """config set accepts negative integers like -7 as positional values."""
203
+ app, config_file = _make_app(tmp_path)
204
+
205
+ r = app.test(["config", "set", "count", "-7"])
206
+ assert r.exit_code == 0, f"exit_code={r.exit_code}, stderr={r.stderr!r}"
207
+
208
+ data = json.loads(config_file.read_text())
209
+ assert isinstance(data["count"], int), f"expected int, got {type(data['count']).__name__}"
210
+ assert data["count"] == -7
211
+
212
+
213
+ def test_config_set_negative_float(tmp_path):
214
+ """config set accepts negative floats like -3.14 as positional values."""
215
+ app, config_file = _make_app(tmp_path)
216
+
217
+ r = app.test(["config", "set", "rate", "-3.14"])
218
+ assert r.exit_code == 0, f"exit_code={r.exit_code}, stderr={r.stderr!r}"
219
+
220
+ data = json.loads(config_file.read_text())
221
+ assert isinstance(data["rate"], float), f"expected float, got {type(data['rate']).__name__}"
222
+ assert data["rate"] == -3.14
223
+
224
+
201
225
  def test_config_set_round_trip_typed(tmp_path):
202
226
  """Values set via config set are read back with correct types by a new app."""
203
227
  app, config_file = _make_app(tmp_path)
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.15.0"
235
+ version = "0.15.1"
236
236
  source = { editable = "." }
237
237
 
238
238
  [package.dev-dependencies]
@@ -1 +0,0 @@
1
- a6dff298b7a2dad7fa928b088eeb4f3f9c0b2cf5
@@ -1,8 +0,0 @@
1
- {
2
- "targets": [
3
- "pypi",
4
- "npm"
5
- ],
6
- "private": false,
7
- "push_timeout": 300
8
- }
@@ -1 +0,0 @@
1
- 0.60.1
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