#!/usr/bin/env python3 """Render the data-driven sections of landing/index.html. The fork feature list and the client comparison table used to be edited by hand in two places per feature (matrix #11). They now live in landing/data/: features.json - the "our fork adds" groups, one short line per feature comparison.json - the client comparison table + its "reviewed" date and the stylesheet lives in landing/style.css. It is inlined into index.html rather than linked, because the LXC 139 deploy copies index.html only. Run `python3 landing/build.py` after editing either file and commit the regenerated index.html (LXC 139 serves the files as-is; there is no build step there). `--check` exits 1 if index.html is out of date, for CI. Only the text between `` and `` markers is rewritten; everything else in index.html stays hand-edited. """ import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent INDEX = ROOT / "index.html" MARK = {"yes": "✓", "no": "✗", "part": "~"} def render_features(groups): out = ['
'] for g in groups: out.append('
') out.append(f'

{g["title"]}

') out.append(" ") out.append("
") out.append("
") return "\n".join(out) def render_cell(cell, ours): cls = ' class="ours"' if ours else "" if "mark" in cell: inner = f'{MARK[cell["mark"]]}' else: inner = cell["text"] if cell.get("note"): inner += f'{cell["note"]}' return f"{inner}" def render_comparison(data): clients = data["clients"] cols = len(clients) + 1 out = ["", " ", " ", " "] for i, c in enumerate(clients): cls = ' class="ours"' if i == 0 else "" sub = f'{c["sub"]}' if c["sub"] else "" out.append(f' {c["name"]}{sub}') out += [" ", " ", " "] for sec in data["sections"]: out.append(f' ') for row in sec["rows"]: if len(row["cells"]) != len(clients): sys.exit(f'comparison.json: "{row["feature"]}" has {len(row["cells"])} cells, expected {len(clients)}') out.append(" ") out.append(f' ') out.extend(f" {render_cell(c, i == 0)}" for i, c in enumerate(row["cells"])) out.append(" ") out += [" ", "
{sec["title"]}
{row["feature"]}
"] return "\n".join(out) def replace_block(html, name, content): pattern = re.compile( rf"(?P[ \t]*).*?", re.S ) m = pattern.search(html) if not m: sys.exit(f"index.html: missing / markers") indent = m.group("indent") body = "\n".join(indent + line if line else line for line in content.split("\n")) block = f"{indent}\n{body}\n{indent}" return html[: m.start()] + block + html[m.end() :] def main(): features = json.loads((ROOT / "data" / "features.json").read_text(encoding="utf-8")) comparison = json.loads((ROOT / "data" / "comparison.json").read_text(encoding="utf-8")) css = (ROOT / "style.css").read_text(encoding="utf-8").rstrip("\n") html = INDEX.read_text(encoding="utf-8") styles = "" new = replace_block(html, "styles", styles) new = replace_block(new, "features", render_features(features)) new = replace_block(new, "comparison", render_comparison(comparison)) new = replace_block( new, "reviewed", f'Client comparison last reviewed ' ) if "--check" in sys.argv: if new != html: sys.exit("landing/index.html is out of date: run python3 landing/build.py") print("landing/index.html is up to date") return INDEX.write_text(new, encoding="utf-8") print("wrote", INDEX) if __name__ == "__main__": main()