Some checks are pending
offline / test (push) Waiting to run
Render published markdown with pandoc + WeasyPrint (indigo tables, dark code). Home page links to PDFs with a top-right switch to Markdown indexes. Serve CONSOLE.pdf from the operator console.
542 lines
22 KiB
Python
Executable file
542 lines
22 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""Build static documentation site into ./site for zapier.georgelambert.org.
|
||
|
||
Markdown is rendered to colored HTML *and* PDF. The home page (index.html)
|
||
defaults to PDF links; index-md.html is the Markdown/HTML index.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import subprocess
|
||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||
from datetime import datetime, timezone
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
SITE = ROOT / "site"
|
||
CSS = ROOT / "scripts" / "docs-print.css"
|
||
|
||
SECTIONS = [
|
||
(
|
||
"Start here",
|
||
[
|
||
("packages/overview/README.md", "System overview (plain language)"),
|
||
("packages/overview/INDEX.md", "Documentation index"),
|
||
("packages/verae-fleet/docs/CONSOLE.md", "Operator console (Fleet · Trace · Docs)"),
|
||
("README.md", "Workspace README"),
|
||
("docs/04-activate/SETUP-ZAPIER-DEVELOPER.pdf", "Zapier developer setup (PDF)"),
|
||
("docs/OPEN.md", "Still open"),
|
||
("TODO.md", "Implementation TODO / gates"),
|
||
],
|
||
),
|
||
(
|
||
"Architecture",
|
||
[
|
||
("docs/02-architecture/composition.md", "Zappier + middleware composition"),
|
||
("docs/02-architecture/overview.md", "Middleware overview"),
|
||
("docs/02-architecture/nats-gateway.md", "NATS on NS1"),
|
||
("docs/02-architecture/nats-subjects.md", "NATS subjects"),
|
||
("docs/02-architecture/archive-nats.md", "Archive NATS, bloom, multi-receipt"),
|
||
("docs/02-architecture/modules-and-nats.md", "Module catalog and NATS addresses"),
|
||
("docs/02-architecture/tree-nodes.md", "Tree nodes and bulk Merkle summaries"),
|
||
("docs/02-architecture/fleet.md", "Fleet replica floors and monitor"),
|
||
("packages/overview/03-nats-cluster.md", "NATS.IO 3-server cluster"),
|
||
("packages/overview/04-uptime.md", "Uptime / replica floors"),
|
||
("packages/overview/05-network-failures.md", "Local network failures"),
|
||
("packages/overview/06-address-routing.md", "Address routing"),
|
||
("packages/overview/08-diagrams.md", "Architectural diagrams"),
|
||
],
|
||
),
|
||
(
|
||
"User guide (signup → lookup)",
|
||
[
|
||
("packages/zapier-user-docs/README.md", "User guide index"),
|
||
("packages/zapier-user-docs/02-signup-zappier-portal.md", "Sign up"),
|
||
("packages/zapier-user-docs/04-register-a-hash.md", "Register a SHA-256"),
|
||
("packages/zapier-user-docs/06-lookup-central-chain.md", "Central chain lookup"),
|
||
("packages/zapier-user-docs/09-lookup-tree-nodes.md", "Tree-node lookup"),
|
||
],
|
||
),
|
||
(
|
||
"Product",
|
||
[
|
||
("docs/01-product/features-a-m.md", "Features a–m"),
|
||
("docs/01-product/api-gap-analysis.md", "Verae OpenAPI gaps"),
|
||
("docs/01-product/billing-and-keys.md", "Billing and API keys"),
|
||
("docs/00-sources/workspace-brief.md", "Original brief"),
|
||
("docs/00-sources/provenance.md", "Provenance"),
|
||
],
|
||
),
|
||
(
|
||
"API specs",
|
||
[
|
||
("docs/api/middleware-openapi.yaml", "Middleware OpenAPI"),
|
||
("packages/zappier/openapi.yaml", "Zappier OpenAPI"),
|
||
("docs/00-sources/veraetime-openapi.yaml", "Verae Timestamping OpenAPI snapshot"),
|
||
],
|
||
),
|
||
]
|
||
|
||
|
||
def rel(p: Path) -> str:
|
||
return p.relative_to(ROOT).as_posix()
|
||
|
||
|
||
def copy_tree(src: Path, dest: Path, ignore=None) -> None:
|
||
if not src.exists():
|
||
return
|
||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||
if src.is_file():
|
||
shutil.copy2(src, dest)
|
||
return
|
||
shutil.copytree(src, dest, dirs_exist_ok=True, ignore=ignore)
|
||
|
||
|
||
def site_href_for(path: str, src: Path) -> tuple[str, str]:
|
||
"""Return (pdf_or_native_href, md_or_html_href) relative to SITE."""
|
||
suffix = src.suffix.lower()
|
||
if suffix == ".md":
|
||
pdf = Path(path).with_suffix(".pdf").as_posix()
|
||
html = Path(path).with_suffix(".html").as_posix()
|
||
return pdf, html
|
||
return path, path
|
||
|
||
|
||
def render_md(src: Path, html_dest: Path, pdf_dest: Path, title: str) -> str:
|
||
"""Pandoc markdown → colored HTML + WeasyPrint PDF. Returns '' on success."""
|
||
html_dest.parent.mkdir(parents=True, exist_ok=True)
|
||
pdf_dest.parent.mkdir(parents=True, exist_ok=True)
|
||
header = html_dest.with_suffix(".hdr.html")
|
||
banner = html_dest.with_suffix(".ban.html")
|
||
rel_src = src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else src.name
|
||
css_text = CSS.read_text(encoding="utf-8")
|
||
header.write_text(f"<style>{css_text}</style>\n", encoding="utf-8")
|
||
banner.write_text(
|
||
f'<div class="doc-banner">'
|
||
f'<nav class="site"><a href="/">zapier.georgelambert.org</a>'
|
||
f' · <a href="/index-md.html">Markdown indexes</a></nav>'
|
||
f'<div class="kicker">Verae Time × Zapier</div>'
|
||
f"<h1>{title}</h1>"
|
||
f'<div class="source-path">{rel_src}</div>'
|
||
f"</div>\n",
|
||
encoding="utf-8",
|
||
)
|
||
resource = str(src.parent)
|
||
r = subprocess.run(
|
||
[
|
||
"pandoc",
|
||
str(src),
|
||
"-o",
|
||
str(html_dest),
|
||
"--standalone",
|
||
f"--resource-path={resource}",
|
||
"--highlight-style=breezedark",
|
||
f"--metadata=title={title}",
|
||
f"--include-in-header={header}",
|
||
f"--include-before-body={banner}",
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
header.unlink(missing_ok=True)
|
||
banner.unlink(missing_ok=True)
|
||
if r.returncode != 0:
|
||
return f"pandoc {src}: {r.stderr[-400:]}"
|
||
w = subprocess.run(
|
||
["weasyprint", str(html_dest), str(pdf_dest)],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
if w.returncode != 0:
|
||
return f"weasyprint {src}: {w.stderr[-400:]}"
|
||
return ""
|
||
|
||
|
||
def convert_all_markdown(copied: list[tuple[Path, Path, str]]) -> list[str]:
|
||
errors = []
|
||
n = len(copied)
|
||
print(f"rendering {n} markdown files to HTML + PDF…")
|
||
with ThreadPoolExecutor(max_workers=6) as pool:
|
||
futs = {
|
||
pool.submit(render_md, src, html, pdf, title): src
|
||
for src, html, pdf, title in (
|
||
(
|
||
src,
|
||
dest.with_suffix(".html"),
|
||
dest.with_suffix(".pdf"),
|
||
title,
|
||
)
|
||
for src, dest, title in copied
|
||
)
|
||
}
|
||
done = 0
|
||
for fut in as_completed(futs):
|
||
err = fut.result()
|
||
done += 1
|
||
if err:
|
||
errors.append(err)
|
||
print(f" [{done}/{n}] FAIL {futs[fut].name}")
|
||
elif done % 25 == 0 or done == n:
|
||
print(f" [{done}/{n}]")
|
||
return errors
|
||
|
||
|
||
def format_switch(to_md: bool) -> str:
|
||
if to_md:
|
||
href, label = "/index-md.html", "Markdown indexes"
|
||
else:
|
||
href, label = "/", "PDF catalog"
|
||
return (
|
||
f'<a class="format-switch" href="{href}">{label}</a>'
|
||
)
|
||
|
||
|
||
def page_shell(now: str, body: str, switch_to_md: bool, title: str) -> str:
|
||
switch = format_switch(switch_to_md)
|
||
return f"""<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||
<title>{title}</title>
|
||
<style>
|
||
:root {{ --ink:#171a26; --muted:#5b6178; --line:#d9dce8; --bg:#f4f5fb; --accent:#4f46e5; --accent-deep:#312e81; --soft:#eef0fe; }}
|
||
body {{ margin:0; font:16px/1.5 Georgia, serif; color:var(--ink); background:var(--bg); }}
|
||
header {{ background:linear-gradient(160deg,#312e81 0%,#4f46e5 60%,#7c74f0 100%); color:#eef0fe; padding:2rem 1.5rem 1.4rem; position:relative; }}
|
||
header p {{ max-width:44rem; color:#e4e7ff; }}
|
||
.kicker {{ letter-spacing:.12em; text-transform:uppercase; font:700 11px system-ui; opacity:.75; }}
|
||
.format-switch {{
|
||
position:absolute; top:1.25rem; right:1.5rem;
|
||
font:650 13px system-ui, sans-serif; color:#fff; text-decoration:none;
|
||
background:rgba(255,255,255,.16); border:1px solid rgba(255,255,255,.35);
|
||
padding:.4rem .75rem; border-radius:999px;
|
||
}}
|
||
.format-switch:hover {{ background:rgba(255,255,255,.28); }}
|
||
main {{ max-width:52rem; margin:0 auto; padding:1.5rem 1.25rem 3rem; }}
|
||
h1 {{ margin:.4rem 0 .6rem; font-size:1.85rem; }}
|
||
h2 {{ border-top:1px solid var(--line); padding-top:.8rem; margin-top:1.8rem; color:var(--accent-deep); }}
|
||
ul {{ padding-left:1.2rem; }}
|
||
li {{ margin:.35rem 0; }}
|
||
a {{ color:var(--accent); }}
|
||
.path {{ color:var(--muted); font:12px ui-monospace, Menlo, monospace; margin-left:.4rem; }}
|
||
.badge {{ font:700 10px system-ui; letter-spacing:.04em; text-transform:uppercase;
|
||
background:var(--soft); color:var(--accent); padding:.1rem .35rem; border-radius:4px; margin-left:.25rem; }}
|
||
input {{ width:100%; padding:.55rem .7rem; font:16px system-ui; border:1px solid var(--line); border-radius:6px; }}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
{switch}
|
||
<div class="kicker">zapier.georgelambert.org</div>
|
||
<h1>Verae Time × Zapier documentation</h1>
|
||
<p>Project reference: setup, architecture, NATS, module APIs, user guide, and operator console.
|
||
Catalog defaults to <strong>colored PDFs</strong>. Generated {now}.</p>
|
||
</header>
|
||
<main>
|
||
<p><input id="q" type="search" placeholder="Filter links…" /></p>
|
||
{body}
|
||
</main>
|
||
<script>
|
||
const q = document.getElementById('q');
|
||
q.addEventListener('input', () => {{
|
||
const v = q.value.toLowerCase();
|
||
document.querySelectorAll('li').forEach(li => {{
|
||
li.style.display = li.textContent.toLowerCase().includes(v) ? '' : 'none';
|
||
}});
|
||
}});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
|
||
def main() -> None:
|
||
if SITE.exists():
|
||
shutil.rmtree(SITE)
|
||
SITE.mkdir()
|
||
|
||
copy_tree(ROOT / "docs", SITE / "docs", ignore=shutil.ignore_patterns("_build", ".DS_Store"))
|
||
sphinx = ROOT / "docs" / "sphinx" / "_build" / "html"
|
||
if sphinx.exists():
|
||
copy_tree(sphinx, SITE / "sphinx")
|
||
for pkg in (
|
||
"verae-activate",
|
||
"verae-zapier",
|
||
"verae-zapier-middleware",
|
||
"zappier",
|
||
"verae-request-splitter",
|
||
"verae-archive-worm",
|
||
"verae-archive-aggregator",
|
||
"verae-tree-node",
|
||
"verae-zapier-simulator",
|
||
"zapier-user-docs",
|
||
"verae-fleet",
|
||
"overview",
|
||
"verae-nats-process",
|
||
"docs-master",
|
||
):
|
||
pkg_root = ROOT / "packages" / pkg
|
||
if pkg in {"zapier-user-docs", "overview", "docs-master"}:
|
||
copy_tree(
|
||
pkg_root,
|
||
SITE / "packages" / pkg,
|
||
ignore=shutil.ignore_patterns("node_modules", ".git", "src", "test", "data"),
|
||
)
|
||
elif pkg == "verae-fleet":
|
||
for name in ("README.md", "SUMMARY.md", "NATS.md", "SERVICES.md"):
|
||
p = pkg_root / name
|
||
if p.exists():
|
||
copy_tree(p, SITE / "packages" / pkg / name)
|
||
copy_tree(
|
||
pkg_root / "docs",
|
||
SITE / "packages" / pkg / "docs",
|
||
ignore=shutil.ignore_patterns("node_modules"),
|
||
)
|
||
elif pkg == "verae-nats-process":
|
||
for p in pkg_root.glob("*.md"):
|
||
copy_tree(p, SITE / "packages" / pkg / p.name)
|
||
else:
|
||
readme = pkg_root / "README.md"
|
||
if readme.exists():
|
||
copy_tree(readme, SITE / "packages" / pkg / "README.md")
|
||
zdocs = ROOT / "packages" / "zappier" / "docs"
|
||
if zdocs.exists():
|
||
copy_tree(
|
||
zdocs,
|
||
SITE / "packages" / "zappier" / "docs",
|
||
ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers"),
|
||
)
|
||
for name in ("README.md", "TODO.md", "OPEN.md"):
|
||
src = ROOT / name
|
||
if not src.exists() and name == "OPEN.md":
|
||
src = ROOT / "docs" / "OPEN.md"
|
||
if src.exists():
|
||
copy_tree(src, SITE / src.name if src.parent == ROOT else SITE / "docs" / "OPEN.md")
|
||
copy_tree(ROOT / "docs" / "WORK-LOG.md", SITE / "docs" / "WORK-LOG.md")
|
||
copy_tree(ROOT / "packages" / "docs-master", SITE / "docs-master")
|
||
copy_tree(ROOT / "packages" / "overview", SITE / "overview")
|
||
copy_tree(ROOT / "packages" / "zapier-user-docs", SITE / "user-docs", ignore=shutil.ignore_patterns("node_modules"))
|
||
copy_tree(
|
||
ROOT / "packages" / "verae-fleet" / "docs",
|
||
SITE / "packages" / "verae-fleet" / "docs",
|
||
ignore=shutil.ignore_patterns("node_modules"),
|
||
)
|
||
sim_pub = ROOT / "packages" / "verae-zapier-simulator" / "public" / "index.html"
|
||
if sim_pub.exists():
|
||
copy_tree(sim_pub, SITE / "simulator" / "index.html")
|
||
|
||
r = ROOT / "research" / "zapier"
|
||
if r.exists():
|
||
for fname in (
|
||
"README.md",
|
||
"getting-started.md",
|
||
"PLATFORM-REFERENCE.md",
|
||
"FUNCTIONS-REFERENCE.md",
|
||
"MCP-REFERENCE.md",
|
||
"LOGIN.md",
|
||
"docs/zapier-billing.md",
|
||
):
|
||
p = r / fname
|
||
if p.exists():
|
||
copy_tree(p, SITE / "research" / fname)
|
||
diagrams = r / "docs" / "diagrams"
|
||
if diagrams.exists():
|
||
copy_tree(diagrams, SITE / "research" / "docs" / "diagrams")
|
||
|
||
# Render every published .md from the site copy so relative images resolve.
|
||
to_render: list[tuple[Path, Path, str]] = []
|
||
seen: set[Path] = set()
|
||
for md in SITE.rglob("*.md"):
|
||
if not md.is_file():
|
||
continue
|
||
key = md.resolve()
|
||
if key in seen:
|
||
continue
|
||
seen.add(key)
|
||
to_render.append((md, md, md.stem.replace("-", " ")))
|
||
|
||
# Prefer curated titles for catalog entries
|
||
title_map = {}
|
||
for _sec, links in SECTIONS:
|
||
for path, label in links:
|
||
title_map[path] = label
|
||
labeled = []
|
||
for src, dest, title in to_render:
|
||
try:
|
||
key = dest.relative_to(SITE).as_posix()
|
||
except ValueError:
|
||
key = dest.name
|
||
# packages/overview/... also listed as packages/overview
|
||
labeled.append((src, dest, title_map.get(key, title_map.get(src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else key, title))))
|
||
|
||
errors = convert_all_markdown(labeled)
|
||
|
||
# Also write CONSOLE.pdf next to the markdown in the fleet package (local console)
|
||
console_md = ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.md"
|
||
if console_md.exists():
|
||
err = render_md(
|
||
console_md,
|
||
ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.html",
|
||
ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.pdf",
|
||
"Operator console",
|
||
)
|
||
if err:
|
||
errors.append(err)
|
||
|
||
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
||
|
||
def cards(mode: str) -> str:
|
||
out = []
|
||
for title, links in SECTIONS:
|
||
lis = []
|
||
for path, label in links:
|
||
src = ROOT / path
|
||
if not src.exists() and path == "OPEN.md":
|
||
src = ROOT / "docs" / "OPEN.md"
|
||
path = "docs/OPEN.md"
|
||
if not src.exists():
|
||
continue
|
||
pdf_href, html_href = site_href_for(path, src)
|
||
if mode == "pdf":
|
||
href = pdf_href if (SITE / pdf_href).exists() or src.suffix.lower() in {".pdf", ".yaml", ".yml"} else html_href
|
||
badge = "PDF" if href.endswith(".pdf") else src.suffix.lstrip(".").upper() or "FILE"
|
||
else:
|
||
href = html_href if (SITE / html_href).exists() else path
|
||
badge = "MD" if src.suffix.lower() == ".md" else src.suffix.lstrip(".").upper() or "FILE"
|
||
lis.append(
|
||
f'<li><a href="{href}">{label}</a>'
|
||
f'<span class="badge">{badge}</span>'
|
||
f'<span class="path">{path}</span></li>'
|
||
)
|
||
out.append(f"<section><h2>{title}</h2><ul>{''.join(lis)}</ul></section>")
|
||
return "".join(out)
|
||
|
||
git_repos = [
|
||
("master-zapier-plan-draft", "Monorepo (this workspace)"),
|
||
("zappier-edge", "Metered HTTPS edge"),
|
||
("verae-middleware", "Zapier-facing HTTP + NATS workers"),
|
||
("verae-zapier-app", "Full Zapier Platform app"),
|
||
("verae-activate", "Activate-now Zapier app"),
|
||
("verae-request-splitter", "Hash vs attachment splitter"),
|
||
("verae-archive-worm", "Bloom-filtered WORM archive node"),
|
||
("verae-archive-aggregator", "Archive reply aggregator"),
|
||
("zapier-docs-master", "Master summaries and NATS contracts"),
|
||
("verae-tree-node", "Merkle leaf proofs (bulk summaries)"),
|
||
("verae-zapier-simulator", "Zapier interface + trace console"),
|
||
("zapier-user-docs", "Signup-to-usage user guide"),
|
||
("verae-fleet", "Service catalog, replica floors, monitor"),
|
||
("overview", "High-level system overview"),
|
||
("verae-nats-process", "Template for a new NATS address"),
|
||
]
|
||
git_lis = "".join(
|
||
f'<li><a href="https://git.georgelambert.org/marchon/{name}">{name}</a> '
|
||
f'<span class="path">{desc}</span></li>'
|
||
for name, desc in git_repos
|
||
)
|
||
|
||
extra_pdf = [
|
||
("docs-master/README.pdf", "Master summaries (docs-master)"),
|
||
("docs-master/MESSAGE-FLOWS.pdf", "Numbered message flows"),
|
||
("docs-master/modules-and-nats.pdf", "NATS address table"),
|
||
("user-docs/README.pdf", "User guide index"),
|
||
("packages/verae-zapier-simulator/README.pdf", "Simulator README"),
|
||
("packages/verae-fleet/docs/CONSOLE.pdf", "Operator console"),
|
||
("overview/README.pdf", "System overview"),
|
||
("overview/INDEX.pdf", "Documentation index"),
|
||
("docs/modules-pdf/", "Module PDFs (book)"),
|
||
("docs/models-pdf/", "Model PDFs (book)"),
|
||
("sphinx/index.html", "Sphinx HTML"),
|
||
]
|
||
extra_md = [
|
||
("docs-master/README.html", "Master summaries (docs-master)"),
|
||
("docs-master/MESSAGE-FLOWS.html", "Numbered message flows"),
|
||
("docs-master/modules-and-nats.html", "NATS address table"),
|
||
("user-docs/README.html", "User guide index"),
|
||
("packages/verae-zapier-simulator/README.html", "Simulator README"),
|
||
("packages/verae-fleet/docs/CONSOLE.html", "Operator console"),
|
||
("overview/README.html", "System overview"),
|
||
("overview/INDEX.html", "Documentation index"),
|
||
("docs/modules/", "All module markdown"),
|
||
("docs/models/", "All model markdown"),
|
||
("sphinx/index.html", "Sphinx HTML"),
|
||
]
|
||
|
||
def extra_section(items: list[tuple[str, str]]) -> str:
|
||
lis = "".join(
|
||
f'<li><a href="{h}">{lab}</a></li>'
|
||
for h, lab in items
|
||
if (SITE / h).exists() or h.endswith("/") or h.startswith("sphinx")
|
||
)
|
||
return f"<section><h2>Catalogs</h2><ul>{lis}</ul></section>"
|
||
|
||
rest_pdf = f"""
|
||
{cards("pdf")}
|
||
<section>
|
||
<h2>Git repositories (Forgejo)</h2>
|
||
<p>Clone: <code>ssh://git@git.georgelambert.org:2223/marchon/<name>.git</code> (SSH port 2223). Branches <code>main</code> and <code>master</code>.</p>
|
||
<ul>{git_lis}</ul>
|
||
</section>
|
||
<section>
|
||
<h2>Master module docs</h2>
|
||
<ul>
|
||
<li><a href="docs-master/README.pdf">docs-master README</a><span class="badge">PDF</span></li>
|
||
<li><a href="docs-master/MESSAGE-FLOWS.pdf">MESSAGE-FLOWS</a><span class="badge">PDF</span></li>
|
||
<li><a href="docs-master/modules-and-nats.pdf">modules-and-nats</a><span class="badge">PDF</span></li>
|
||
<li><a href="docs-master/archive-nats.pdf">archive-nats</a><span class="badge">PDF</span></li>
|
||
<li><a href="docs-master/composition.pdf">composition</a><span class="badge">PDF</span></li>
|
||
<li><a href="user-docs/README.pdf">User guide (signup → tree-node lookup)</a><span class="badge">PDF</span></li>
|
||
<li><a href="packages/verae-zapier-simulator/README.pdf">Simulator README</a><span class="badge">PDF</span></li>
|
||
<li><a href="overview/README.pdf">System overview</a><span class="badge">PDF</span></li>
|
||
<li><a href="overview/INDEX.pdf">Documentation index</a><span class="badge">PDF</span></li>
|
||
<li><a href="packages/verae-fleet/docs/CONSOLE.pdf">Operator console</a><span class="badge">PDF</span></li>
|
||
<li><a href="overview/diagrams/system.svg">System diagram (SVG)</a></li>
|
||
</ul>
|
||
</section>
|
||
{extra_section(extra_pdf)}
|
||
"""
|
||
rest_md = f"""
|
||
{cards("md")}
|
||
<section>
|
||
<h2>Git repositories (Forgejo)</h2>
|
||
<p>Clone: <code>ssh://git@git.georgelambert.org:2223/marchon/<name>.git</code> (SSH port 2223).</p>
|
||
<ul>{git_lis}</ul>
|
||
</section>
|
||
<section>
|
||
<h2>Master module docs (HTML / Markdown)</h2>
|
||
<ul>
|
||
<li><a href="docs-master/README.html">docs-master README</a><span class="badge">MD</span></li>
|
||
<li><a href="docs-master/MESSAGE-FLOWS.html">MESSAGE-FLOWS</a><span class="badge">MD</span></li>
|
||
<li><a href="docs-master/modules-and-nats.html">modules-and-nats</a><span class="badge">MD</span></li>
|
||
<li><a href="docs-master/archive-nats.md">archive-nats.md</a><span class="badge">MD</span></li>
|
||
<li><a href="docs-master/composition.md">composition.md</a><span class="badge">MD</span></li>
|
||
<li><a href="user-docs/README.html">User guide</a><span class="badge">MD</span></li>
|
||
<li><a href="packages/verae-zapier-simulator/README.html">Simulator README</a><span class="badge">MD</span></li>
|
||
<li><a href="overview/README.html">System overview</a><span class="badge">MD</span></li>
|
||
<li><a href="overview/INDEX.html">Documentation index</a><span class="badge">MD</span></li>
|
||
<li><a href="packages/verae-fleet/docs/CONSOLE.html">Operator console</a><span class="badge">MD</span></li>
|
||
<li><a href="overview/diagrams/system.svg">System diagram (SVG)</a></li>
|
||
</ul>
|
||
</section>
|
||
{extra_section(extra_md)}
|
||
"""
|
||
|
||
(SITE / "index.html").write_text(
|
||
page_shell(now, rest_pdf, switch_to_md=True, title="Verae Time × Zapier documentation"),
|
||
encoding="utf-8",
|
||
)
|
||
(SITE / "index-md.html").write_text(
|
||
page_shell(now, rest_md, switch_to_md=False, title="Verae Time × Zapier — Markdown indexes"),
|
||
encoding="utf-8",
|
||
)
|
||
nfiles = sum(1 for _ in SITE.rglob("*") if _.is_file())
|
||
npdf = sum(1 for _ in SITE.rglob("*.pdf") if _.is_file())
|
||
print(f"site built at {SITE} ({nfiles} files, {npdf} PDFs)")
|
||
if errors:
|
||
print(f"{len(errors)} render errors:")
|
||
for e in errors[:20]:
|
||
print(" ", e[:300])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|