strictcli 0.8.5__tar.gz → 0.8.6__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 (107) hide show
  1. strictcli-0.8.6/.rlsbl/changes/.validated +1 -0
  2. strictcli-0.8.6/.rlsbl/changes/0.8.6.jsonl +6 -0
  3. strictcli-0.8.6/.rlsbl/changes/0.8.6.md +5 -0
  4. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/config.json +2 -1
  5. strictcli-0.8.6/.rlsbl/releases/v0.8.6.toml +3 -0
  6. strictcli-0.8.6/.rlsbl/version +1 -0
  7. {strictcli-0.8.5 → strictcli-0.8.6}/CHANGELOG.md +6 -0
  8. {strictcli-0.8.5 → strictcli-0.8.6}/PKG-INFO +1 -1
  9. {strictcli-0.8.5 → strictcli-0.8.6}/package-lock.json +2 -2
  10. {strictcli-0.8.5 → strictcli-0.8.6}/package.json +1 -1
  11. {strictcli-0.8.5 → strictcli-0.8.6}/pyproject.toml +1 -1
  12. {strictcli-0.8.5 → strictcli-0.8.6}/strictcli/__init__.py +17 -6
  13. strictcli-0.8.6/tests/test_command_help_suggestion.py +60 -0
  14. {strictcli-0.8.5 → strictcli-0.8.6}/uv.lock +1 -1
  15. strictcli-0.8.5/.rlsbl/changes/.validated +0 -1
  16. strictcli-0.8.5/.rlsbl/version +0 -1
  17. {strictcli-0.8.5 → strictcli-0.8.6}/.claude/settings.json +0 -0
  18. {strictcli-0.8.5 → strictcli-0.8.6}/.github/workflows/ci.yml +0 -0
  19. {strictcli-0.8.5 → strictcli-0.8.6}/.github/workflows/publish.yml +0 -0
  20. {strictcli-0.8.5 → strictcli-0.8.6}/.gitignore +0 -0
  21. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.claude/settings.json +0 -0
  22. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
  23. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
  24. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.gitignore +0 -0
  25. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -0
  26. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -0
  27. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -0
  28. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/lint/go.toml +0 -0
  29. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -0
  30. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
  31. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/CHANGELOG.md +0 -0
  32. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/CLAUDE.md +0 -0
  33. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/bases/LICENSE +0 -0
  34. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.4.0.jsonl +0 -0
  35. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.4.0.md +0 -0
  36. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.4.1.jsonl +0 -0
  37. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.4.1.md +0 -0
  38. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.5.0.jsonl +0 -0
  39. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.5.0.md +0 -0
  40. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.6.0.jsonl +0 -0
  41. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.6.0.md +0 -0
  42. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.6.1.jsonl +0 -0
  43. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.6.1.md +0 -0
  44. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.7.0.jsonl +0 -0
  45. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.7.0.md +0 -0
  46. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.7.1.jsonl +0 -0
  47. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.7.1.md +0 -0
  48. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.0.jsonl +0 -0
  49. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.0.md +0 -0
  50. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.1.jsonl +0 -0
  51. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.1.md +0 -0
  52. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.2.jsonl +0 -0
  53. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.2.md +0 -0
  54. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.3.jsonl +0 -0
  55. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.3.md +0 -0
  56. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.4.jsonl +0 -0
  57. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.4.md +0 -0
  58. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.5.jsonl +0 -0
  59. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/0.8.5.md +0 -0
  60. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/changes/unreleased.jsonl +0 -0
  61. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/hashes.json +0 -0
  62. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/hooks/post-release.sh +0 -0
  63. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/hooks/pre-checks.sh +0 -0
  64. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/hooks/pre-release.sh +0 -0
  65. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/lint/go.toml +0 -0
  66. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/lint/npm.toml +0 -0
  67. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/lint/python.toml +0 -0
  68. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/releases/unreleased.toml +0 -0
  69. {strictcli-0.8.5 → strictcli-0.8.6}/.rlsbl/releases/v0.8.5.toml +0 -0
  70. {strictcli-0.8.5 → strictcli-0.8.6}/CLAUDE.md +0 -0
  71. {strictcli-0.8.5 → strictcli-0.8.6}/LICENSE +0 -0
  72. {strictcli-0.8.5 → strictcli-0.8.6}/README.md +0 -0
  73. {strictcli-0.8.5 → strictcli-0.8.6}/index.js +0 -0
  74. {strictcli-0.8.5 → strictcli-0.8.6}/postinstall.js +0 -0
  75. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_arg_default.py +0 -0
  76. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_auto_version.py +0 -0
  77. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_check_command.py +0 -0
  78. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_check_discovery.py +0 -0
  79. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_check_runner.py +0 -0
  80. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_check_schema.py +0 -0
  81. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_check_types.py +0 -0
  82. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_choices.py +0 -0
  83. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_config.py +0 -0
  84. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_deep_nesting.py +0 -0
  85. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_dependencies.py +0 -0
  86. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_deprecated.py +0 -0
  87. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_dump_schema.py +0 -0
  88. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_e2e.py +0 -0
  89. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_env.py +0 -0
  90. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_exit_codes.py +0 -0
  91. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_float_type.py +0 -0
  92. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_global_flags.py +0 -0
  93. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_help.py +0 -0
  94. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_int_type.py +0 -0
  95. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_mutex.py +0 -0
  96. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_nesting.py +0 -0
  97. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_parser.py +0 -0
  98. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_passthrough.py +0 -0
  99. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_registration.py +0 -0
  100. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_repeatable.py +0 -0
  101. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_tagdsl.py +0 -0
  102. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_tags.py +0 -0
  103. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_toml_loading.py +0 -0
  104. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_validate.py +0 -0
  105. {strictcli-0.8.5 → strictcli-0.8.6}/tests/test_variadic.py +0 -0
  106. {strictcli-0.8.5 → strictcli-0.8.6}/todo/.defer/deferred.md +0 -0
  107. {strictcli-0.8.5 → strictcli-0.8.6}/todo/.done/original-idea.md +0 -0
