from collections.abc import Callable from dataclasses import dataclass, field from itertools import chain from typing import ClassVar, Self, final, override def format_src(src: str) -> str: return f"${{_{src.upper()}}}" @final @dataclass(frozen=True, slots=True) class ReplaceUnique: src: str dest: str # | Callable[[], str] def __call__(self, string: str) -> str: return string.replace(self.src, self.dest) @classmethod def auto_format(cls, src: str, dest: str): return cls(format_src(src), dest) # @override # def __str__(self) -> str: # if not self.dest: # return "" # if isinstance(self.dest, str): # return self.dest # return self.dest() @final @dataclass(frozen=True, slots=True) class ReplaceDynamic: val: str fmt: str = field(init=False) def __post_init__(self): setter = super(ReplaceDynamic, self).__setattr__ setter("fmt", format_src(self.val)) def __call__(self, string: str) -> str: return string.replace(self.fmt, self.val) # def __str__(self) -> str: # return self.val if isinstance(self.val, str) else self.val.fmt def build_placeholder(self, *args: "ReplaceDynamic"): data = ((rep.val.upper(), rep.fmt) for rep in chain((self,), args)) src: tuple[str, ...] dest: tuple[str, ...] src, dest = zip(*data) return ReplaceUnique("_".join(src), "_".join(dest)) @dataclass(frozen=True, slots=True) class ReplaceStatic: src: ClassVar[ReplaceDynamic] dest: None | str | Callable[[], str] def __call__(self, string: str) -> str: return string.replace(self.src.fmt, str(self)) @override def __str__(self) -> str: if not self.dest: return "" if isinstance(self.dest, str): return self.dest return self.dest() @classmethod def two_stage(cls, dest: str) -> tuple[Self, ReplaceDynamic]: dest_var = ReplaceDynamic(dest) return cls(dest_var.fmt), dest_var