"""Config and recorded routines on disk.""" from __future__ import annotations import json from pathlib import Path ROOT = Path(__file__).resolve().parent.parent CONFIG_DIR = ROOT / "config" ROUTINE_DIR = CONFIG_DIR / "routines" def _merge(defaults, saved): """Saved values win; keys only in defaults are filled in.""" if not isinstance(defaults, dict) or not isinstance(saved, dict): return saved if saved is not None else defaults out = dict(defaults) for key, value in saved.items(): out[key] = _merge(defaults.get(key), value) if key in defaults else value return out def config_path(macro: str) -> Path: return CONFIG_DIR / f"{macro}.json" def load(macro: str, defaults: dict) -> dict: path = config_path(macro) if not path.exists(): return json.loads(json.dumps(defaults)) with path.open(encoding="utf-8") as handle: return _merge(defaults, json.load(handle)) def save(macro: str, data: dict) -> Path: CONFIG_DIR.mkdir(parents=True, exist_ok=True) path = config_path(macro) with path.open("w", encoding="utf-8") as handle: json.dump(data, handle, indent=2) handle.write("\n") return path def routine_path(name: str) -> Path: return ROUTINE_DIR / f"{name}.json" def load_routine(name: str) -> list[dict]: path = routine_path(name) if not path.exists(): raise FileNotFoundError(f"no routine called {name!r} — record one first") with path.open(encoding="utf-8") as handle: data = json.load(handle) return data.get("steps", []) if isinstance(data, dict) else data def save_routine(name: str, steps: list[dict], note: str = "") -> Path: ROUTINE_DIR.mkdir(parents=True, exist_ok=True) path = routine_path(name) with path.open("w", encoding="utf-8") as handle: json.dump({"note": note, "steps": steps}, handle, indent=2) handle.write("\n") return path def list_routines() -> list[str]: if not ROUTINE_DIR.exists(): return [] return sorted(path.stem for path in ROUTINE_DIR.glob("*.json"))