strictcli 0.16.1__tar.gz → 0.18.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.18.0/.rlsbl/changes/.validated +1 -0
- strictcli-0.18.0/.rlsbl/changes/0.16.1.md +6 -0
- strictcli-0.18.0/.rlsbl/changes/0.17.0.jsonl +3 -0
- strictcli-0.18.0/.rlsbl/changes/0.17.0.md +9 -0
- strictcli-0.18.0/.rlsbl/changes/0.18.0.jsonl +4 -0
- strictcli-0.18.0/.rlsbl/changes/0.18.0.md +5 -0
- strictcli-0.18.0/.rlsbl/releases/v0.17.0.toml +8 -0
- strictcli-0.18.0/.rlsbl/releases/v0.18.0.toml +8 -0
- strictcli-0.18.0/.rlsbl/version +1 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/CHANGELOG.md +25 -2
- {strictcli-0.16.1 → strictcli-0.18.0}/PKG-INFO +1 -1
- {strictcli-0.16.1 → strictcli-0.18.0}/package-lock.json +2 -2
- {strictcli-0.16.1 → strictcli-0.18.0}/package.json +1 -1
- {strictcli-0.16.1 → strictcli-0.18.0}/pyproject.toml +1 -1
- {strictcli-0.16.1 → strictcli-0.18.0}/strictcli/__init__.py +114 -31
- strictcli-0.18.0/tests/test_check_public_api.py +435 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_check_runner.py +9 -9
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_config.py +4 -5
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_repeatable.py +101 -0
- strictcli-0.18.0/todo/.done/public-check-runner-api.md +36 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/uv.lock +1 -1
- strictcli-0.16.1/.rlsbl/changes/.validated +0 -1
- strictcli-0.16.1/.rlsbl/version +0 -1
- {strictcli-0.16.1 → strictcli-0.18.0}/.claude/settings.json +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.github/workflows/ci.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.github/workflows/publish.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.gitignore +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.npmignore +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.claude/settings.json +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.gitignore +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.npmignore +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/CHANGELOG.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/CLAUDE.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/bases/LICENSE +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.10.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.10.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.11.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.11.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.12.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.12.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.13.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.13.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.14.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.14.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.15.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.15.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.15.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.15.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.16.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.16.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.16.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.4.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.4.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.4.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.4.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.5.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.5.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.6.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.6.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.6.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.6.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.7.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.7.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.7.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.7.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.2.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.2.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.3.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.3.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.4.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.4.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.5.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.5.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.6.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.6.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.7.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.8.7.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.9.0.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.9.0.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.9.1.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/0.9.1.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/changes/unreleased.jsonl +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/config.json +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/hashes.json +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/unreleased.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.10.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.11.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.12.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.13.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.15.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.15.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.16.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.16.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.8.5.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.8.6.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.8.7.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.9.0.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.rlsbl/releases/v0.9.1.toml +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/.strictcli/schema.json +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/CLAUDE.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/LICENSE +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/README.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/index.js +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/postinstall.js +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_arg_default.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_at_prefix.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_auto_version.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_check_command.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_check_discovery.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_check_schema.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_check_types.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_choices.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_command_help_suggestion.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_config_file_path.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_config_set_bugs.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_deep_nesting.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_dependencies.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_deprecated.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_dump_schema.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_e2e.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_env.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_exit_codes.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_float_type.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_global_flags.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_help.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_int_type.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_mutex.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_nesting.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_parser.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_passthrough.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_registration.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_tagdsl.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_tags.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_toml_loading.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_unique.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_utilities.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_validate.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/tests/test_variadic.py +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/todo/.defer/deferred.md +0 -0
- {strictcli-0.16.1 → strictcli-0.18.0}/todo/.done/original-idea.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9fe0fe3a00effc0c9e39704a0bd0cc865542da83
|
|
@@ -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,9 @@
|
|
|
1
|
+
## 0.17.0
|
|
2
|
+
|
|
3
|
+
### Breaking
|
|
4
|
+
|
|
5
|
+
- **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
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **New.** Help text now displays non-empty repeatable flag defaults as `[default: a, b, c]`.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"commits":["ab63f2e29d95b834aef6e3307d23b53e3f845afe","fc7ae2927aa12b7627c3119c818d9a7e89209e39","db1924cbd143285c857324a8fcf2110718080bfa"],"user_facing":true,"description":"**New.** Public API for running checks programmatically: `App.run_checks()`, `format_check_results()`, `format_check_results_json()`, `CheckRunResult`.","type":"feature"}
|
|
2
|
+
{"commits":["6407ea33c3a16a83dcd7c362cda8e98980074a73","edfcf45b0eb1ba4896fcff1d31ea9993c9b363a1","fcaae9bb7895805308a6b9299158ada7f6d45f3b","b6720d2dd1063bff0b49f6d5624608672c0cac33"],"user_facing":false}
|
|
3
|
+
{"commits":["9cc04830fa5b7eef0d87b249e57248415fd769a5","4e89101a5fdb8c0a6f46b62829f22e09b79ec2d1"],"user_facing":false}
|
|
4
|
+
{"commits":["67ee39ff3cdf4d09a9a29c0bdcb9853999878ee9"],"user_facing":false}
|
|
@@ -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 = []
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Version bump type: patch, minor, or major
|
|
2
|
+
bump = "minor"
|
|
3
|
+
# Short description of this release (required)
|
|
4
|
+
description = "Public API for running checks programmatically"
|
|
5
|
+
# Optional context explaining why these changes were made
|
|
6
|
+
context = "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."
|
|
7
|
+
include = ["pypi", "npm"]
|
|
8
|
+
exclude = []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.67.1
|
|
@@ -2,9 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.18.0
|
|
6
|
+
|
|
7
|
+
Public API for running checks programmatically
|
|
8
|
+
|
|
9
|
+
<details>
|
|
10
|
+
<summary>Context</summary>
|
|
11
|
+
|
|
12
|
+
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.
|
|
13
|
+
|
|
14
|
+
</details>
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- **New.** Public API for running checks programmatically: `App.run_checks()`, `format_check_results()`, `format_check_results_json()`, `CheckRunResult`.
|
|
6
19
|
|
|
7
|
-
|
|
20
|
+
## 0.17.0
|
|
21
|
+
|
|
22
|
+
### Breaking
|
|
23
|
+
|
|
24
|
+
- **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.
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
- **New.** Help text now displays non-empty repeatable flag defaults as `[default: a, b, c]`.
|
|
29
|
+
|
|
30
|
+
## 0.16.1
|
|
8
31
|
|
|
9
32
|
### Fixes
|
|
10
33
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strictcli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "strictcli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.18.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT"
|
|
12
12
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.18.0"
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"App", "Flag", "Arg", "Tag", "MutexGroup", "CoRequired", "Requires",
|
|
9
9
|
"Implies", "Passthrough", "DeprecatedCommand", "Result", "flag", "arg",
|
|
10
|
-
"CheckResult", "CheckContext",
|
|
10
|
+
"CheckResult", "CheckContext", "CheckRunResult",
|
|
11
|
+
"format_check_results", "format_check_results_json",
|
|
11
12
|
]
|
|
12
13
|
|
|
13
14
|
import contextlib
|
|
@@ -588,6 +589,38 @@ class Flag:
|
|
|
588
589
|
raise ValueError(
|
|
589
590
|
f'Flag "{self.name}": choice {c!r} is not of type {self.type.__name__}'
|
|
590
591
|
)
|
|
592
|
+
# Validate repeatable flag defaults
|
|
593
|
+
if self.repeatable and not isinstance(self.default, _MissingSentinel):
|
|
594
|
+
if self.default is not None:
|
|
595
|
+
if not isinstance(self.default, list):
|
|
596
|
+
raise ValueError(
|
|
597
|
+
f'Flag "{self.name}": repeatable flag default must be a list'
|
|
598
|
+
)
|
|
599
|
+
if len(self.default) == 0:
|
|
600
|
+
raise ValueError(
|
|
601
|
+
f'Flag "{self.name}": explicit empty default is redundant '
|
|
602
|
+
f"for repeatable flags, omit the default"
|
|
603
|
+
)
|
|
604
|
+
# Validate element types
|
|
605
|
+
type_name = {str: "str", int: "int", float: "float"}[self.type]
|
|
606
|
+
for i, elem in enumerate(self.default):
|
|
607
|
+
if self.type is str:
|
|
608
|
+
if not isinstance(elem, str):
|
|
609
|
+
raise ValueError(
|
|
610
|
+
f'Flag "{self.name}": default element {i} is not of type {type_name}'
|
|
611
|
+
)
|
|
612
|
+
elif self.type is int:
|
|
613
|
+
if not isinstance(elem, int) or isinstance(elem, bool):
|
|
614
|
+
raise ValueError(
|
|
615
|
+
f'Flag "{self.name}": default element {i} is not of type {type_name}'
|
|
616
|
+
)
|
|
617
|
+
elif self.type is float:
|
|
618
|
+
if not isinstance(elem, (int, float)) or isinstance(elem, bool):
|
|
619
|
+
raise ValueError(
|
|
620
|
+
f'Flag "{self.name}": default element {i} is not of type {type_name}'
|
|
621
|
+
)
|
|
622
|
+
if isinstance(elem, int):
|
|
623
|
+
self.default[i] = float(elem)
|
|
591
624
|
# Validate default type for int flags
|
|
592
625
|
if self.type is int and not isinstance(self.default, _MissingSentinel) and self.default is not None:
|
|
593
626
|
if not self.repeatable and not isinstance(self.default, int):
|
|
@@ -824,6 +857,14 @@ class CheckResult:
|
|
|
824
857
|
raise ValueError("CheckResult.message must be a non-empty string")
|
|
825
858
|
|
|
826
859
|
|
|
860
|
+
@dataclass(frozen=True)
|
|
861
|
+
class CheckRunResult:
|
|
862
|
+
"""A named check result returned by App.run_checks()."""
|
|
863
|
+
|
|
864
|
+
name: str
|
|
865
|
+
result: CheckResult
|
|
866
|
+
|
|
867
|
+
|
|
827
868
|
@runtime_checkable
|
|
828
869
|
class CheckContext(Protocol):
|
|
829
870
|
"""Minimal interface that tool-specific check contexts must satisfy."""
|
|
@@ -969,6 +1010,38 @@ class App:
|
|
|
969
1010
|
"""
|
|
970
1011
|
self._check_context_factory = factory
|
|
971
1012
|
|
|
1013
|
+
def run_checks(
|
|
1014
|
+
self,
|
|
1015
|
+
context: CheckContext,
|
|
1016
|
+
*,
|
|
1017
|
+
tag_expr: str | None = None,
|
|
1018
|
+
name_glob: str | None = None,
|
|
1019
|
+
run_all: bool = False,
|
|
1020
|
+
ignore_warnings: bool = False,
|
|
1021
|
+
) -> tuple[list[CheckRunResult], int]:
|
|
1022
|
+
"""Run checks programmatically with filtering and dependency resolution.
|
|
1023
|
+
|
|
1024
|
+
Returns (results, exit_code) where results is a list of CheckRunResult
|
|
1025
|
+
and exit_code is 0 if all pass (or all warn with ignore_warnings), else 1.
|
|
1026
|
+
"""
|
|
1027
|
+
if not self._checks_enabled:
|
|
1028
|
+
raise ValueError("checks are not enabled on this App")
|
|
1029
|
+
err = self._validate_check_registrations()
|
|
1030
|
+
if err:
|
|
1031
|
+
raise ValueError(err)
|
|
1032
|
+
selected = _filter_checks(self._check_defs, tag_expr, name_glob, run_all)
|
|
1033
|
+
if not selected:
|
|
1034
|
+
return ([], 0)
|
|
1035
|
+
order = _resolve_check_order(self._check_defs, selected)
|
|
1036
|
+
raw_results, exit_code = _run_checks(
|
|
1037
|
+
self._check_defs, order, context, ignore_warnings,
|
|
1038
|
+
)
|
|
1039
|
+
results = [
|
|
1040
|
+
CheckRunResult(name=name, result=result)
|
|
1041
|
+
for name, result in raw_results
|
|
1042
|
+
]
|
|
1043
|
+
return (results, exit_code)
|
|
1044
|
+
|
|
972
1045
|
def _register_check_command(self) -> None:
|
|
973
1046
|
"""Register the auto-generated 'check' command when checks.toml exists."""
|
|
974
1047
|
app_ref = self # capture for closure
|
|
@@ -1016,14 +1089,19 @@ class App:
|
|
|
1016
1089
|
)
|
|
1017
1090
|
return 1
|
|
1018
1091
|
context = app_ref._check_context_factory()
|
|
1019
|
-
|
|
1020
|
-
app_ref, order, context, ignore_warnings,
|
|
1092
|
+
raw_results, exit_code = _run_checks(
|
|
1093
|
+
app_ref._check_defs, order, context, ignore_warnings,
|
|
1021
1094
|
)
|
|
1022
1095
|
|
|
1096
|
+
results_wrapped = [
|
|
1097
|
+
CheckRunResult(name=n, result=r) for n, r in raw_results
|
|
1098
|
+
]
|
|
1023
1099
|
if json:
|
|
1024
|
-
|
|
1100
|
+
print(format_check_results_json(results_wrapped))
|
|
1025
1101
|
else:
|
|
1026
|
-
|
|
1102
|
+
output = format_check_results(results_wrapped, verbose)
|
|
1103
|
+
if output:
|
|
1104
|
+
print(output)
|
|
1027
1105
|
|
|
1028
1106
|
return exit_code
|
|
1029
1107
|
|
|
@@ -2904,8 +2982,10 @@ def _build_flag_meta(f: Flag) -> str:
|
|
|
2904
2982
|
if f.type is bool:
|
|
2905
2983
|
meta_parts.append(f"default: {'true' if f.default else 'false'}")
|
|
2906
2984
|
elif f.repeatable:
|
|
2907
|
-
# Repeatable flags are never required;
|
|
2908
|
-
|
|
2985
|
+
# Repeatable flags are never required; show default only if non-empty
|
|
2986
|
+
if f.default:
|
|
2987
|
+
joined = ", ".join(_format_value_for_error(elem) for elem in f.default)
|
|
2988
|
+
meta_parts.append(f"default: {joined}")
|
|
2909
2989
|
elif f.default is not None:
|
|
2910
2990
|
meta_parts.append(f"default: {f.default}")
|
|
2911
2991
|
else:
|
|
@@ -3296,7 +3376,7 @@ def _find_cycle(
|
|
|
3296
3376
|
|
|
3297
3377
|
|
|
3298
3378
|
def _run_checks(
|
|
3299
|
-
|
|
3379
|
+
check_defs: dict,
|
|
3300
3380
|
check_names: list[str],
|
|
3301
3381
|
context: CheckContext,
|
|
3302
3382
|
ignore_warnings: bool,
|
|
@@ -3306,7 +3386,6 @@ def _run_checks(
|
|
|
3306
3386
|
Returns (results_list, exit_code). exit_code is 0 if all pass (or all
|
|
3307
3387
|
warn with ignore_warnings=True), 1 otherwise.
|
|
3308
3388
|
"""
|
|
3309
|
-
check_defs = app._check_defs
|
|
3310
3389
|
results: list[tuple[str, CheckResult]] = []
|
|
3311
3390
|
failed_checks: set[str] = set()
|
|
3312
3391
|
exit_code = 0
|
|
@@ -3406,39 +3485,43 @@ def _check_dry_run_mode(
|
|
|
3406
3485
|
print(f" {i}. {name}")
|
|
3407
3486
|
|
|
3408
3487
|
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3488
|
+
|
|
3489
|
+
def format_check_results(
|
|
3490
|
+
results: list[CheckRunResult], verbose: bool = False,
|
|
3491
|
+
) -> str:
|
|
3492
|
+
"""Format check results as a human-readable aligned string."""
|
|
3413
3493
|
if not results:
|
|
3414
|
-
return
|
|
3494
|
+
return ""
|
|
3415
3495
|
|
|
3416
|
-
name_width = max(len(name) for
|
|
3496
|
+
name_width = max(len(r.name) for r in results)
|
|
3497
|
+
lines: list[str] = []
|
|
3417
3498
|
|
|
3418
|
-
for
|
|
3419
|
-
label = _CHECK_STATUS_LABELS[result.status]
|
|
3420
|
-
|
|
3499
|
+
for r in results:
|
|
3500
|
+
label = _CHECK_STATUS_LABELS[r.result.status]
|
|
3501
|
+
lines.append(f"{label} {r.name:<{name_width}} {r.result.message}")
|
|
3421
3502
|
|
|
3422
|
-
show_details = result.details and (
|
|
3423
|
-
verbose or result.status in ("fail", "warn", "skip")
|
|
3503
|
+
show_details = r.result.details and (
|
|
3504
|
+
verbose or r.result.status in ("fail", "warn", "skip")
|
|
3424
3505
|
)
|
|
3425
3506
|
if show_details:
|
|
3426
|
-
for detail in result.details:
|
|
3427
|
-
|
|
3507
|
+
for detail in r.result.details:
|
|
3508
|
+
lines.append(f" {detail}")
|
|
3509
|
+
|
|
3510
|
+
return "\n".join(lines)
|
|
3428
3511
|
|
|
3429
3512
|
|
|
3430
|
-
def
|
|
3431
|
-
"""
|
|
3513
|
+
def format_check_results_json(results: list[CheckRunResult]) -> str:
|
|
3514
|
+
"""Format check results as a JSON string."""
|
|
3432
3515
|
items = [
|
|
3433
3516
|
{
|
|
3434
|
-
"name": name,
|
|
3435
|
-
"status": result.status,
|
|
3436
|
-
"message": result.message,
|
|
3437
|
-
"details": result.details,
|
|
3517
|
+
"name": r.name,
|
|
3518
|
+
"status": r.result.status,
|
|
3519
|
+
"message": r.result.message,
|
|
3520
|
+
"details": r.result.details if r.result.details is not None else [],
|
|
3438
3521
|
}
|
|
3439
|
-
for
|
|
3522
|
+
for r in results
|
|
3440
3523
|
]
|
|
3441
|
-
|
|
3524
|
+
return json.dumps(items, separators=(",", ":"))
|
|
3442
3525
|
|
|
3443
3526
|
|
|
3444
3527
|
# ---------------------------------------------------------------------------
|