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.
- strictcli-0.17.0/.rlsbl/changes/.validated +1 -0
- strictcli-0.17.0/.rlsbl/changes/0.16.1.md +6 -0
- strictcli-0.17.0/.rlsbl/changes/0.17.0.jsonl +3 -0
- strictcli-0.17.0/.rlsbl/releases/v0.17.0.toml +8 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/CHANGELOG.md +12 -2
- {strictcli-0.16.1 → strictcli-0.17.0}/PKG-INFO +1 -1
- {strictcli-0.16.1 → strictcli-0.17.0}/package-lock.json +2 -2
- {strictcli-0.16.1 → strictcli-0.17.0}/package.json +1 -1
- {strictcli-0.16.1 → strictcli-0.17.0}/pyproject.toml +1 -1
- {strictcli-0.16.1 → strictcli-0.17.0}/strictcli/__init__.py +37 -3
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config.py +4 -5
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_repeatable.py +101 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/uv.lock +1 -1
- strictcli-0.16.1/.rlsbl/changes/.validated +0 -1
- {strictcli-0.16.1 → strictcli-0.17.0}/.claude/settings.json +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.github/workflows/ci.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.github/workflows/publish.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.gitignore +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.npmignore +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.claude/settings.json +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.gitignore +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.npmignore +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/CHANGELOG.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/CLAUDE.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/bases/LICENSE +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.10.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.10.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.11.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.11.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.12.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.12.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.13.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.13.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.14.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.14.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.15.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.16.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.4.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.5.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.5.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.6.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.7.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.2.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.2.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.3.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.3.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.4.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.4.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.5.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.5.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.6.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.6.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.7.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.8.7.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/0.9.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/changes/unreleased.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/config.json +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hashes.json +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/unreleased.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.10.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.11.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.12.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.13.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.15.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.15.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.16.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.16.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.5.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.6.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.8.7.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.9.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/releases/v0.9.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.rlsbl/version +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/.strictcli/schema.json +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/CLAUDE.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/LICENSE +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/README.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/index.js +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/postinstall.js +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_arg_default.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_at_prefix.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_auto_version.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_command.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_discovery.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_runner.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_schema.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_check_types.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_choices.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_command_help_suggestion.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config_file_path.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_config_set_bugs.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_deep_nesting.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_dependencies.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_deprecated.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_dump_schema.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_e2e.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_env.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_exit_codes.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_float_type.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_global_flags.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_help.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_int_type.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_mutex.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_nesting.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_parser.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_passthrough.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_registration.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_tagdsl.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_tags.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_toml_loading.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_unique.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_utilities.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_validate.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/tests/test_variadic.py +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/todo/.defer/deferred.md +0 -0
- {strictcli-0.16.1 → strictcli-0.17.0}/todo/.done/original-idea.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
912fca00b39e373782d5c0b88d65d2067f89a1b1
|
|
@@ -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.
|
|
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
|
-
|
|
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,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strictcli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
9
|
+
"version": "0.17.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT"
|
|
12
12
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
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;
|
|
2908
|
-
|
|
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
|
|
1119
|
+
unique=False)
|
|
1121
1120
|
@strictcli.flag("rates", type=float, help="rates", repeatable=True,
|
|
1122
|
-
unique=False
|
|
1121
|
+
unique=False)
|
|
1123
1122
|
@strictcli.flag("ids", type=int, help="unique ids", repeatable=True,
|
|
1124
|
-
unique=True
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|