"""One document, written twice: Markdown and a self-contained HTML page. The report is built as a list of blocks (heading, paragraph, bullet list, table) and each writer turns the same list into its format, so the two can never say different things. """ import html CSS = """ :root { --bg:#fff; --fg:#1d1d1f; --mute:#6e6e73; --line:#d9d9de; --head:#f3f3f5; --pass:#1a7f37; --fail:#c62828; --warn:#9a6700; --passbg:#e8f5ec; --failbg:#fdecea; --series1:#2a78d6; --good:#0ca30c; --critical:#d03b3b; --warning:#fab219; --neutral:#898781; --grid:#e1e0d9; --axis:#c3c2b7; } @media (prefers-color-scheme: dark) { :root { --bg:#161618; --fg:#e8e8ea; --mute:#9a9aa0; --line:#3a3a3e; --head:#232326; --pass:#5cc97a; --fail:#ff7b72; --warn:#e3b341; --passbg:#16301f; --failbg:#3a1a1a; --series1:#3987e5; --grid:#2c2c2a; --axis:#383835; } } figure { margin:12px 0 20px; max-width:900px; } figcaption { color:var(--mute); font-size:13px; } svg.plot { width:100%; height:auto; display:block; } svg.plot text { fill:var(--mute); font:11px system-ui, sans-serif; } svg.plot .grid { stroke:var(--grid); stroke-width:1; } svg.plot .ref { stroke:var(--axis); stroke-width:2; } svg.plot circle:hover, svg.plot rect:hover { stroke:var(--fg); stroke-width:2; } body { background:var(--bg); color:var(--fg); margin:0 auto; max-width:1400px; padding:16px; font:14px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif; } h1 { font-size:22px; margin:8px 0 4px; } h2 { font-size:18px; margin:28px 0 8px; } h3 { font-size:15px; margin:20px 0 6px; } p, li { max-width:900px; } .mute { color:var(--mute); } .wrap { overflow-x:auto; margin:8px 0 16px; } table { border-collapse:collapse; font-variant-numeric:tabular-nums; } th, td { border-bottom:1px solid var(--line); padding:4px 8px; text-align:left; white-space:nowrap; } th { background:var(--head); position:sticky; top:0; } td.num, th.num { text-align:right; } td.pass { color:var(--pass); font-weight:600; } td.fail { color:var(--fail); font-weight:600; } td.warn { color:var(--warn); font-weight:600; } tr.fail td { background:var(--failbg); } code { font:12px ui-monospace, Menlo, monospace; } """ class Doc: def __init__(self, title): self.title = title self.blocks = [] def h(self, level, text): self.blocks.append(("h", level, text)) def p(self, text): self.blocks.append(("p", text)) def ul(self, items): self.blocks.append(("ul", list(items))) def svg(self, markup, caption): """An inline SVG plot (HTML only; the Markdown names it and points at the HTML).""" self.blocks.append(("svg", markup, caption)) def table(self, head, rows, num=(), row_class=None, cell_class=None): """head: column titles; rows: lists of str; num: indices of right-aligned columns; row_class(row) and cell_class(i, value) give optional CSS classes (HTML only).""" self.blocks.append(("table", head, rows, set(num), row_class, cell_class)) def markdown(self): out = [f"# {self.title}", ""] for b in self.blocks: if b[0] == "h": out += ["#" * b[1] + " " + b[2], ""] elif b[0] == "p": out += [b[1], ""] elif b[0] == "ul": out += ["- " + x for x in b[1]] + [""] elif b[0] == "svg": out += [f"_Plot in report.html: {b[2]}_", ""] else: _, head, rows, num, _, _ = b if not rows: out += ["(none)", ""] continue out.append("| " + " | ".join(head) + " |") out.append("|" + "|".join("--:" if i in num else ":--" for i in range(len(head))) + "|") for r in rows: out.append("| " + " | ".join(str(x).replace("|", "/") for x in r) + " |") out.append("") return "\n".join(out) def html(self): e = html.escape out = ["", '', '', f"{e(self.title)}", f"

{e(self.title)}

"] for b in self.blocks: if b[0] == "h": out.append(f"{e(b[2])}") elif b[0] == "p": out.append(f"

{e(b[1])}

") elif b[0] == "ul": out.append("") elif b[0] == "svg": out.append(f"
{b[1]}
{e(b[2])}
") else: _, head, rows, num, row_class, cell_class = b if not rows: out.append('

(none)

') continue t = ['
'] t += [f'' if i in num else f"" for i, h in enumerate(head)] t.append("") for r in rows: rc = row_class(r) if row_class else "" t.append(f'' if rc else "") for i, x in enumerate(r): cls = ["num"] if i in num else [] if cell_class and cell_class(i, x): cls.append(cell_class(i, x)) c = f' class="{" ".join(cls)}"' if cls else "" t.append(f"{e(str(x))}") t.append("") t.append("
{e(h)}{e(h)}
") out.append("".join(t)) out.append("") return "\n".join(out) # ------------------------------------------------------------------ plots (inline SVG, no libraries) STATUS = {"pass": "var(--good)", "fail": "var(--critical)", "unscored": "var(--neutral)", "not_run": "var(--warning)"} def verdict_bars(rows): """One horizontal bar per arm, split pass / fail / unscored / not run. rows: [(arm, {verdict: count})].""" e = html.escape w, left, bar, gap = 900, 90, 22, 14 h = 30 + len(rows) * (bar + gap) total = max(sum(c.values()) for _, c in rows) or 1 out = [f''] x = left for v, col in STATUS.items(): # legend, above the bars out.append(f'' f'{v.replace("_", " ")}') x += 90 for i, (arm, cnt) in enumerate(rows): y = 26 + i * (bar + gap) out.append(f'{e(arm)}') x = left for v, col in STATUS.items(): n = cnt.get(v, 0) if not n: continue bw = (w - left - 10) * n / total out.append(f'{e(arm)}: {n} {v.replace("_", " ")}') if bw > 28: out.append(f'{n}') x += bw out.append("") return "".join(out) def ratio_dots(points, what): """Sets sorted by a ratio (ours / reference), on a log axis clipped to 0.5..2, with the line at 1. points: [(set, ratio, verdict, tooltip)]. Failed sets are drawn in the fail colour.""" e = html.escape import math w, h, left, top, bottom = 900, 240, 44, 22, 18 lo, hi = math.log(0.5), math.log(2.0) y = lambda r: top + (hi - math.log(min(max(r, 0.5), 2.0))) / (hi - lo) * (h - top - bottom) pts = sorted(points, key=lambda p: p[1]) step = (w - left - 10) / max(len(pts), 1) out = [f''] for t in (0.5, 0.75, 1.0, 1.5, 2.0): out.append(f'{t:g}') out.append(f'' f'pass or unscored' f'' f'fail' f'(clipped to 0.5-2; hover a point for the set)') for i, (name, r, verdict, tip) in enumerate(pts): col = "var(--critical)" if verdict == "fail" else "var(--series1)" out.append(f'' f'{e(name)}: {r:.3f} - {e(tip)}') out.append("") return "".join(out)