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.
112 lines
3.4 KiB
Python
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("<", "<")
|
|
.replace(">", ">")
|
|
.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()
|