56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
|
|
_DOLLAR_FIELD_PATTERN = re.compile(r"(?<!\\)\$\{(.*?)(?<!\\)\}")
|
|
_BRACE_FIELD_PATTERN = re.compile(r"(?<!\\)\{\{\s*(.*?)\s*\}\}")
|
|
|
|
|
|
def normalize_template_key(raw: str) -> str:
|
|
key = raw.strip()
|
|
if key.startswith("fields."):
|
|
key = key.removeprefix("fields.")
|
|
elif key.startswith("local."):
|
|
key = "local::" + key.removeprefix("local.")
|
|
elif key.startswith("global."):
|
|
key = "global::" + key.removeprefix("global.")
|
|
|
|
if key.startswith("local::") or key.startswith("global::"):
|
|
return key
|
|
if key.startswith("local:"):
|
|
return "local::" + key.removeprefix("local:")
|
|
if key.startswith("global:"):
|
|
return "global::" + key.removeprefix("global:")
|
|
return key
|
|
|
|
|
|
def render_template(
|
|
template: str,
|
|
values: Mapping[str, Any],
|
|
*,
|
|
keep_missing: bool = True,
|
|
) -> str:
|
|
def replace(match: re.Match[str]) -> str:
|
|
key = normalize_template_key(match.group(1))
|
|
if key in values:
|
|
value = values[key]
|
|
return "" if value is None else str(value)
|
|
return match.group(0) if keep_missing else ""
|
|
|
|
rendered = _DOLLAR_FIELD_PATTERN.sub(replace, template)
|
|
rendered = _BRACE_FIELD_PATTERN.sub(replace, rendered)
|
|
return rendered.replace(r"\${", "${").replace(r"\}", "}")
|
|
|
|
|
|
def find_unresolved_placeholders(text: str | None) -> set[str]:
|
|
if not text:
|
|
return set()
|
|
return {
|
|
normalize_template_key(match.group(1))
|
|
for pattern in (_DOLLAR_FIELD_PATTERN, _BRACE_FIELD_PATTERN)
|
|
for match in pattern.finditer(text)
|
|
}
|