master-zapier-plan-draft/scripts/gen-module-pdfs.py
George Lambert 10c663cc0c Milestone 2: module/model docs (MD, Sphinx RST, PDF) and work log
74 source modules documented with extracted signatures, JSDoc params,
imports, and call graphs. 10 first-class models (PriceRule through
StatusResponse) have their own MD/RST/PDF. Sphinx HTML builds from
docs/sphinx. Per-module PDFs in docs/modules-pdf and docs/models-pdf.

Middleware gates 2–6 and 10 pass on MOCK_VERAE without NATS.

Learned: RST includes are relative to the RST file; keep one PDF per
module; Add Numbers remains the only push required tomorrow.
2026-09-09 02:44:51 -04:00

112 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Render each docs/modules/*.md file to a PDF via reportlab."""
from __future__ import annotations
from pathlib import Path
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import PageBreak, Paragraph, Preformatted, SimpleDocTemplate, Spacer
ROOT = Path(__file__).resolve().parents[1]
MD_ROOT = ROOT / "docs" / "modules"
PDF_ROOT = ROOT / "docs" / "modules-pdf"
def md_to_flowables(text: str, styles):
story = []
in_code = False
code_buf = []
for raw in text.splitlines():
line = raw.rstrip()
if line.startswith("```"):
if in_code:
story.append(Preformatted("\n".join(code_buf) or " ", styles["ModuleCode"]))
code_buf = []
in_code = False
else:
in_code = True
continue
if in_code:
code_buf.append(line)
continue
if line.startswith("# "):
story.append(Paragraph(esc(line[2:]), styles["Title"]))
elif line.startswith("## "):
story.append(Spacer(1, 8))
story.append(Paragraph(esc(line[3:]), styles["Heading2"]))
elif line.startswith("### "):
story.append(Paragraph(esc(line[4:]), styles["Heading3"]))
elif line.startswith("|"):
story.append(Paragraph(esc(line.replace("|", " · ")), styles["BodyText"]))
elif line.startswith("- "):
story.append(Paragraph("" + esc(line[2:]), styles["BodyText"]))
elif line.strip() == "":
story.append(Spacer(1, 6))
else:
story.append(Paragraph(esc(line), styles["BodyText"]))
return story
def esc(s: str) -> str:
return (
s.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("`", "")
)
def main() -> None:
PDF_ROOT.mkdir(parents=True, exist_ok=True)
styles = getSampleStyleSheet()
styles.add(
ParagraphStyle(
name="ModuleCode",
fontName="Courier",
fontSize=7,
leading=9,
)
)
mds = sorted(MD_ROOT.rglob("*.md"))
n = 0
combined = []
for md in mds:
rel = md.relative_to(MD_ROOT)
pdf_path = PDF_ROOT / rel.with_suffix(".pdf")
pdf_path.parent.mkdir(parents=True, exist_ok=True)
text = md.read_text(encoding="utf-8", errors="replace")
doc = SimpleDocTemplate(
str(pdf_path),
pagesize=letter,
leftMargin=0.7 * inch,
rightMargin=0.7 * inch,
topMargin=0.7 * inch,
bottomMargin=0.7 * inch,
)
flow = md_to_flowables(text, styles)
doc.build(flow)
combined.extend(flow)
combined.append(PageBreak())
n += 1
try:
from pypdf import PdfWriter
writer = PdfWriter()
for md in mds:
pdf_path = PDF_ROOT / md.relative_to(MD_ROOT).with_suffix(".pdf")
if pdf_path.exists():
writer.append(str(pdf_path))
book = PDF_ROOT / "ALL-MODULES.pdf"
with open(book, "wb") as fh:
writer.write(fh)
print(f"wrote {n} pdfs + {book.name}")
except Exception as exc:
print(f"wrote {n} pdfs (combined book skipped: {exc})")
if __name__ == "__main__":
main()