@@ -0,0 +1 @@
1
+ c3a0fa4107268a0d0dae764c29358768d694199d
@@ -0,0 +1,6 @@
1
+ {"commits":["81c9f3996c118362a9a3952eeddb3a12310479e0"],"user_facing":true,"description":"**New feature.** Parse error messages now suggest the correct subcommand help (e.g., `try 'app stream --help'` instead of `try 'app --help'`).","type":"feature"}
2
+ {"commits":["8089797a28ed0dc23c6f1365df464d1b2d5a9e0f","dda8012e14645ac6653b1599e41a609ea8f77b42"],"user_facing":false}
3
+ {"commits":["8f71653fe5d65a735aff24c5e5367b0227e93c1b"],"user_facing":false}
4
+ {"commits":["f65b5a5859bb26db246b4b1e786eecf0ad78cd5e"],"user_facing":false}
5
+ {"commits":["dda8012e14645ac6653b1599e41a609ea8f77b42","6e990245b1d0ef8cb056e71cc0fdda97229fd395","d21c48ee1cc8f02357dae2b9ed28c646c461760b","f67895e847ab4d19fc95c2c21a47d8b033d31370"],"user_facing":false}
6
+ {"commits":["33cf59b2bc27d4bac9fc8dd820668d51a35cb9c6"],"user_facing":false}
@@ -0,0 +1,5 @@
1
+ ## 0.8.6
2
+
3
+ ### Features
4
+
5
+ - **New feature.** Parse error messages now suggest the correct subcommand help (e.g., `try 'app stream --help'` instead of `try 'app --help'`).
@@ -2,5 +2,6 @@
2
2
  "targets": [
3
3
  "pypi",
4
4
  "npm"
5
- ]
5
+ ],
6
+ "private": false
6
7
  }
@@ -0,0 +1,3 @@
1
+ bump = "patch"
2
+ include = ["pypi", "npm"]
3
+ exclude = []
@@ -0,0 +1 @@
1
+ 0.41.4
@@ -2,6 +2,12 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## 0.8.6
6
+
7
+ ### Features
8
+
9
+ - **New feature.** Parse error messages now suggest the correct subcommand help (e.g., `try 'app stream --help'` instead of `try 'app --help'`).
10
+
5
11
  ## 0.8.5
6
12
 
7
13
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.8.5
3
+ Version: 0.8.6
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.8.5",
3
+ "version": "0.8.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.8.5",
9
+ "version": "0.8.6",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
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.8.5"
7
+ version = "0.8.6"
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.8.5"
5
+ __version__ = "0.8.6"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "Tag", "MutexGroup", "CoRequired", "Requires",
@@ -223,6 +223,10 @@ class _DumpSchemaRequested(Exception):
223
223
  class _ParseError(Exception):
224
224
  """Raised for user-facing parse errors."""
225
225
 
226
+ def __init__(self, message: str, command_prefix: str | None = None):
227
+ super().__init__(message)
228
+ self.command_prefix = command_prefix
229
+
226
230
 
227
231
  def _strict_int(s: str) -> int:
