72 lines
2.0 KiB
Python
72 lines
2.0 KiB
Python
"""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
|