strictcli 0.16.1__tar.gz → 0.17.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 (151) hide show
  1. strictcli-0.17.0/.rlsbl/changes/.validated +1 -0
  2. strictcli-0.17.0/.rlsbl/changes/0.16.1.md +6 -0
  3. strictcli-0.17.0/.rlsbl/changes/0.17.0.jsonl +3 -0
  4. strictcli-0.17.0/.rlsbl/releases/v0.17.0.toml +8 -0
  5. {strictcli-0.16.1 → strictcli-0.17.0}/CHANGELOG.md +12 -2
  6. {strictcli-0.16.1 → strictcli-0.17.0}/PKG-INFO +1 -1
  7. {strictcli-0.16.1 → strictcli-0.17.0}/package-lock.json +2 -2
  8. {strictcli-0.16.1 → strictcli-0.17.0}/package.json +1 -1
  9. {strictcli-0.16.1 → strictcli-0.17.0}/pyproject.toml +1 -1
  10. {strictcli-0.16.1 → strictcli-0.17.0}/strictcli/__init__.py +37 -3
  11. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config.py +4 -5
  12. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_repeatable.py +101 -0
  13. {strictcli-0.16.1 → strictcli-0.17.0}/uv.lock +1 -1
  14. strictcli-0.16.1/.rlsbl/changes/.validated +0 -1
  15. {strictcli-0.16.1 → strictcli-0.17.0}/.claude/settings.json +0 -0
  16. {strictcli-0.16.1 → strictcli-0.17.0}/.github/workflows/ci.yml +0 -0
  17. {strictcli-0.16.1 → strictcli-0.17.0}/.github/workflows/publish.yml +0 -0
  18. {strictcli-0.16.1 → strictcli-0.17.0}/.gitignore +0 -0
  19. {strictcli-0.16.1 → strictcli-0.17.0}/.npmignore +0 -0
  20. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.claude/settings.json +0 -0
  21. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
  22. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
  23. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.gitignore +0 -0
  24. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.npmignore +0 -0
  25. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
  26. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
  27. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
  28. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
  29. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
  30. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
  31. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/CHANGELOG.md +0 -0
  32. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/CLAUDE.md +0 -0
  33. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/LICENSE +0 -0
  34. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.10.0.jsonl +0 -0
  35. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.10.0.md +0 -0
  36. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.11.0.jsonl +0 -0
  37. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.11.0.md +0 -0
  38. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.12.0.jsonl +0 -0
  39. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.12.0.md +0 -0
  40. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.13.0.jsonl +0 -0
  41. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.13.0.md +0 -0
  42. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.14.0.jsonl +0 -0
  43. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.14.0.md +0 -0
  44. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.0.jsonl +0 -0
  45. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.0.md +0 -0
  46. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.1.jsonl +0 -0
  47. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.1.md +0 -0
  48. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.0.jsonl +0 -0
  49. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.0.md +0 -0
  50. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.1.jsonl +0 -0
  51. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.0.jsonl +0 -0
  52. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.0.md +0 -0
  53. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.1.jsonl +0 -0
  54. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.1.md +0 -0
  55. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.5.0.jsonl +0 -0
  56. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.5.0.md +0 -0
  57. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.0.jsonl +0 -0
  58. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.0.md +0 -0
  59. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.1.jsonl +0 -0
  60. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.1.md +0 -0
  61. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.0.jsonl +0 -0
  62. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.0.md +0 -0
  63. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.1.jsonl +0 -0
  64. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.1.md +0 -0
  65. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.0.jsonl +0 -0
  66. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.0.md +0 -0
  67. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.1.jsonl +0 -0
  68. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.1.md +0 -0
  69. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.2.jsonl +0 -0
  70. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.2.md +0 -0
  71. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.3.jsonl +0 -0
  72. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.3.md +0 -0
  73. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.4.jsonl +0 -0
  74. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.4.md +0 -0
  75. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.5.jsonl +0 -0
  76. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.5.md +0 -0
  77. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.6.jsonl +0 -0
  78. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.6.md +0 -0
  79. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.7.jsonl +0 -0
  80. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.7.md +0 -0
  81. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.0.jsonl +0 -0
  82. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.0.md +0 -0
  83. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.1.jsonl +0 -0
  84. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.1.md +0 -0
  85. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/unreleased.jsonl +0 -0
  86. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/config.json +0 -0
  87. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hashes.json +0 -0
  88. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/post-release.sh +0 -0
  89. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/pre-checks.sh +0 -0
  90. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/pre-release.sh +0 -0
  91. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/go.toml +0 -0
  92. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/npm.toml +0 -0
  93. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/python.toml +0 -0
  94. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/unreleased.toml +0 -0
  95. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.10.0.toml +0 -0
  96. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.11.0.toml +0 -0
  97. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.12.0.toml +0 -0
  98. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.13.0.toml +0 -0
  99. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.15.0.toml +0 -0
  100. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.15.1.toml +0 -0
  101. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.16.0.toml +0 -0
  102. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.16.1.toml +0 -0
  103. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.5.toml +0 -0
  104. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.6.toml +0 -0
  105. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.7.toml +0 -0
  106. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.9.0.toml +0 -0
  107. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.9.1.toml +0 -0
  108. {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/version +0 -0
  109. {strictcli-0.16.1 → strictcli-0.17.0}/.strictcli/schema.json +0 -0
  110. {strictcli-0.16.1 → strictcli-0.17.0}/CLAUDE.md +0 -0
  111. {strictcli-0.16.1 → strictcli-0.17.0}/LICENSE +0 -0
  112. {strictcli-0.16.1 → strictcli-0.17.0}/README.md +0 -0
  113. {strictcli-0.16.1 → strictcli-0.17.0}/index.js +0 -0
  114. {strictcli-0.16.1 → strictcli-0.17.0}/postinstall.js +0 -0
  115. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_arg_default.py +0 -0
  116. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_at_prefix.py +0 -0
  117. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_auto_version.py +0 -0
  118. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_command.py +0 -0
  119. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_discovery.py +0 -0
  120. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_runner.py +0 -0
  121. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_schema.py +0 -0
  122. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_types.py +0 -0
  123. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_choices.py +0 -0
  124. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_command_help_suggestion.py +0 -0
  125. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config_file_path.py +0 -0
  126. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config_set_bugs.py +0 -0
  127. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_deep_nesting.py +0 -0
  128. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_dependencies.py +0 -0
  129. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_deprecated.py +0 -0
  130. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_dump_schema.py +0 -0
  131. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_e2e.py +0 -0
  132. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_env.py +0 -0
  133. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_exit_codes.py +0 -0
  134. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_float_type.py +0 -0
  135. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_global_flags.py +0 -0
  136. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_help.py +0 -0
  137. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_int_type.py +0 -0
  138. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_mutex.py +0 -0
  139. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_nesting.py +0 -0
  140. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_parser.py +0 -0
  141. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_passthrough.py +0 -0
  142. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_registration.py +0 -0
  143. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_tagdsl.py +0 -0
  144. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_tags.py +0 -0
  145. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_toml_loading.py +0 -0
  146. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_unique.py +0 -0
  147. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_utilities.py +0 -0
  148. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_validate.py +0 -0
  149. {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_variadic.py +0 -0
  150. {strictcli-0.16.1 → strictcli-0.17.0}/todo/.defer/deferred.md +0 -0
  151. {strictcli-0.16.1 → strictcli-0.17.0}/todo/.done/original-idea.md +0 -0
@@ -0,0 +1 @@
1
+ 912fca00b39e373782d5c0b88d65d2067f89a1b1
@@ -0,0 +1,6 @@
1
+ ## 0.16.1
2
+
3
+ ### Fixes
4
+
5
+ - **Fix.** `config set` mutex error messages now include the `config set:` prefix for consistency.
6
+ - **Fix.** Config set help text now mentions backslash escaping for values containing commas in repeatable flags.
@@ -0,0 +1,3 @@
1
+ {"commits":["5660d554d8a5dbb47bb6872d8513ba497fb04e7f"],"user_facing":false}
2
+ {"commits":["ba0ca0dd03644fa63f6ca6525ce81d814100a7a1"],"user_facing":true,"description":"**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.","type":"breaking"}
3
+ {"commits":["47de2e49ebd7248a5b16a54860ec9cdf1e5e98e9"],"user_facing":true,"description":"**New.** Help text now displays non-empty repeatable flag defaults as `[default: a, b, c]`.","type":"feature"}
@@ -0,0 +1,8 @@
1
+ # Version bump type: patch, minor, or major
2
+ bump = "minor"
3
+ # Short description of this release (required)
4
+ description = "Repeatable flag default validation and help text display"
5
+ # Optional context explaining why these changes were made
6
+ context = ""
7
+ include = ["pypi", "npm"]
8
+ exclude = []
@@ -2,9 +2,19 @@
2
2
 
3
3
  # Changelog
4
4
 
5
- ## 0.16.1
5
+ ## 0.17.0
6
+
7
+ Repeatable flag default validation and help text display
8
+
9
+ ### Breaking
10
+
11
+ - **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.
6
12
 
7
- Error message fixes and help text improvements
13
+ ### Features
14
+
15
+ - **New.** Help text now displays non-empty repeatable flag defaults as `[default: a, b, c]`.
16
+
17
+ ## 0.16.1
8
18
 
9
19
  ### Fixes
10
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.16.1
3
+ Version: 0.17.0
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.16.1",
3
+ "version": "0.17.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.16.1",
9
+ "version": "0.17.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
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.16.1"
7
+ version = "0.17.0"
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.16.1"
5
+ __version__ = "0.17.0"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "Tag", "MutexGroup", "CoRequired", "Requires",
@@ -588,6 +588,38 @@ class Flag:
588
588
  raise ValueError(
589
589
  f'Flag "{self.name}": choice {c!r} is not of type {self.type.__name__}'
590
590
  )
591
+ # Validate repeatable flag defaults
592
+ if self.repeatable and not isinstance(self.default, _MissingSentinel):
593
+ if self.default is not None:
594
+ if not isinstance(self.default, list):
595
+ raise ValueError(
596
+ f'Flag "{self.name}": repeatable flag default must be a list'
597
+ )
598
+ if len(self.default) == 0:
599
+ raise ValueError(
600
+ f'Flag "{self.name}": explicit empty default is redundant '
601
+ f"for repeatable flags, omit the default"
602
+ )
603
+ # Validate element types
604
+ type_name = {str: "str", int: "int", float: "float"}[self.type]
605
+ for i, elem in enumerate(self.default):
606
+ if self.type is str:
607
+ if not isinstance(elem, str):
608
+ raise ValueError(
609
+ f'Flag "{self.name}": default element {i} is not of type {type_name}'
610
+ )
611
+ elif self.type is int:
612
+ if not isinstance(elem, int) or isinstance(elem, bool):
613
+ raise ValueError(
614
+ f'Flag "{self.name}": default element {i} is not of type {type_name}'
615
+ )
616
+ elif self.type is float:
617
+ if not isinstance(elem, (int, float)) or isinstance(elem, bool):
618
+ raise ValueError(
619
+ f'Flag "{self.name}": default element {i} is not of type {type_name}'
620
+ )
621
+ if isinstance(elem, int):
622
+ self.default[i] = float(elem)
591
623
  # Validate default type for int flags
592
624
  if self.type is int and not isinstance(self.default, _MissingSentinel) and self.default is not None:
593
625
  if not self.repeatable and not isinstance(self.default, int):
@@ -2904,8 +2936,10 @@ def _build_flag_meta(f: Flag) -> str:
2904
2936
  if f.type is bool:
2905
2937
  meta_parts.append(f"default: {'true' if f.default else 'false'}")
2906
2938
  elif f.repeatable:
2907
- # Repeatable flags are never required; no default shown
2908
- pass
2939
+ # Repeatable flags are never required; show default only if non-empty
2940
+ if f.default:
2941
+ joined = ", ".join(_format_value_for_error(elem) for elem in f.default)
2942
+ meta_parts.append(f"default: {joined}")
2909
2943
  elif f.default is not None:
2910
2944
  meta_parts.append(f"default: {f.default}")
2911
2945
  else:
@@ -1114,14 +1114,13 @@ def _make_config_set_app(config_path=None, config_format="json"):
1114
1114
  )
1115
1115
 
1116
1116
  @app.command("run", help="run something")
1117
- @strictcli.flag("tags", type=str, help="tags", repeatable=True, unique=False,
1118
- default=[])
1117
+ @strictcli.flag("tags", type=str, help="tags", repeatable=True, unique=False)
1119
1118
  @strictcli.flag("counts", type=int, help="counts", repeatable=True,
1120
- unique=False, default=[])
1119
+ unique=False)
1121
1120
  @strictcli.flag("rates", type=float, help="rates", repeatable=True,
1122
- unique=False, default=[])
1121
+ unique=False)
1123
1122
  @strictcli.flag("ids", type=int, help="unique ids", repeatable=True,
1124
- unique=True, default=[])
1123
+ unique=True)
1125
1124
  @strictcli.flag("name", type=str, help="name", default="default")
