Source code for goojprt.template

"""TOML template engine for the GoojPrt PT-210.

Lets callers define a print job as a list of items in a ``.toml`` file,
with dynamic variables substituted before printing. A job is printed
by calling :func:`print_template` — the function loads the file,
expands variables, connects over BLE and issues the matching SDK calls.

Supported item types (see :func:`print_template` for the full dispatch
table): ``text_image``, ``text``, ``pdf417``, ``qr``, ``line``, ``feed``,
``cut``, ``grid``, ``ekg``. Unknown types produce a warning on stdout
and are skipped.
"""

import datetime
import re
import secrets
import tomllib

from goojprt.enums import Align, TextSize
from goojprt.printer import GoojPrtPT210


[docs] def random_password(length: int) -> str: """Generate a human-readable random password. Uses :func:`secrets.choice` and excludes characters that can be confused visually (``0`` vs ``O``, ``1`` vs ``l`` vs ``I``). :param length: Desired password length in characters. """ alphabet = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789" return "".join(secrets.choice(alphabet) for _ in range(length))
[docs] def build_vars(extra: dict[str, str] | None = None) -> dict[str, str]: """Build the built-in variable table (date, time, expiries, passwords). Time-related variables (values reflect "now" at call time): * ``{{date}}`` — today's date (``02.04.2026``) * ``{{date_iso}}`` — ISO date (``2026-04-02``) * ``{{time}}`` — current time (``14:35``) * ``{{time_full}}`` — current time with seconds * ``{{datetime}}`` — date + time * ``{{weekday}}`` — Czech weekday name (``Středa``) * ``{{week}}`` — ISO week number * ``{{expire_1h}}`` — time + 1 hour * ``{{expire_2h}}`` — time + 2 hours * ``{{expire_4h}}`` — time + 4 hours * ``{{expire_24h}}`` — date + time in 24 hours Passwords (generated once, identical inside a single template run): * ``{{password_8}}`` — 8 characters * ``{{password_12}}`` — 12 characters * ``{{password_16}}`` — 16 characters :param extra: User-supplied variables (e.g. from the CLI ``--var`` flag). They override built-ins when names collide. """ now = datetime.datetime.now() fmt = "%H:%M" weekdays = ["Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"] built_in: dict[str, str] = { "date": now.strftime("%d.%m.%Y"), "date_iso": now.strftime("%Y-%m-%d"), "time": now.strftime(fmt), "time_full": now.strftime("%H:%M:%S"), "datetime": now.strftime(f"%d.%m.%Y {fmt}"), "weekday": weekdays[now.weekday()], "week": now.strftime("%V"), "expire_1h": (now + datetime.timedelta(hours=1)).strftime(fmt), "expire_2h": (now + datetime.timedelta(hours=2)).strftime(fmt), "expire_4h": (now + datetime.timedelta(hours=4)).strftime(fmt), "expire_24h": (now + datetime.timedelta(hours=24)).strftime(f"%d.%m.%Y {fmt}"), "password_8": random_password(8), "password_12": random_password(12), "password_16": random_password(16), } if extra: built_in.update(extra) return built_in
[docs] def substitute(text: str, variables: dict[str, str]) -> str: """Replace ``{{key}}`` placeholders with the matching value. Unknown keys are left verbatim. """ def replace(m: re.Match) -> str: """Look up a single variable; return the original placeholder on miss.""" key = m.group(1).strip() if key in variables: return variables[key] return m.group(0) return re.sub(r"\{\{(.+?)\}\}", replace, text)
[docs] def substitute_deep(obj, variables: dict[str, str]): """Recursively substitute ``{{variables}}`` inside any string within ``obj``.""" if isinstance(obj, str): return substitute(obj, variables) if isinstance(obj, dict): return {k: substitute_deep(v, variables) for k, v in obj.items()} if isinstance(obj, list): return [substitute_deep(v, variables) for v in obj] return obj