stage-1: project bootstrap
Structure, config loader, personality/tools/memory from clawd, venv, 22 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
76
src/config.py
Normal file
76
src/config.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user