228
232
  """Parse an integer string strictly -- no leading/trailing whitespace allowed.
@@ -999,9 +1003,14 @@ class App:
999
1003
  return cmd, rest
1000
1004
 
1001
1005
  # Step 3: parse remaining tokens for the resolved command
1002
- cmd, kwargs, post_global = _parse_command(
1003
- cmd, rest, self._global_flags, config_data=self._config_data,
1004
- )
1006
+ try:
1007
+ cmd, kwargs, post_global = _parse_command(
1008
+ cmd, rest, self._global_flags, config_data=self._config_data,
1009
+ )
1010
+ except _ParseError as e:
1011
+ prefix_parts = [self.name] + path + [cmd.name]
1012
+ e.command_prefix = " ".join(prefix_parts)
1013
+ raise
1005
1014
 
1006
1015
  # Step 4: merge global flag values into kwargs
1007
1016
  # Post-command global flags override pre-command ones
@@ -1292,7 +1301,8 @@ class App:
1292
1301
  sys.exit(0)
1293
1302
  except _ParseError as e:
1294
1303
  print(f"error: {e}", file=sys.stderr)
1295
- print(f"try '{self.name} --help'", file=sys.stderr)
1304
+ prefix = e.command_prefix or self.name
1305
+ print(f"try '{prefix} --help'", file=sys.stderr)
1296
1306
  sys.exit(1)
1297
1307
  else:
1298
1308
  if cmd.passthrough is not None:
@@ -1333,7 +1343,8 @@ class App:
1333
1343
  stdout_buf.write(path + "\n")
1334
1344
  except _ParseError as e:
1335
1345
  stderr_buf.write(f"error: {e}\n")
1336
- stderr_buf.write(f"try '{self.name} --help'\n")
1346
+ prefix = e.command_prefix or self.name
1347
+ stderr_buf.write(f"try '{prefix} --help'\n")
1337
1348
  exit_code = 1
1338
1349
  else:
1339
1350
  with contextlib.redirect_stdout(stdout_buf), contextlib.redirect_stderr(stderr_buf):
@@ -0,0 +1,60 @@
1
+ """Tests for command-specific help suggestions in parse error messages."""
2
+
3
+ import strictcli
4
+
5
+
6
+ def _make_app():
7
+ """Helper: app with a command that has a required flag."""
8
+ app = strictcli.App(name="myapp", version="1.0.0", help="test app")
9
+
10
+ @app.command("stream", help="stream data")
11
+ @strictcli.flag("target", type=str, help="the target")
12
+ def stream(target):
13
+ print(f"target={target}")
14
+
15
+ return app
16
+
17
+
18
+ def _make_group_app():
19
+ """Helper: app with a nested group command that has a required flag."""
20
+ app = strictcli.App(name="myapp", version="1.0.0", help="test app")
21
+ grp = app.group("config", help="manage configuration")
22
+
23
+ @grp.command("set", help="set a value")
24
+ @strictcli.flag("key", type=str, help="config key")
25
+ def set_(key):
26
+ print(f"key={key}")
27
+
28
+ return app
29
+
30
+
31
+ def test_missing_required_arg_suggests_subcommand_help():
32
+ """Missing required flag on subcommand suggests 'myapp stream --help'."""
33
+ app = _make_app()
34
+ r = app.test(["stream"])
35
+ assert r.exit_code == 1
36
+ assert "try 'myapp stream --help'" in r.stderr
37
+
38
+
39
+ def test_unknown_flag_suggests_subcommand_help():
40
+ """Unknown flag on subcommand suggests 'myapp stream --help'."""
41
+ app = _make_app()
42
+ r = app.test(["stream", "--bogus"])
43
+ assert r.exit_code == 1
44
+ assert "try 'myapp stream --help'" in r.stderr
45
+
46
+
47
+ def test_unknown_toplevel_command_suggests_app_help():
48
+ """Unknown top-level command suggests 'myapp --help' (no command_prefix)."""
49
+ app = _make_app()
50
+ r = app.test(["nonexistent"])
51
+ assert r.exit_code == 1
52
+ assert "try 'myapp --help'" in r.stderr
53
+
54
+
55
+ def test_nested_group_missing_arg_suggests_full_path():
56
+ """Missing required flag in group command suggests 'myapp config set --help'."""
57
+ app = _make_group_app()
58
+ r = app.test(["config", "set"])
59
+ assert r.exit_code == 1
60
+ assert "try 'myapp config set --help'" in r.stderr
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.8.5"
235
+ version = "0.8.6"
236
236
  source = { editable = "." }
237
237
 
238
238
  [package.dev-dependencies]
@@ -1 +0,0 @@
1
- fd52271bcf11157ae4eb47ee9e63d6ed5d9000b9
@@ -1 +0,0 @@
1
- 0.39.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