Skip to content

Commit 3183fd1

Browse files
fix(integrations): exit cleanly on malformed --integration-options quoting
_parse_integration_options called shlex.split(raw_options) unguarded. an unbalanced quote (e.g. --integration-options='--commands-dir "foo') makes shlex raise ValueError('No closing quotation'), so a raw traceback escaped instead of the typer.Exit(1) error every other bad-input path in this function produces. reachable from specify init and every integration install/switch/ upgrade/migrate that accepts --integration-options. wrap the split and convert ValueError into the same clean CLI error. added a regression test; confirmed it fails on the pre-fix code (raw ValueError).
1 parent 3f7392a commit 3183fd1

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

src/specify_cli/integrations/_helpers.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,18 @@ def _parse_integration_options(integration: Any, raw_options: str) -> dict[str,
190190
"""
191191
import shlex
192192
parsed: dict[str, Any] = {}
193-
tokens = shlex.split(raw_options)
193+
try:
194+
tokens = shlex.split(raw_options)
195+
except ValueError as error:
196+
# shlex raises on malformed input (e.g. an unbalanced quote in
197+
# --integration-options='--commands-dir "foo'). Surface the clean CLI
198+
# error every other bad-input path here produces instead of leaking a
199+
# raw ValueError traceback.
200+
console.print(
201+
f"[red]Error:[/red] Could not parse integration options "
202+
f"'{raw_options}': {error}."
203+
)
204+
raise typer.Exit(1) from error
194205
declared_options = list(integration.options())
195206
declared = {opt.name.lstrip("-"): opt for opt in declared_options}
196207
allowed = ", ".join(sorted(opt.name for opt in declared_options))

tests/integrations/test_integration_subcommand.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,24 @@ def test_equals_form_parsed(self):
26752675
assert result_space["commands_dir"] == "./mydir"
26762676
assert result_equals["commands_dir"] == "./mydir"
26772677

2678+
def test_malformed_quoting_exits_cleanly(self):
2679+
"""An unbalanced quote must raise a clean typer.Exit, not a ValueError.
2680+
2681+
shlex.split raises ValueError('No closing quotation') on malformed
2682+
input. Every other bad-input path in this function exits via
2683+
typer.Exit(1); a raw ValueError traceback would be inconsistent and
2684+
user-hostile for a plain CLI-flag typo."""
2685+
import typer
2686+
2687+
from specify_cli.integrations._commands import _parse_integration_options
2688+
from specify_cli.integrations import get_integration
2689+
2690+
integration = get_integration("generic")
2691+
assert integration is not None
2692+
2693+
with pytest.raises(typer.Exit):
2694+
_parse_integration_options(integration, '--commands-dir "foo')
2695+
26782696

26792697
class TestUninstallNoManifestClearsInitOptions:
26802698
def test_init_options_cleared_on_no_manifest_uninstall(self, tmp_path):

0 commit comments

Comments
 (0)