from dataclasses import dataclass from pathlib import Path from typing import Any, Dict, Optional from .utils import read_yaml class ConfigError(ValueError): """Raised when Nutfile contents are invalid.""" @dataclass(frozen=True) class BuildConfig: name: str build_folder: str entry: str @property def buildFolder(self) -> str: # Compatibility alias for existing camelCase references. return self.build_folder @dataclass(frozen=True) class Config: path: Path raw: Dict[str, Any] build: BuildConfig @property def buildFolder(self) -> str: # Compatibility alias for older call-sites. return self.build.build_folder def _ensure_dict(value: Any, context: str) -> Dict[str, Any]: if not isinstance(value, dict): raise ConfigError(f"{context} must be a mapping") return value def _read_string(mapping: Dict[str, Any], key: str, default: str) -> str: value = mapping.get(key, default) if not isinstance(value, str) or not value.strip(): raise ConfigError(f"build.{key} must be a non-empty string") return value def load_config(path: str = "Nutfile") -> Config: config_path = Path(path) raw_data: Optional[Dict[str, Any]] = read_yaml(str(config_path)) if raw_data is None: raw_data = {} raw_dict = _ensure_dict(raw_data, "Nutfile root") build_section = raw_dict.get("build", {}) build_dict = _ensure_dict(build_section, "build section") build = BuildConfig( name=_read_string(build_dict, "name", "Example Build"), build_folder=_read_string(build_dict, "buildFolder", "build"), entry=_read_string(build_dict, "entry", "src/main.nut"), ) return Config( path=config_path, raw=raw_dict, build=build, )