Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""The ``--groups`` map file: parse ``{group_name: [refdes, ...]}`` (TOML/JSON).
Parsing lives next to the planner that consumes the map rather than in the
command module, so a future non-CLI caller (a hook, the workstation app) reads
the same file with the same rules. The engine reports malformed input as
:class:`GroupsFileError`; turning that into an exit code and a message is the
caller's job — this layer never decides how a process should die.
"""
from __future__ import annotations
import json
from pathlib import Path
__all__ = ["GroupsFileError", "load_groups_file"]
class GroupsFileError(ValueError):
"""A ``--groups`` file that cannot be used, with the reason as its message.
``missing`` distinguishes "the path is not a usable file" (the caller maps
it to the not-found exit class) from "the contents are wrong" (a usage
error) — the two classes the CLI has always reported for this input.
"""
def __init__(self, message: str, *, missing: bool = False) -> None:
super().__init__(message)
self.missing = missing
def load_groups_file(path: str) -> dict[str, list[str]]:
"""Parse a ``--groups`` map: ``{group_name: [refdes, ...]}`` (TOML or JSON).
TOML uses a top-level ``[groups]`` table (or bare ``name = [...]`` tables);
JSON is either ``{"groups": {...}}`` or the bare mapping. Order is preserved
(groups stack down the page in file order). Malformed input raises
:class:`GroupsFileError`.
"""
p = Path(path)
try:
raw = p.read_bytes()
except FileNotFoundError:
raise GroupsFileError(f"groups file not found: {path}", missing=True)
except OSError as exc: # a directory, mode 000, ... — never a traceback
raise GroupsFileError(
f"groups file unreadable: {path}: {exc.strerror or exc}",
missing=True)
try:
text = raw.decode("utf-8")
except UnicodeDecodeError as exc:
raise GroupsFileError(
f"groups file is not UTF-8 text: {path} "
f"(byte 0x{raw[exc.start]:02x} at offset {exc.start})")
data: object
if p.suffix.lower() == ".json":
try:
data = json.loads(text)
except json.JSONDecodeError as e:
raise GroupsFileError(f"invalid groups JSON: {e}")
else:
import tomllib
try:
data = tomllib.loads(text)
except tomllib.TOMLDecodeError as e:
raise GroupsFileError(f"invalid groups TOML: {e}")
if isinstance(data, dict) and isinstance(data.get("groups"), dict):
data = data["groups"]
if not isinstance(data, dict) or not data:
raise GroupsFileError(
"groups file must map group-name -> [refdes, ...]")
out: dict[str, list[str]] = {}
for gname, refs in data.items():
if (not isinstance(refs, list)
or not all(isinstance(r, str) and r for r in refs)):
raise GroupsFileError(
f"group {gname!r} must be a list of designators")
out[str(gname)] = list(refs)
return out
2126c3284This file is pinned to the selected release. Download the original to open it in your design tool.