104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
"""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
|