1126
1125
  def run(tags, counts, rates, ids, name):
1127
1126
  print(f"tags={tags} counts={counts} rates={rates} ids={ids} name={name}")
@@ -534,3 +534,104 @@ def test_env_separator_float_nan_error(monkeypatch):
534
534
  r = app.test(["cmd"])
535
535
  assert r.exit_code == 1
536
536
  assert "--rate: NaN is not allowed (from env var 'RATES')" in r.stderr
537
+
538
+
539
+ # --- Repeatable flag default validation ---
540
+
541
+
542
+ def test_repeatable_default_must_be_list():
543
+ """Repeatable flag with non-list default raises ValueError."""
544
+ with pytest.raises(ValueError, match='Flag "record": repeatable flag default must be a list'):
545
+ _make_app_with_repeatable(type=str, default="not a list")
546
+
547
+
548
+ def test_repeatable_default_empty_list_error():
549
+ """Repeatable flag with explicit default=[] raises ValueError."""
550
+ with pytest.raises(
551
+ ValueError,
552
+ match='Flag "record": explicit empty default is redundant for repeatable flags, omit the default',
553
+ ):
554
+ _make_app_with_repeatable(type=str, default=[])
555
+
556
+
557
+ def test_repeatable_default_wrong_element_type_str():
558
+ """Repeatable str flag with default=[1] raises ValueError at element 0."""
559
+ with pytest.raises(ValueError, match='Flag "record": default element 0 is not of type str'):
560
+ _make_app_with_repeatable(type=str, default=[1])
561
+
562
+
563
+ def test_repeatable_default_wrong_element_type_int():
564
+ """Repeatable int flag with default=["x"] raises ValueError."""
565
+ with pytest.raises(ValueError, match='Flag "record": default element 0 is not of type int'):
566
+ _make_app_with_repeatable(type=int, default=["x"])
567
+
568
+
569
+ def test_repeatable_default_int_coerced_to_float():
570
+ """Repeatable float flag with default=[1, 2] coerces to [1.0, 2.0]."""
571
+ app = strictcli.App(name="test", version="1.0.0", help="test app")
572
+
573
+ @app.command("cmd", help="a command")
574
+ @strictcli.flag("val", type=float, help="a value", repeatable=True, unique=False,
575
+ default=[1, 2])
576
+ def cmd(val):
577
+ print(f"val={val!r}")
578
+
579
+ # The flag object should have coerced defaults
580
+ flag_obj = app._commands["cmd"].flags[0]
581
+ assert flag_obj.default == [1.0, 2.0]
582
+ assert all(isinstance(x, float) for x in flag_obj.default)
583
+
584
+
585
+ def test_repeatable_default_valid():
586
+ """Repeatable flag with valid non-empty default succeeds."""
587
+ app = _make_app_with_repeatable(type=str, default=["a", "b"])
588
+ r = app.test(["cmd"])
589
+ assert r.exit_code == 0
590
+ assert "record=['a', 'b']" in r.stdout
591
+
592
+
593
+ def test_repeatable_default_applied():
594
+ """Regression guard: repeatable flag default is applied when no CLI/env value."""
595
+ app = strictcli.App(name="test", version="1.0.0", help="test app")
596
+
597
+ @app.command("cmd", help="a command")
598
+ @strictcli.flag("tag", help="a tag", repeatable=True, unique=False,
599
+ default=["a", "b"])
600
+ def cmd(tag):
601
+ print(f"tag={tag!r}")
602
+
603
+ r = app.test(["cmd"])
604
+ assert r.exit_code == 0
605
+ assert "tag=['a', 'b']" in r.stdout
606
+
607
+
608
+ def test_repeatable_default_shown_in_help():
609
+ """Repeatable flag with non-empty default shows it in help text."""
610
+ app = _make_app_with_repeatable(type=str, default=["x", "y"])
611
+ r = app.test(["cmd", "--help"])
612
+ assert r.exit_code == 0
613
+ assert "[default: x, y]" in r.stdout
614
+
615
+
616
+ def test_repeatable_no_default_not_in_help():
617
+ """Repeatable flag with no explicit default shows no default in help."""
618
+ app = _make_app_with_repeatable(type=str)
619
+ r = app.test(["cmd", "--help"])
620
+ assert r.exit_code == 0
621
+ assert "[default" not in r.stdout
622
+
623
+
624
+ def test_repeatable_default_int_in_help():
625
+ """Repeatable int flag with default shows formatted ints in help."""
626
+ app = _make_app_with_repeatable(type=int, default=[1, 2, 3])
627
+ r = app.test(["cmd", "--help"])
628
+ assert r.exit_code == 0
629
+ assert "[default: 1, 2, 3]" in r.stdout
630
+
631
+
632
+ def test_repeatable_default_float_in_help():
633
+ """Repeatable float flag with default shows formatted floats in help."""
634
+ app = _make_app_with_repeatable(type=float, default=[1.5, 2.5])
635
+ r = app.test(["cmd", "--help"])
636
+ assert r.exit_code == 0
637
+ assert "[default: 1.5, 2.5]" in r.stdout
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.16.1"
235
+ version = "0.17.0"
236
236
  source = { editable = "." }
237
237
 
238
238
  [package.dev-dependencies]
@@ -1 +0,0 @@
1
- 2a117a0d39aebe26c1f73df78aecb99c81ee2128
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