"""Minimal YAML mapping loader for layer and PEP-stance declarations. Stdlib only. Handles the subset this repository actually writes: nested maps, lists of scalars, lists of maps, quoted strings, booleans, null, integers, and empty lists. Not a general YAML implementation. """ from __future__ import annotations from pathlib import Path from typing import Any def load_mapping(path: Path) -> dict[str, Any]: data = load_mapping_text(path.read_text()) if not isinstance(data, dict): raise ValueError(f"{path} did not parse as a mapping") return data def load_mapping_text(text: str) -> dict[str, Any]: data = _parse(text) if not isinstance(data, dict): raise ValueError("YAML text did not parse as a mapping") return data def _parse(text: str) -> Any: lines: list[tuple[int, str]] = [] for raw in text.splitlines(): stripped = raw.split("#", 1)[0].rstrip() if not stripped: continue indent = len(raw) - len(raw.lstrip(" ")) lines.append((indent, stripped.lstrip(" "))) value, _ = _parse_block(lines, 0, 0) return value def _parse_block( lines: list[tuple[int, str]], index: int, indent: int ) -> tuple[Any, int]: if index >= len(lines): return {}, index current_indent, content = lines[index] if current_indent < indent: return {}, index if content.startswith("- "): return _parse_list(lines, index, current_indent) return _parse_map(lines, index, current_indent) def _parse_map( lines: list[tuple[int, str]], index: int, indent: int ) -> tuple[dict[str, Any], int]: result: dict[str, Any] = {} while index < len(lines): current_indent, content = lines[index] if current_indent < indent: break if current_indent > indent: raise ValueError(f"unexpected indent at {content!r}") if content.startswith("- "): break key, separator, remainder = content.partition(":") if not separator: raise ValueError(f"expected key: value, got {content!r}") key = _parse_scalar(key.strip()) if not isinstance(key, str): key = str(key) remainder = remainder.strip() index += 1 if remainder in ("", "|", ">"): if index < len(lines) and lines[index][0] > indent: value, index = _parse_block(lines, index, lines[index][0]) else: value = None else: value = _parse_scalar(remainder) result[key] = value return result, index def _parse_list( lines: list[tuple[int, str]], index: int, indent: int ) -> tuple[list[Any], int]: result: list[Any] = [] while index < len(lines): current_indent, content = lines[index] if current_indent < indent: break if current_indent > indent: raise ValueError(f"unexpected indent at {content!r}") if not content.startswith("- "): break item = content[2:].strip() index += 1 if not item: if index < len(lines) and lines[index][0] > indent: value, index = _parse_block(lines, index, lines[index][0]) else: value = None result.append(value) continue if ":" in item and not item.startswith(("'", '"')): key, _, remainder = item.partition(":") mapping: dict[str, Any] = {key.strip(): _parse_scalar(remainder.strip())} if index < len(lines) and lines[index][0] > indent: nested, index = _parse_block(lines, index, lines[index][0]) if isinstance(nested, dict): mapping.update(nested) result.append(mapping) else: result.append(_parse_scalar(item)) return result, index def _parse_scalar(text: str) -> Any: if text in ("", "~", "null", "Null", "NULL"): return None if text in ("true", "True"): return True if text in ("false", "False"): return False if text == "[]": return [] if text == "{}": return {} if len(text) >= 2 and text[0] == text[-1] and text[0] in "'\"": return text[1:-1] try: return int(text) except ValueError: return text