Kansho/backend/placeholders.py

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