DotCache-Arena / dotcache /config_io.py
DeanoCalver's picture
Initial DotCache Arena Space upload
751ad26 verified
Raw
History Blame Contribute Delete
4.89 kB
from __future__ import annotations
from dataclasses import fields
from pathlib import Path
from typing import Any
from .config import DotCacheConfig
def _coerce_scalar(raw_value: str) -> Any:
value = raw_value.strip()
if value == "[]":
return []
if value.lower() in {"true", "false"}:
return value.lower() == "true"
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
return value[1:-1]
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
return value
def load_flat_yaml(path: str | Path) -> dict[str, Any]:
config_path = Path(path)
data: dict[str, Any] = {}
for line_number, raw_line in enumerate(config_path.read_text(encoding="utf-8").splitlines(), start=1):
stripped = raw_line.strip()
if not stripped or stripped.startswith("#"):
continue
if ":" not in raw_line:
raise ValueError(f"invalid config line {line_number}: expected 'key: value'")
key, raw_value = raw_line.split(":", 1)
key = key.strip()
if not key:
raise ValueError(f"invalid config line {line_number}: missing key")
data[key] = _coerce_scalar(raw_value)
return data
def load_layer_profile(path: str | Path) -> dict[str, Any]:
profile_path = Path(path)
lines = profile_path.read_text(encoding="utf-8").splitlines()
data: dict[str, Any] = {}
index = 0
while index < len(lines):
raw_line = lines[index]
stripped = raw_line.strip()
if not stripped or stripped.startswith("#"):
index += 1
continue
if raw_line[:1].isspace():
raise ValueError(f"invalid profile line {index + 1}: unexpected indentation at top level")
if ":" not in raw_line:
raise ValueError(f"invalid profile line {index + 1}: expected 'key: value'")
key, raw_value = raw_line.split(":", 1)
key = key.strip()
value = raw_value.strip()
if value:
if value == ">":
folded: list[str] = []
index += 1
while index < len(lines):
continuation = lines[index]
if continuation.strip() and not continuation[:1].isspace():
break
if continuation.strip():
folded.append(continuation.strip())
index += 1
data[key] = " ".join(folded)
continue
data[key] = _coerce_scalar(value)
index += 1
continue
block_lines: list[str] = []
index += 1
while index < len(lines):
continuation = lines[index]
if continuation.strip() and not continuation[:1].isspace():
break
if continuation.strip():
block_lines.append(continuation)
index += 1
if not block_lines:
data[key] = {}
continue
stripped_block = [line.lstrip() for line in block_lines]
if all(line.startswith("- ") for line in stripped_block):
data[key] = [_coerce_scalar(line[2:]) for line in stripped_block]
continue
nested: dict[str, Any] = {}
block_index = 0
while block_index < len(block_lines):
nested_line = stripped_block[block_index]
if ":" not in nested_line:
raise ValueError(f"invalid nested profile entry under '{key}': expected 'child: value'")
nested_key, nested_value_raw = nested_line.split(":", 1)
nested_key = nested_key.strip()
nested_value = nested_value_raw.strip()
if nested_value == ">":
folded: list[str] = []
block_index += 1
while block_index < len(block_lines):
candidate = block_lines[block_index]
if len(candidate) - len(candidate.lstrip()) <= len(block_lines[0]) - len(block_lines[0].lstrip()):
break
if candidate.strip():
folded.append(candidate.strip())
block_index += 1
nested[nested_key] = " ".join(folded)
continue
nested[nested_key] = _coerce_scalar(nested_value)
block_index += 1
data[key] = nested
return data
def load_dotcache_config(path: str | Path) -> DotCacheConfig:
raw = load_flat_yaml(path)
valid_keys = {field.name for field in fields(DotCacheConfig)}
unknown_keys = sorted(set(raw) - valid_keys)
if unknown_keys:
raise ValueError(f"unknown DotCacheConfig fields in {path}: {', '.join(unknown_keys)}")
return DotCacheConfig(**raw)