"""Named profiles: an enabled-set plus a load order (later = higher priority).""" from __future__ import annotations from pathlib import Path from .util import atomic_write_json, read_json DEFAULT = "Default" class ProfileError(Exception): pass class Profiles: def __init__(self, path: Path): self.path = path self.active = DEFAULT self.data: dict[str, dict] = {DEFAULT: {"enabled": [], "order": []}} raw = read_json(path, None) if isinstance(raw, dict) and isinstance(raw.get("profiles"), dict) and raw["profiles"]: self.data = {n: {"enabled": list(p.get("enabled", [])), "order": list(p.get("order", []))} for n, p in raw["profiles"].items() if isinstance(p, dict)} or self.data self.active = raw.get("active") if raw.get("active") in self.data else next(iter(self.data)) def save(self) -> None: atomic_write_json(self.path, {"active": self.active, "profiles": self.data}) @property def names(self) -> list[str]: return list(self.data) @property def current(self) -> dict: return self.data[self.active] # -- reconciliation with the library ------------------------------------ def reconcile(self, known_ids: list[str]) -> None: known = set(known_ids) for p in self.data.values(): p["order"] = [i for i in dict.fromkeys(p["order"]) if i in known] p["order"] += [i for i in known_ids if i not in p["order"]] p["enabled"] = [i for i in dict.fromkeys(p["enabled"]) if i in known] # -- queries ------------------------------------------------------------ def is_enabled(self, mod_id: str) -> bool: return mod_id in self.current["enabled"] def order(self) -> list[str]: return list(self.current["order"]) def enabled_in_order(self) -> list[str]: en = set(self.current["enabled"]) return [i for i in self.current["order"] if i in en] # -- edits -------------------------------------------------------------- def set_enabled(self, mod_id: str, on: bool) -> None: en = self.current["enabled"] if on and mod_id not in en: en.append(mod_id) if mod_id not in self.current["order"]: self.current["order"].append(mod_id) elif not on and mod_id in en: en.remove(mod_id) def move(self, mod_id: str, new_index: int) -> None: order = self.current["order"] if mod_id not in order: return order.remove(mod_id) order.insert(max(0, min(new_index, len(order))), mod_id) def remove_mod(self, mod_id: str) -> None: for p in self.data.values(): p["order"] = [i for i in p["order"] if i != mod_id] p["enabled"] = [i for i in p["enabled"] if i != mod_id] def create(self, name: str, copy_from: str | None = None) -> None: name = name.strip() if not name or name in self.data: raise ProfileError("A profile with that name already exists" if name else "Name required") src = self.data.get(copy_from or "", {"enabled": [], "order": list(self.current["order"])}) self.data[name] = {"enabled": list(src["enabled"]), "order": list(src["order"])} def rename(self, old: str, new: str) -> None: new = new.strip() if old not in self.data or not new or new in self.data: raise ProfileError("Invalid profile name") self.data = {(new if k == old else k): v for k, v in self.data.items()} if self.active == old: self.active = new def delete(self, name: str) -> None: if len(self.data) <= 1: raise ProfileError("Cannot delete the last profile") self.data.pop(name, None) if self.active == name: self.active = next(iter(self.data)) def switch(self, name: str) -> None: if name not in self.data: raise ProfileError(f"No such profile: {name}") self.active = name