"""Kleine Hilfsfunktionen ohne Fachlogik.""" from __future__ import annotations import re import time from pathlib import Path from typing import Any import yaml _DURATION = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*([smhd]?)\s*$") _FACTORS = {"": 1, "s": 1, "m": 60, "h": 3600, "d": 86400} _PLACEHOLDER = re.compile(r"\{([a-zA-Z0-9_]+)\}") def now() -> float: return time.time() def load_yaml(path: str | Path) -> Any: with open(path, encoding="utf-8") as fh: return yaml.safe_load(fh) or {} def parse_duration(value: str | int | float | None) -> float: """'5m' -> 300.0; Zahlen gelten als Sekunden.""" if value is None: return 0.0 if isinstance(value, int | float): return float(value) match = _DURATION.match(value) if not match: raise ValueError(f"Ungültige Dauer: {value!r}") return float(match.group(1)) * _FACTORS[match.group(2)] class MissingLabel(KeyError): pass def fill_template(value: Any, labels: dict[str, str]) -> Any: """Ersetzt {label} rekursiv; fehlende Labels lösen MissingLabel aus.""" if isinstance(value, str): def repl(m: re.Match[str]) -> str: key = m.group(1) if key not in labels: raise MissingLabel(key) return str(labels[key]) return _PLACEHOLDER.sub(repl, value) if isinstance(value, dict): return {k: fill_template(v, labels) for k, v in value.items()} if isinstance(value, list): return [fill_template(v, labels) for v in value] return value