strictcli 0.4.1__tar.gz → 0.5.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.5.0/.rlsbl/changes/.validated +1 -0
- strictcli-0.5.0/.rlsbl/changes/0.5.0.jsonl +3 -0
- strictcli-0.5.0/.rlsbl/changes/0.5.0.md +5 -0
- strictcli-0.5.0/.rlsbl/version +1 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/CHANGELOG.md +6 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/PKG-INFO +1 -1
- {strictcli-0.4.1 → strictcli-0.5.0}/package.json +1 -1
- {strictcli-0.4.1 → strictcli-0.5.0}/pyproject.toml +1 -1
- {strictcli-0.4.1 → strictcli-0.5.0}/strictcli/__init__.py +66 -6
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_dependencies.py +269 -1
- {strictcli-0.4.1 → strictcli-0.5.0}/uv.lock +1 -1
- strictcli-0.4.1/.rlsbl/changes/.validated +0 -1
- strictcli-0.4.1/.rlsbl/version +0 -1
- {strictcli-0.4.1 → strictcli-0.5.0}/.claude/settings.json +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.github/workflows/ci.yml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.github/workflows/publish.yml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.gitignore +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.claude/settings.json +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.gitignore +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/CHANGELOG.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/CLAUDE.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/bases/LICENSE +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/changes/0.4.0.jsonl +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/changes/0.4.0.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/changes/0.4.1.jsonl +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/changes/0.4.1.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/changes/unreleased.jsonl +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/config.json +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/hashes.json +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/hooks/post-release.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/hooks/pre-checks.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/hooks/pre-release.sh +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/lint/go.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/lint/npm.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/.rlsbl/lint/python.toml +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/CLAUDE.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/LICENSE +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/README.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/index.js +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/package-lock.json +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/postinstall.js +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_arg_default.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_choices.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_e2e.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_env.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_exit_codes.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_global_flags.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_help.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_int_type.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_mutex.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_nesting.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_parser.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_passthrough.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_registration.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_repeatable.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_tags.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_validate.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/tests/test_variadic.py +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/todo/.defer/deferred.md +0 -0
- {strictcli-0.4.1 → strictcli-0.5.0}/todo/.done/original-idea.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d7f1969ff795da06e8ed45792a8508559487c830
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"commits":["2a230acb24171d4d380ef4c499f20dc834db9aae"],"user_facing":true,"description":"**New feature.** `Implies` flag dependency type: when a trigger flag is set, automatically set a target bool flag to a specified value. Explicit contradictions are parse errors.","type":"feature"}
|
|
2
|
+
{"commits":["8802c6d7d49c9d0243e29439ff0488db0d817caa"],"user_facing":false}
|
|
3
|
+
{"commits":["af7f4514b16a02cb275b8cae467a6d6c9952c558"],"user_facing":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.32.0
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## 0.5.0
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **New feature.** `Implies` flag dependency type: when a trigger flag is set, automatically set a target bool flag to a specified value. Explicit contradictions are parse errors.
|
|
10
|
+
|
|
5
11
|
## 0.4.1
|
|
6
12
|
|
|
7
13
|
### Fixes
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.5.0"
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"App", "Flag", "Arg", "Tag", "MutexGroup", "CoRequired", "Requires",
|
|
9
|
-
"Passthrough", "Result", "flag", "arg",
|
|
9
|
+
"Implies", "Passthrough", "Result", "flag", "arg",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
import contextlib
|
|
@@ -175,6 +175,15 @@ class Requires:
|
|
|
175
175
|
depends_on: str
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
@dataclass
|
|
179
|
+
class Implies:
|
|
180
|
+
"""When a trigger flag is provided, automatically set a target flag to a value."""
|
|
181
|
+
|
|
182
|
+
flag: str # trigger flag name
|
|
183
|
+
implies: str # target flag name
|
|
184
|
+
value: bool # value to set on target when trigger is present
|
|
185
|
+
|
|
186
|
+
|
|
178
187
|
@dataclass
|
|
179
188
|
class Passthrough:
|
|
180
189
|
"""Marks a command as passthrough -- all tokens after the command name are
|
|
@@ -194,7 +203,7 @@ class Command:
|
|
|
194
203
|
args: list[Arg] = field(default_factory=list)
|
|
195
204
|
tags: list[Tag] = field(default_factory=list)
|
|
196
205
|
mutex: list[MutexGroup] = field(default_factory=list)
|
|
197
|
-
dependencies: list[CoRequired | Requires] = field(default_factory=list)
|
|
206
|
+
dependencies: list[CoRequired | Requires | Implies] = field(default_factory=list)
|
|
198
207
|
passthrough: Passthrough | None = None
|
|
199
208
|
|
|
200
209
|
def __post_init__(self) -> None:
|
|
@@ -222,7 +231,7 @@ class Group:
|
|
|
222
231
|
args: list[Arg] | None = None,
|
|
223
232
|
tags: list[Tag] | None = None,
|
|
224
233
|
mutex: list[MutexGroup] | None = None,
|
|
225
|
-
dependencies: list[CoRequired | Requires] | None = None,
|
|
234
|
+
dependencies: list[CoRequired | Requires | Implies] | None = None,
|
|
226
235
|
passthrough: Passthrough | None = None,
|
|
227
236
|
) -> Callable:
|
|
228
237
|
"""Decorator to register a command within this group."""
|
|
@@ -281,7 +290,7 @@ class App:
|
|
|
281
290
|
args: list[Arg] | None = None,
|
|
282
291
|
tags: list[Tag] | None = None,
|
|
283
292
|
mutex: list[MutexGroup] | None = None,
|
|
284
|
-
dependencies: list[CoRequired | Requires] | None = None,
|
|
293
|
+
dependencies: list[CoRequired | Requires | Implies] | None = None,
|
|
285
294
|
passthrough: Passthrough | None = None,
|
|
286
295
|
) -> Callable:
|
|
287
296
|
"""Decorator to register a top-level command."""
|
|
@@ -849,6 +858,22 @@ def _parse_command(
|
|
|
849
858
|
names = ", ".join(f"--{f.name}" for f in mg.flags)
|
|
850
859
|
raise _ParseError(f"one of {names} is required")
|
|
851
860
|
|
|
861
|
+
# Step 4.55: resolve Implies dependencies (before dependency checks, so
|
|
862
|
+
# implied values participate in downstream CoRequired/Requires validation)
|
|
863
|
+
for dep in cmd.dependencies:
|
|
864
|
+
if isinstance(dep, Implies):
|
|
865
|
+
if dep.flag in cli_set:
|
|
866
|
+
if dep.implies in cli_set:
|
|
867
|
+
if cli_set[dep.implies] != dep.value:
|
|
868
|
+
neg = "no-" if not dep.value else ""
|
|
869
|
+
explicit_neg = "" if not dep.value else "no-"
|
|
870
|
+
raise _ParseError(
|
|
871
|
+
f"flag '--{dep.flag}' implies '--{neg}{dep.implies}', "
|
|
872
|
+
f"but '--{explicit_neg}{dep.implies}' was explicitly provided"
|
|
873
|
+
)
|
|
874
|
+
else:
|
|
875
|
+
cli_set[dep.implies] = dep.value
|
|
876
|
+
|
|
852
877
|
# Step 4.6: enforce flag dependencies (before defaults, so cli_set only
|
|
853
878
|
# contains values explicitly provided via CLI or env)
|
|
854
879
|
for dep in cmd.dependencies:
|
|
@@ -974,7 +999,7 @@ def _build_and_validate_command(
|
|
|
974
999
|
args: list[Arg] | None,
|
|
975
1000
|
tags: list[Tag] | None,
|
|
976
1001
|
mutex: list[MutexGroup] | None,
|
|
977
|
-
dependencies: list[CoRequired | Requires] | None = None,
|
|
1002
|
+
dependencies: list[CoRequired | Requires | Implies] | None = None,
|
|
978
1003
|
env_prefix: str | None,
|
|
979
1004
|
global_flags: list[Flag] | None = None,
|
|
980
1005
|
passthrough: Passthrough | None = None,
|
|
@@ -1182,6 +1207,41 @@ def _build_and_validate_command(
|
|
|
1182
1207
|
f'command "{name}": Requires flag and depends_on cannot be '
|
|
1183
1208
|
f'the same ("{dep.flag}")'
|
|
1184
1209
|
)
|
|
1210
|
+
elif isinstance(dep, Implies):
|
|
1211
|
+
if dep.flag not in seen_flag_names:
|
|
1212
|
+
raise ValueError(
|
|
1213
|
+
f'command "{name}": Implies references unknown flag '
|
|
1214
|
+
f'"{dep.flag}"'
|
|
1215
|
+
)
|
|
1216
|
+
if dep.implies not in seen_flag_names:
|
|
1217
|
+
raise ValueError(
|
|
1218
|
+
f'command "{name}": Implies references unknown flag '
|
|
1219
|
+
f'"{dep.implies}"'
|
|
1220
|
+
)
|
|
1221
|
+
if dep.flag == dep.implies:
|
|
1222
|
+
raise ValueError(
|
|
1223
|
+
f'command "{name}": Implies flag and implies cannot be '
|
|
1224
|
+
f'the same ("{dep.flag}")'
|
|
1225
|
+
)
|
|
1226
|
+
# Look up the actual Flag objects to validate types
|
|
1227
|
+
all_flags_by_name = {f.name: f for f in all_flags}
|
|
1228
|
+
trigger_flag = all_flags_by_name[dep.flag]
|
|
1229
|
+
target_flag = all_flags_by_name[dep.implies]
|
|
1230
|
+
if trigger_flag.type is not bool:
|
|
1231
|
+
raise ValueError(
|
|
1232
|
+
f'command "{name}": Implies trigger flag "{dep.flag}" '
|
|
1233
|
+
f"must be type=bool"
|
|
1234
|
+
)
|
|
1235
|
+
if target_flag.type is not bool:
|
|
1236
|
+
raise ValueError(
|
|
1237
|
+
f'command "{name}": Implies target flag "{dep.implies}" '
|
|
1238
|
+
f"must be type=bool"
|
|
1239
|
+
)
|
|
1240
|
+
if not isinstance(dep.value, bool):
|
|
1241
|
+
raise ValueError(
|
|
1242
|
+
f'command "{name}": Implies value must be a bool, '
|
|
1243
|
+
f"got {type(dep.value).__name__!r}"
|
|
1244
|
+
)
|
|
1185
1245
|
|
|
1186
1246
|
return Command(
|
|
1187
1247
|
name=name,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tests for CoRequired and
|
|
1
|
+
"""Tests for CoRequired, Requires, and Implies flag dependencies."""
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
@@ -398,3 +398,271 @@ def test_corequired_duplicate_flag_error():
|
|
|
398
398
|
@strictcli.flag("output", type=str, help="output path", default=None)
|
|
399
399
|
def cmd(output):
|
|
400
400
|
pass
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ===========================================================================
|
|
404
|
+
# Implies tests
|
|
405
|
+
# ===========================================================================
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ---------------------------------------------------------------------------
|
|
409
|
+
# Implies: trigger set -> target auto-set to implied value
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def test_implies_trigger_set_target_auto_set():
|
|
414
|
+
"""Implies: when trigger is set, target is automatically set to implied value."""
|
|
415
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
416
|
+
|
|
417
|
+
@app.command(
|
|
418
|
+
"cmd", help="a command",
|
|
419
|
+
dependencies=[strictcli.Implies(flag="fast", implies="embeddings", value=False)],
|
|
420
|
+
)
|
|
421
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
422
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
423
|
+
def cmd(fast, embeddings):
|
|
424
|
+
print(f"fast={fast} embeddings={embeddings}")
|
|
425
|
+
|
|
426
|
+
r = app.test(["cmd", "--fast"])
|
|
427
|
+
assert r.exit_code == 0
|
|
428
|
+
assert "fast=True" in r.stdout
|
|
429
|
+
assert "embeddings=False" in r.stdout
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# ---------------------------------------------------------------------------
|
|
433
|
+
# Implies: trigger not set -> target gets normal default
|
|
434
|
+
# ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_implies_trigger_not_set_target_gets_default():
|
|
438
|
+
"""Implies: when trigger is not set, target gets its normal default."""
|
|
439
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
440
|
+
|
|
441
|
+
@app.command(
|
|
442
|
+
"cmd", help="a command",
|
|
443
|
+
dependencies=[strictcli.Implies(flag="fast", implies="embeddings", value=False)],
|
|
444
|
+
)
|
|
445
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
446
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings", default=True)
|
|
447
|
+
def cmd(fast, embeddings):
|
|
448
|
+
print(f"fast={fast} embeddings={embeddings}")
|
|
449
|
+
|
|
450
|
+
r = app.test(["cmd"])
|
|
451
|
+
assert r.exit_code == 0
|
|
452
|
+
assert "fast=False" in r.stdout
|
|
453
|
+
assert "embeddings=True" in r.stdout
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
# ---------------------------------------------------------------------------
|
|
457
|
+
# Implies: explicit conflict -> parse error
|
|
458
|
+
# ---------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_implies_explicit_conflict_error():
|
|
462
|
+
"""Implies: trigger + contradicting explicit target -> error."""
|
|
463
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
464
|
+
|
|
465
|
+
@app.command(
|
|
466
|
+
"cmd", help="a command",
|
|
467
|
+
dependencies=[strictcli.Implies(flag="fast", implies="embeddings", value=False)],
|
|
468
|
+
)
|
|
469
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
470
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
471
|
+
def cmd(fast, embeddings):
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
r = app.test(["cmd", "--fast", "--embeddings"])
|
|
475
|
+
assert r.exit_code == 1
|
|
476
|
+
assert "implies" in r.stderr
|
|
477
|
+
assert "--fast" in r.stderr
|
|
478
|
+
assert "--no-embeddings" in r.stderr
|
|
479
|
+
assert "explicitly provided" in r.stderr
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
# ---------------------------------------------------------------------------
|
|
483
|
+
# Implies: explicit agreement -> no error
|
|
484
|
+
# ---------------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_implies_explicit_agreement_ok():
|
|
488
|
+
"""Implies: trigger + matching explicit target -> OK."""
|
|
489
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
490
|
+
|
|
491
|
+
@app.command(
|
|
492
|
+
"cmd", help="a command",
|
|
493
|
+
dependencies=[strictcli.Implies(flag="fast", implies="embeddings", value=False)],
|
|
494
|
+
)
|
|
495
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
496
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
497
|
+
def cmd(fast, embeddings):
|
|
498
|
+
print(f"fast={fast} embeddings={embeddings}")
|
|
499
|
+
|
|
500
|
+
r = app.test(["cmd", "--fast", "--no-embeddings"])
|
|
501
|
+
assert r.exit_code == 0
|
|
502
|
+
assert "fast=True" in r.stdout
|
|
503
|
+
assert "embeddings=False" in r.stdout
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# ---------------------------------------------------------------------------
|
|
507
|
+
# Registration error: unknown trigger flag
|
|
508
|
+
# ---------------------------------------------------------------------------
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def test_implies_unknown_trigger_flag_error():
|
|
512
|
+
"""Implies: unknown trigger flag -> ValueError at registration."""
|
|
513
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
514
|
+
|
|
515
|
+
with pytest.raises(ValueError, match='Implies references unknown flag "nonexistent"'):
|
|
516
|
+
|
|
517
|
+
@app.command(
|
|
518
|
+
"cmd", help="a command",
|
|
519
|
+
dependencies=[strictcli.Implies(flag="nonexistent", implies="embeddings", value=False)],
|
|
520
|
+
)
|
|
521
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
522
|
+
def cmd(embeddings):
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# ---------------------------------------------------------------------------
|
|
527
|
+
# Registration error: unknown target flag
|
|
528
|
+
# ---------------------------------------------------------------------------
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test_implies_unknown_target_flag_error():
|
|
532
|
+
"""Implies: unknown target flag -> ValueError at registration."""
|
|
533
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
534
|
+
|
|
535
|
+
with pytest.raises(ValueError, match='Implies references unknown flag "nonexistent"'):
|
|
536
|
+
|
|
537
|
+
@app.command(
|
|
538
|
+
"cmd", help="a command",
|
|
539
|
+
dependencies=[strictcli.Implies(flag="fast", implies="nonexistent", value=False)],
|
|
540
|
+
)
|
|
541
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
542
|
+
def cmd(fast):
|
|
543
|
+
pass
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# ---------------------------------------------------------------------------
|
|
547
|
+
# Registration error: self-implication
|
|
548
|
+
# ---------------------------------------------------------------------------
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def test_implies_self_implication_error():
|
|
552
|
+
"""Implies: flag == implies -> ValueError at registration."""
|
|
553
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
554
|
+
|
|
555
|
+
with pytest.raises(ValueError, match="cannot be the same"):
|
|
556
|
+
|
|
557
|
+
@app.command(
|
|
558
|
+
"cmd", help="a command",
|
|
559
|
+
dependencies=[strictcli.Implies(flag="fast", implies="fast", value=True)],
|
|
560
|
+
)
|
|
561
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
562
|
+
def cmd(fast):
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# ---------------------------------------------------------------------------
|
|
567
|
+
# Registration error: trigger flag is not bool
|
|
568
|
+
# ---------------------------------------------------------------------------
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def test_implies_trigger_not_bool_error():
|
|
572
|
+
"""Implies: trigger flag not type=bool -> ValueError at registration."""
|
|
573
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
574
|
+
|
|
575
|
+
with pytest.raises(ValueError, match='trigger flag "name" must be type=bool'):
|
|
576
|
+
|
|
577
|
+
@app.command(
|
|
578
|
+
"cmd", help="a command",
|
|
579
|
+
dependencies=[strictcli.Implies(flag="name", implies="embeddings", value=False)],
|
|
580
|
+
)
|
|
581
|
+
@strictcli.flag("name", type=str, help="a name", default="")
|
|
582
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
583
|
+
def cmd(name, embeddings):
|
|
584
|
+
pass
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
# ---------------------------------------------------------------------------
|
|
588
|
+
# Registration error: target flag is not bool
|
|
589
|
+
# ---------------------------------------------------------------------------
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def test_implies_target_not_bool_error():
|
|
593
|
+
"""Implies: target flag not type=bool -> ValueError at registration."""
|
|
594
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
595
|
+
|
|
596
|
+
with pytest.raises(ValueError, match='target flag "name" must be type=bool'):
|
|
597
|
+
|
|
598
|
+
@app.command(
|
|
599
|
+
"cmd", help="a command",
|
|
600
|
+
dependencies=[strictcli.Implies(flag="fast", implies="name", value=False)],
|
|
601
|
+
)
|
|
602
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
603
|
+
@strictcli.flag("name", type=str, help="a name", default="")
|
|
604
|
+
def cmd(fast, name):
|
|
605
|
+
pass
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
# ---------------------------------------------------------------------------
|
|
609
|
+
# Env var trigger: trigger set via env var also triggers implication
|
|
610
|
+
# ---------------------------------------------------------------------------
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def test_implies_env_var_trigger(monkeypatch):
|
|
614
|
+
"""Implies: trigger set via env var -> target auto-set."""
|
|
615
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
616
|
+
|
|
617
|
+
@app.command(
|
|
618
|
+
"cmd", help="a command",
|
|
619
|
+
dependencies=[strictcli.Implies(flag="fast", implies="embeddings", value=False)],
|
|
620
|
+
)
|
|
621
|
+
@strictcli.flag("fast", type=bool, help="fast mode",
|
|
622
|
+
env="TEST_IMPLIES_FAST", prefixed=False)
|
|
623
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
624
|
+
def cmd(fast, embeddings):
|
|
625
|
+
print(f"fast={fast} embeddings={embeddings}")
|
|
626
|
+
|
|
627
|
+
monkeypatch.setenv("TEST_IMPLIES_FAST", "true")
|
|
628
|
+
r = app.test(["cmd"])
|
|
629
|
+
assert r.exit_code == 0
|
|
630
|
+
assert "fast=True" in r.stdout
|
|
631
|
+
assert "embeddings=False" in r.stdout
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
# ---------------------------------------------------------------------------
|
|
635
|
+
# Interaction: Implies + Requires on same command
|
|
636
|
+
# ---------------------------------------------------------------------------
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def test_implies_with_requires_interaction():
|
|
640
|
+
"""Implies + Requires on same command work together."""
|
|
641
|
+
app = strictcli.App(name="test", version="1.0.0", help="test app")
|
|
642
|
+
|
|
643
|
+
@app.command(
|
|
644
|
+
"cmd", help="a command",
|
|
645
|
+
dependencies=[
|
|
646
|
+
strictcli.Implies(flag="fast", implies="embeddings", value=False),
|
|
647
|
+
strictcli.Requires(flag="output", depends_on="fast"),
|
|
648
|
+
],
|
|
649
|
+
)
|
|
650
|
+
@strictcli.flag("fast", type=bool, help="fast mode")
|
|
651
|
+
@strictcli.flag("embeddings", type=bool, help="use embeddings")
|
|
652
|
+
@strictcli.flag("output", type=str, help="output path", default=None)
|
|
653
|
+
def cmd(fast, embeddings, output):
|
|
654
|
+
print(f"fast={fast} embeddings={embeddings} output={output}")
|
|
655
|
+
|
|
656
|
+
# --fast --output works (Requires satisfied, Implies sets embeddings=False)
|
|
657
|
+
r = app.test(["cmd", "--fast", "--output", "file.txt"])
|
|
658
|
+
assert r.exit_code == 0
|
|
659
|
+
assert "fast=True" in r.stdout
|
|
660
|
+
assert "embeddings=False" in r.stdout
|
|
661
|
+
assert "output=file.txt" in r.stdout
|
|
662
|
+
|
|
663
|
+
# --output without --fast -> error (Requires violation)
|
|
664
|
+
r = app.test(["cmd", "--output", "file.txt"])
|
|
665
|
+
assert r.exit_code == 1
|
|
666
|
+
assert "requires" in r.stderr
|
|
667
|
+
assert "--output" in r.stderr
|
|
668
|
+
assert "--fast" in r.stderr
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
9f2ad6a550f949c15aeb8301b16a999601dd3a79
|
strictcli-0.4.1/.rlsbl/version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.31.0
|
|
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
|