Project-Rivet / v2 /engine /config.py
HumboldtJoker's picture
Upload folder using huggingface_hub
4554903 verified
Raw
History Blame Contribute Delete
4.25 kB
"""Config loader for kintsugi_config.yaml.
Uses PyYAML when available. Falls back to a strict-subset parser so the
deployment target needs nothing beyond the stdlib. The subset covers
exactly what kintsugi_config.yaml uses: nested mappings by indentation,
`- item` lists, scalars (str/int/float/bool/null), quoted strings, and
`#` comments. No anchors, no multi-line strings, no flow collections.
"""
from pathlib import Path
from typing import Any
DEFAULT_CONFIG_PATH = Path(__file__).parent.parent / "kintsugi_config.yaml"
def load_config(path: Path | str = DEFAULT_CONFIG_PATH) -> dict:
text = Path(path).read_text()
try:
import yaml
return yaml.safe_load(text)
except ImportError:
return _parse_subset(text)
def _scalar(raw: str) -> Any:
raw = raw.strip()
if raw.startswith('"') and raw.endswith('"') and len(raw) >= 2:
return raw[1:-1]
if raw.startswith("'") and raw.endswith("'") and len(raw) >= 2:
return raw[1:-1]
low = raw.lower()
if low in ("true", "yes"):
return True
if low in ("false", "no"):
return False
if low in ("null", "~", ""):
return None
try:
return int(raw)
except ValueError:
pass
try:
return float(raw)
except ValueError:
pass
return raw
def _strip_comment(line: str) -> str:
"""Remove a trailing comment, respecting quoted strings."""
in_s = in_d = False
for i, ch in enumerate(line):
if ch == "'" and not in_d:
in_s = not in_s
elif ch == '"' and not in_s:
in_d = not in_d
elif ch == "#" and not in_s and not in_d:
return line[:i]
return line
def _parse_subset(text: str) -> dict:
# Tokenize into (indent, content) lines, skipping blanks/comments.
lines: list[tuple[int, str]] = []
for raw in text.splitlines():
stripped = _strip_comment(raw).rstrip()
if not stripped.strip():
continue
indent = len(stripped) - len(stripped.lstrip(" "))
lines.append((indent, stripped.strip()))
root, _ = _parse_block(lines, 0, 0)
return root
def _parse_block(lines: list, pos: int, indent: int) -> tuple[Any, int]:
"""Parse a block starting at lines[pos] with the given indent level."""
if pos >= len(lines):
return {}, pos
if lines[pos][1].startswith("- "):
result_list: list = []
while pos < len(lines) and lines[pos][0] == indent and lines[pos][1].startswith("- "):
item = lines[pos][1][2:].strip()
if ":" in item and not item.startswith(("'", '"')):
# list of single-key mappings: "- key: value"
key, _, val = item.partition(":")
entry = {key.strip(): _scalar(val)}
pos += 1
# absorb continuation keys indented under the dash
while pos < len(lines) and lines[pos][0] > indent and not lines[pos][1].startswith("- "):
k, _, v = lines[pos][1].partition(":")
entry[k.strip()] = _scalar(v)
pos += 1
result_list.append(entry)
else:
result_list.append(_scalar(item))
pos += 1
return result_list, pos
result: dict = {}
while pos < len(lines):
line_indent, content = lines[pos]
if line_indent < indent:
break
if line_indent > indent:
raise ValueError(f"Unexpected indent in config near: {content!r}")
key, sep, val = content.partition(":")
if not sep:
raise ValueError(f"Expected 'key:' in config near: {content!r}")
key = key.strip()
val = val.strip()
if val:
result[key] = _scalar(val)
pos += 1
else:
pos += 1
if pos < len(lines) and lines[pos][0] > indent:
child, pos = _parse_block(lines, pos, lines[pos][0])
result[key] = child
else:
result[key] = None
return result, pos
if __name__ == "__main__":
import json
cfg = _parse_subset(Path(DEFAULT_CONFIG_PATH).read_text())
print(json.dumps(cfg, indent=2))