"""Template variable engine. Supported variables (case-sensitive, %Delimited%): Recipient: %FirstName% %LastName% %CompanyName% %CompanyPhone% %CustomerEmail% %Email% plus custom CSV columns (auto-detected) Generated: %RandomInvoiceNumber% %RandomTotal% %RandomShippingCharge% %RandomName% Configured: %InvoiceDate% %DueDate% %CompanyPhone% System: %UnsubscribeLink% Unknown variables produce validation warnings (surfaced before campaign start). """ import re import secrets import string from dataclasses import dataclass, field from datetime import date, datetime from typing import Optional VARIABLE_PATTERN = re.compile(r"%([A-Za-z0-9_]+)%") KNOWN_VARIABLES = { "FirstName", "LastName", "CompanyName", "CompanyPhone", "CustomerEmail", "Email", "RandomInvoiceNumber", "RandomTotal", "RandomShippingCharge", "RandomName", "InvoiceDate", "DueDate", "UnsubscribeLink", } @dataclass class GeneratedValues: invoice_number: str = "" amount: Optional[float] = None shipping: Optional[float] = None random_name: str = "" invoice_date: str = "" due_date: str = "" phone: str = "" @dataclass class SubstitutionResult: text: str unknown: list = field(default_factory=list) missing: list = field(default_factory=list) # known but no data available used: list = field(default_factory=list) class VariableEngine: def __init__(self, config: Optional[dict] = None, rng=None): self.config = config or {} self.rng = rng or secrets.SystemRandom() # ------------------------------------------------------------------ helpers def _cfg(self, key: str, default): return self.config.get(key, default) def generate_invoice_number(self, used: set = None) -> str: """Generate a unique invoice/reference number using configurable format tokens. Tokens: {prefix} {R#} uppercase {r#} lowercase {d#} digits {seq4} {date} {hash} Cryptographically strong randomness via SystemRandom. """ used = used if used is not None else set() fmt = self._cfg("invoice_format", "{prefix}{R4}-{R5}-{R6}-{seq4}") prefix = self._cfg("invoice_prefix", "INV-") for _ in range(50): val = self._render_format(fmt, prefix) if val not in used: return val raise ValueError("Could not generate unique invoice number after 50 attempts") def _render_format(self, fmt: str, prefix: str) -> str: from string import ascii_letters, digits upper = string.ascii_uppercase + string.digits lower = string.ascii_lowercase + string.digits digits_only = string.digits def rnd(chars, n): return "".join(self.rng.choice(chars) for _ in range(n)) def repl(match): token = match.group(1)