Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/specify_cli/integrations/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,18 @@ def _parse_integration_options(integration: Any, raw_options: str) -> dict[str,
"""
import shlex
parsed: dict[str, Any] = {}
tokens = shlex.split(raw_options)
try:
tokens = shlex.split(raw_options)
except ValueError as error:
# shlex raises on malformed input (e.g. an unbalanced quote in
# --integration-options='--commands-dir "foo'). Surface the clean CLI
# error every other bad-input path here produces instead of leaking a
# raw ValueError traceback.
console.print(
f"[red]Error:[/red] Could not parse integration options "
f"'{raw_options}': {error}."
)
raise typer.Exit(1) from error
declared_options = list(integration.options())
declared = {opt.name.lstrip("-"): opt for opt in declared_options}
allowed = ", ".join(sorted(opt.name for opt in declared_options))
Expand Down
18 changes: 18 additions & 0 deletions tests/integrations/test_integration_subcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -2675,6 +2675,24 @@ def test_equals_form_parsed(self):
assert result_space["commands_dir"] == "./mydir"
assert result_equals["commands_dir"] == "./mydir"

def test_malformed_quoting_exits_cleanly(self):
"""An unbalanced quote must raise a clean typer.Exit, not a ValueError.

shlex.split raises ValueError('No closing quotation') on malformed
input. Every other bad-input path in this function exits via
typer.Exit(1); a raw ValueError traceback would be inconsistent and
user-hostile for a plain CLI-flag typo."""
import typer

from specify_cli.integrations._commands import _parse_integration_options
from specify_cli.integrations import get_integration

integration = get_integration("generic")
assert integration is not None

with pytest.raises(typer.Exit):
_parse_integration_options(integration, '--commands-dir "foo')


class TestUninstallNoManifestClearsInitOptions:
def test_init_options_cleared_on_no_manifest_uninstall(self, tmp_path):
Expand Down