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
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,16 @@ def _run(command: str) -> subprocess.CompletedProcess:
)


def load_selection_json(path: Path) -> dict:
"""Load a cleanup selection JSON, tolerating a UTF-8 BOM.

Windows PowerShell `Set-Content -Encoding UTF8` (and several editors)
write a BOM. `json.loads` rejects a leading U+FEFF under encoding=utf-8;
utf-8-sig strips it when present and is a no-op for BOM-less files.
"""
return json.loads(path.read_text(encoding="utf-8-sig"))


def main(argv: list[str]) -> int:
ap = argparse.ArgumentParser(description="Apply a cleanup selection JSON (Windows)")
ap.add_argument("selection", help="path to cleanup-selection-<ts>.json")
Expand All @@ -398,7 +408,7 @@ def main(argv: list[str]) -> int:
print(f"selection file not found: {sel_path}", file=sys.stderr)
return 1
try:
sel = json.loads(sel_path.read_text(encoding="utf-8"))
sel = load_selection_json(sel_path)
except Exception as exc:
print(f"invalid selection JSON: {exc}", file=sys.stderr)
return 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1238,7 +1238,7 @@ def main(argv: list[str]) -> int:
if not data_path.is_file():
print(f"data file not found: {data_path}", file=sys.stderr)
return 2
data = json.loads(data_path.read_text(encoding="utf-8"))
data = json.loads(data_path.read_text(encoding="utf-8-sig"))
html_content = render_html(data)

handler = make_handler(html_content, data.get("categories", []))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,56 @@ def _load_validator():
]


def test_load_selection_bom() -> int:
"""Regression for issue #1: PowerShell UTF8 selection JSON carries a BOM."""
import json
import tempfile
from pathlib import Path

mod = _load_validator()
payload = {
"selected_items": [
{
"id": "t1",
"label": "temp sample",
"path": r"C:\Users\dev\AppData\Local\Temp\sample",
"command": (
r"Remove-Item -LiteralPath "
r"'C:\Users\dev\AppData\Local\Temp\sample' "
r"-Recurse -Force"
),
"size_bytes": 1,
}
],
"protected_overrides": [],
}
body = json.dumps(payload, indent=2).encode("utf-8")
bom_body = b"\xef\xbb\xbf" + body
failed = 0
with tempfile.TemporaryDirectory() as td:
plain = Path(td) / "plain.json"
bom = Path(td) / "bom.json"
plain.write_bytes(body)
bom.write_bytes(bom_body)
try:
a = mod.load_selection_json(plain)
b = mod.load_selection_json(bom)
except Exception as exc:
print(f"FAIL [BOM load raised]: {exc}")
return 1
if a != payload or b != payload:
print("FAIL [BOM load] decoded payload mismatch")
failed += 1
else:
print("OK [BOM load] utf-8 and utf-8-sig selection JSON both load")
code = mod.main([str(HERE / "apply-cleanup-selection.py"), str(bom), "--dry-run"])
if code != 0:
print(f"FAIL [BOM dry-run] exit={code}")
failed += 1
else:
print("OK [BOM dry-run] apply --dry-run accepts BOM selection")
return failed

def main() -> int:
os.environ.update(FAKE_ENV)
mod = _load_validator()
Expand All @@ -158,8 +208,13 @@ def main() -> int:
print(f" cmd = {command!r}")
print(f" expected = ok={exp_ok} prot={exp_prot}")
print(f" got = ok={ok} prot={prot} reason={reason!r}")
bom_failed = test_load_selection_bom()
failed += bom_failed
if bom_failed == 0:
passed += 2

print()
print(f"=== {passed} passed | {failed} failed | {len(CASES)} total ===")
print(f"=== {passed} passed | {failed} failed | {len(CASES)} total (+ BOM checks) ===")
return 0 if failed == 0 else 1


Expand Down