Skip to content
Merged
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
55 changes: 47 additions & 8 deletions scripts/uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,50 @@ def _content_indent(ln):
return ind


def _strip_inline_comment(s):
"""Drop a trailing YAML `#` comment (whitespace-preceded, outside quotes)."""
in_single = in_double = False
for i, ch in enumerate(s):
if ch == "'" and not in_double:
in_single = not in_single
elif ch == '"' and not in_single:
in_double = not in_double
elif ch == "#" and not in_single and not in_double and i > 0 and s[i - 1] in " \t":
return s[:i].rstrip()
return s.rstrip()


def _unquote(s):
"""Strip matching quote delimiters, preserving any inner whitespace."""
s = s.strip()
if len(s) >= 2 and s[0] == s[-1] and s[0] in "\"'":
return s[1:-1]
return s


def _mapping_key(s):
"""Unquoted key of a `key: ...` line (comment stripped), else None."""
s = _strip_inline_comment(s)
if ":" not in s:
return None
return _unquote(s.split(":", 1)[0].strip())


def _provider_value(s):
"""Unquoted value after `provider:` (comment stripped), else None."""
s = _strip_inline_comment(s)
if not s.startswith("provider:"):
return None
return _unquote(s.split(":", 1)[1].strip())


def _is_builder_item(s):
return s == "builder" or s == "- builder" or (s.startswith("-") and s[1:].strip() == "builder")
s = _strip_inline_comment(s).strip()
if s == "builder":
return True
if s.startswith("-"):
return _unquote(s[1:].strip()) == "builder"
return False


removed = []
Expand Down Expand Up @@ -109,8 +151,9 @@ def _cleanup(lines):
path = [k for (_i, k) in stack]

# 1) provider blocks: aws-builder:/builder: directly under `providers`
if s in ("aws-builder:", "builder:") and path == ["providers"]:
removed.append("providers:" + s.rstrip(":"))
provider_slug = _mapping_key(s) if path == ["providers"] else None
if provider_slug in ("aws-builder", "builder"):
removed.append("providers:" + provider_slug)
emptied.add(tuple(path))
ki = ind
j = i + 1
Expand Down Expand Up @@ -142,11 +185,7 @@ def _cleanup(lines):
continue

# 3) dangling model.provider pointing at a removed slug
if (
s in ("provider: aws-builder", "provider: builder",
'provider: "aws-builder"', 'provider: "builder"')
and path == ["model"]
):
if path == ["model"] and _provider_value(s) in ("aws-builder", "builder"):
removed.append("model.provider")
emptied.add(tuple(path))
i += 1
Expand Down
119 changes: 119 additions & 0 deletions tests/test_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,122 @@ def test_uninstall_keeps_compact_sibling_under_cli():
out, removed = _run(cfg)
_assert(out, removed, absent=["- builder"], present=["cli:", "- ask_q"])
assert removed.count("list:builder") == 2


def test_uninstall_handles_quoted_provider_keys_and_values():
# Quoted mapping keys (`"aws-builder":`) and single-quoted values
# (`provider: 'builder'`) must still be matched and removed.
cfg = textwrap.dedent(
"""\
providers:
"aws-builder":
type: aws-bid
'builder':
type: foo
model:
provider: 'builder'
temperature: 0.7
"""
)
out, removed = _run(cfg)
_assert(
out,
removed,
absent=[
'"aws-builder":',
"'builder':",
"type: aws-bid",
"type: foo",
"provider: 'builder'",
],
present=["temperature: 0.7"],
)
assert removed.count("providers:aws-builder") == 1
assert removed.count("providers:builder") == 1
assert "model.provider" in removed


def test_uninstall_handles_quoted_list_items():
# Quoted block-sequence items (`- "builder"`, `- 'builder'`) are still the
# plugin's enabled/toolset entry and must be removed, not left dangling.
cfg = textwrap.dedent(
"""\
plugins:
enabled:
- "builder"
- 'builder'
- other
platform_toolsets:
cli:
- "builder"
"""
)
out, removed = _run(cfg)
_assert(
out,
removed,
absent=['- "builder"', "- 'builder'", "- builder"],
present=["- other"],
)
assert removed.count("list:builder") == 3


def test_uninstall_handles_inline_comments():
# Inline `#` comments on the provider key, list item and model.provider
# line must not defeat removal (the comment travels with its line).
cfg = textwrap.dedent(
"""\
providers:
aws-builder: # my provider
type: aws-bid
plugins:
enabled:
- builder # enabled
model:
provider: builder # points at builder
temperature: 0.7
"""
)
out, removed = _run(cfg)
_assert(
out,
removed,
absent=["aws-builder:", "type: aws-bid", "- builder", "provider: builder"],
present=["temperature: 0.7"],
removed_has="providers:aws-builder",
)
assert "list:builder" in removed
assert "model.provider" in removed


def test_uninstall_preserves_quoted_whitespace_scalars():
# A quoted scalar with intentional inner whitespace (" builder ") is a
# DIFFERENT YAML value from `builder` — uninstall must not remove it.
cfg = textwrap.dedent(
"""\
providers:
" builder ":
type: foo
plugins:
enabled:
- " builder "
- other
model:
provider: " builder "
temperature: 0.7
"""
)
out, removed = _run(cfg)
_assert(
out,
removed,
present=[
'" builder ":',
"type: foo",
'- " builder "',
"- other",
'provider: " builder "',
"temperature: 0.7",
],
)
assert removed == []
Loading