testsAndMisc/python_pkg/vscode_optimizer/_config_io.py
Krzysztof kuhy Rudnicki adf2e402df Grade against 3.14 too, not just run on it
`requires-python` and the CI pin already said 3.14, but ruff, mypy and pylint
were still grading `meta/` against 3.10 -- three tools judging an interpreter
nothing runs any more, and ruff withholding the pyupgrade fixes that only
become safe at the newer target. `pin_ci_python.py` rewrites these three knobs.

What the stale target was hiding: `datetime.timezone.utc` where `datetime.UTC`
is now correct (UP017), and ruff's stable formatter applying PEP 758 --
`except (A, B):` becomes `except A, B:`. That makes the source 3.14-only at the
syntax level, which is what `requires-python = ">=3.14"` already promised; kuhy
chose it deliberately over holding ruff a release back (2026-08-29).

Also drops a comment in `wsg_grabber/models.py` that the bump makes false: it
explained why the file used ``str, Enum`` instead of ``StrEnum`` under mypy at
3.10, sitting directly above a ``class Verdict(StrEnum)`` that had already
stopped obeying it.

ruff, ruff-format, mypy --strict and pylint all clean at the new target.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfnJmxxsZ534AKk16rC2Gr
2026-08-29 11:50:39 +02:00

105 lines
3.1 KiB
Python

"""Find VS Code installations and read and write their configuration.
Settings are JSON with Comments, which the stdlib cannot parse, so this
module carries a small JSONC reader. Every write is preceded by a backup.
"""
from __future__ import annotations
from datetime import UTC, datetime
import json
from pathlib import Path
import re
import shutil
from python_pkg.vscode_optimizer._types import _Opt, _Variant
def _discover_variants() -> list[_Variant]:
"""Find all installed VS Code variants."""
cfg = Path.home() / ".config"
cands = [
("VS Code (stable)", "Code", "code-flags.conf", "code"),
(
"VS Code Insiders",
"Code - Insiders",
"code-insiders-flags.conf",
"code-insiders",
),
("VSCodium", "VSCodium", "vscodium-flags.conf", "codium"),
]
found: list[_Variant] = []
for name, dir_name, flags_name, binary in cands:
sp = cfg / dir_name / "User" / "settings.json"
fp = cfg / flags_name
if sp.exists() or shutil.which(binary):
found.append(_Variant(name, sp, fp, binary))
return found
def _parse_jsonc(text: str) -> dict[str, object]:
"""Parse JSON with Comments (JSONC) used by VS Code."""
out: list[str] = []
i, n = 0, len(text)
while i < n:
ch = text[i]
if ch == '"':
j = i + 1
while j < n:
if text[j] == "\\":
j += 2
continue
if text[j] == '"':
j += 1
break
j += 1
out.append(text[i:j])
i = j
elif ch == "/" and i + 1 < n and text[i + 1] == "/":
while i < n and text[i] != "\n":
i += 1
elif ch == "/" and i + 1 < n and text[i + 1] == "*":
end = text.find("*/", i + 2)
i = end + 2 if end != -1 else n
else:
out.append(ch)
i += 1
cleaned = re.sub(r",(\s*[}\]])", r"\1", "".join(out))
if not cleaned.strip():
return {}
parsed: dict[str, object] = json.loads(cleaned)
return parsed
def _backup(path: Path) -> Path | None:
if not path.exists():
return None
ts = datetime.now(tz=UTC).strftime("%Y%m%dT%H%M%SZ")
dst = path.with_suffix(f".{ts}.bak")
shutil.copy2(path, dst)
return dst
def _read_settings(path: Path) -> dict[str, object]:
return _parse_jsonc(path.read_text()) if path.exists() else {}
def _write_settings(path: Path, current: dict[str, object], opts: list[_Opt]) -> None:
merged = {**current, **{o.key: o.value for o in opts}}
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(merged, indent=4, ensure_ascii=False) + "\n")
def _read_flags(path: Path) -> list[str]:
if not path.exists():
return []
return [
ln.strip()
for ln in path.read_text().splitlines()
if ln.strip() and not ln.strip().startswith("#")
]
def _write_flags(path: Path, flags: list[str]) -> None:
path.write_text("\n".join(flags) + "\n")