"""Context placeholders {{key}}. Implementations register here; templates never define ad-hoc keys.""" from __future__ import annotations import re from dataclasses import dataclass from typing import Any, Callable CONTEXT_PATTERN = re.compile(r"\{\{([a-z][a-z0-9_]*)\}\}") class PlaceholderError(Exception): def __init__(self, code: str, message: str): super().__init__(message) self.code = code self.message = message @dataclass(frozen=True) class Placeholder: key: str description: str data_class: str resolver: Callable[[dict[str, Any]], Any] _REGISTRY: dict[str, Placeholder] = {} def register(placeholder: Placeholder) -> None: if placeholder.data_class == "A": raise PlaceholderError( "class_a_context_key_forbidden", "Klasse-A-Werte gehören nicht in {{key}}-Kontext. Identität läuft über [[…]].", ) _REGISTRY[placeholder.key] = placeholder def get(key: str) -> Placeholder | None: return _REGISTRY.get(key) def list_placeholders() -> list[dict]: return [ { "key": item.key, "description": item.description, "data_class": item.data_class, "syntax": f"{{{{{item.key}}}}}", } for item in sorted(_REGISTRY.values(), key=lambda item: item.key) ] def extract_keys(template: str) -> list[str]: return CONTEXT_PATTERN.findall(template or "") def resolve_template(template: str, context: dict[str, Any]) -> str: keys = extract_keys(template) unknown = sorted({key for key in keys if key not in _REGISTRY}) if unknown: raise PlaceholderError( "unknown_placeholder", "Unbekannte Kontext-Platzhalter: " + ", ".join(f"{{{{{key}}}}}" for key in unknown), ) rendered = template for key in keys: spec = _REGISTRY[key] value = spec.resolver(context) rendered = rendered.replace("{{" + key + "}}", "" if value is None else str(value)) return rendered