"""Renderer fuer templates/kimi-trend-report.html.
Fuellt das mzone-ui-1.1.0-Report-Template aus einem JSON-Kontext.
Naechstes Mal nur noch Kontext-JSON schreiben und:
python render_template.py templates/kimi-trend-report.html kontext.json out/index.html
Kontext-Schema (siehe templates/kimi-trend-report.context.example.json):
page_title, kicker, title, subtitle
meta: { "": "", ... }
stats: [ { "label": str, "value": str|int, "unit": str?, "variant": "ok|warn|danger|none"? } ] (genau 4)
sections: [ { "title": str, "cards": [ {
"label": str, "name": str, "url": str,
"pills": [ {"text": str, "variant": "accent|info|ok|warn|danger|none"} ],
"desc": str (HTML erlaubt), "note": str? } ] } ]
delta: { "lead": str (HTML), "text": str (HTML),
"rows": [ {"name": str, "url": str, "before": str, "after": str, "delta": str, "top": bool?} ] }
(optional; erzeugt Ueberschrift, Lead, Chart und Tabelle)
findings: [ {"id": "F1", "pill": str, "variant": "warn|ok|danger", "body": str (HTML)} ] (optional)
crossrefs: [ {"href": str, "text": str} ]
footer: str (HTML)
"""
import json, sys
from pathlib import Path
def pill(p):
v = p.get("variant", "none")
cls = "mz-pill" + ("" if v == "none" else f" mz-pill--{v}")
return f'{p["text"]}'
def stat_tile(s):
v = s.get("variant", "none")
cls = "mz-stat" + ("" if v == "none" else f" mz-stat--{v}")
unit = f'{s["unit"]}' if s.get("unit") else ""
return (f' {s["label"]}
'
f'
{s["value"]}{unit}
')
def card(c):
pills = "\n ".join(pill(p) for p in c.get("pills", []))
note = f'\n {c["note"]}
' if c.get("note") else ""
return f'''
{c["label"]}
{c["desc"]}
{note}
'''
def section(s):
cards = "\n\n".join(card(c) for c in s["cards"])
return f' {s["title"]}
\n\n{cards}'
def delta_row(r):
flat = r["delta"] in ("±0", "+0", "0")
if flat:
p = '±0'
else:
variant = "mz-pill mz-pill--ok" + (" mz-pill--solid" if r.get("top") else "")
p = f'{r["delta"]}'
return (f' | {r["name"]} | '
f'{r["before"]} | {r["after"]} | {p} |
')
def render(template_path, ctx_path, out_path):
ctx = json.loads(Path(ctx_path).read_text(encoding="utf-8"))
tpl = Path(template_path).read_text(encoding="utf-8")
meta_rows = "\n".join(
f' {k}{v}'
for k, v in ctx["meta"].items())
stat_tiles = "\n".join(stat_tile(s) for s in ctx["stats"])
sections = "\n\n".join(section(s) for s in ctx["sections"])
delta_block, chart_script = "", ""
if ctx.get("delta"):
d = ctx["delta"]
rows = "\n".join(delta_row(r) for r in d["rows"])
delta_block = f''' {d["title"]}
| Repo | {d.get("col_before", "vorher")} | {d.get("col_after", "nachher")} | {d.get("col_delta", "Δ")} |
{rows}
'''
labels = json.dumps(d["chart_labels"], ensure_ascii=False)
data = json.dumps(d["chart_data"])
chart_script = f''' applyMzoneChartTheme(Chart);
new Chart(document.getElementById('delta-chart'), {{
type: 'bar',
data: {{ labels: {labels}, datasets: [{{ label: '{d.get("chart_label", "Δ")}', data: {data}, ...mzPalette.series(0) }}] }},
options: mzChartOptions()
}});
magnify('#delta-chart-wrap', {{ zoom: 2.2, radius: 90, wheelZoom: true, label: true }});'''
findings_block = ""
if ctx.get("findings"):
items = "\n".join(
f'''
{f["id"]}{f["pill"]}
{f["body"]}
''' for f in ctx["findings"])
findings_block = f''' {ctx.get("findings_title", "Aussortiert")}
{items}
'''
crossrefs = "\n".join(
f' {c["text"]}' for c in ctx.get("crossrefs", []))
html = (tpl
.replace("{{PAGE_TITLE}}", ctx["page_title"])
.replace("{{KICKER}}", ctx["kicker"])
.replace("{{TITLE}}", ctx["title"])
.replace("{{SUBTITLE}}", ctx["subtitle"])
.replace("{{META_ROWS}}", meta_rows)
.replace("{{STAT_TILES}}", stat_tiles)
.replace("{{SECTIONS}}", sections)
.replace("{{DELTA_BLOCK}}", delta_block)
.replace("{{FINDINGS_BLOCK}}", findings_block)
.replace("{{CROSSREFS}}", crossrefs)
.replace("{{FOOTER}}", ctx["footer"])
.replace("{{CHART_SCRIPT}}", chart_script))
out = Path(out_path)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(html, encoding="utf-8")
print(f"OK {out} ({len(html)} bytes)")
if __name__ == "__main__":
render(sys.argv[1], sys.argv[2], sys.argv[3])