""" Config loader for Echo-Core. Loads config.json from project root, provides dot-notation access, save/reload support, and raw dict access. """ import json from pathlib import Path from typing import Any _DEFAULT_CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.json" class Config: """Configuration manager with dot-notation get/set.""" def __init__(self, path: Path | str | None = None): self._path = Path(path) if path else _DEFAULT_CONFIG_PATH self._data: dict = {} self.reload() def reload(self) -> None: """Reload config from disk.""" with open(self._path, "r", encoding="utf-8") as f: self._data = json.load(f) def save(self) -> None: """Write current config back to disk.""" with open(self._path, "w", encoding="utf-8") as f: json.dump(self._data, f, indent=2, ensure_ascii=False) f.write("\n") def get(self, key: str, default: Any = None) -> Any: """ Get a value using dot-notation. Examples: config.get("bot.name") -> "Echo" config.get("channels") -> {} config.get("missing", "fallback") -> "fallback" """ parts = key.split(".") current = self._data for part in parts: if isinstance(current, dict) and part in current: current = current[part] else: return default return current def set(self, key: str, value: Any) -> None: """ Set a value using dot-notation. Creates intermediate dicts as needed. Examples: config.set("bot.name", "NewName") config.set("new.nested.key", 42) """ parts = key.split(".") current = self._data for part in parts[:-1]: if part not in current or not isinstance(current[part], dict): current[part] = {} current = current[part] current[parts[-1]] = value def raw(self) -> dict: """Return the raw config dict (reference, not copy).""" return self._data def load_config(path: Path | str | None = None) -> Config: """Convenience function to create a Config instance.""" return Config(path)