Buckets:

Pranav2748's picture
download
raw
11.4 kB
"""Render results/report/REPORT.md (a small Markdown dialect) to results/report/decision_model_report.pdf.
Dialect: '# ' title (first line), '## ' section, '### ' subsection, paragraphs, '- ' bullets, '1. ' numbered items,
pipe tables, '> ' callout lines, '![slug](caption)' figures from results/report_figs/<slug>.png, '---pagebreak---',
'**bold**', '*italic*', '`code`'. Run with the report venv (reportlab + matplotlib's DejaVu fonts).
"""
from __future__ import annotations
import glob
import os
import re
import sys
import matplotlib
from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import (Image, KeepTogether, ListFlowable, ListItem, PageBreak, Paragraph, Spacer, Table,
TableStyle)
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate
from reportlab.platypus.frames import Frame
from reportlab.platypus.tableofcontents import TableOfContents
ROOT = "/workspace/decision-reasoner"
SRC = sys.argv[1] if len(sys.argv) > 1 else f"{ROOT}/results/report/REPORT.md"
OUT = sys.argv[2] if len(sys.argv) > 2 else f"{ROOT}/results/report/decision_model_report.pdf"
FIGS = f"{ROOT}/results/report_figs"
TTF = os.path.join(os.path.dirname(matplotlib.__file__), "mpl-data", "fonts", "ttf")
for name, f in (("Sans", "DejaVuSans.ttf"), ("Sans-Bold", "DejaVuSans-Bold.ttf"), ("Sans-Italic", "DejaVuSans-Oblique.ttf"),
("Sans-BoldItalic", "DejaVuSans-BoldOblique.ttf"), ("Mono", "DejaVuSansMono.ttf")):
pdfmetrics.registerFont(TTFont(name, os.path.join(TTF, f)))
pdfmetrics.registerFontFamily("Sans", normal="Sans", bold="Sans-Bold", italic="Sans-Italic", boldItalic="Sans-BoldItalic")
INK, MUTED, RULE, ACCENT, SOFT = (colors.HexColor(c) for c in ("#111827", "#6b7280", "#e5e7eb", "#2563eb", "#eff6ff"))
BASE = ParagraphStyle("base", fontName="Sans", fontSize=9.4, leading=13.4, textColor=INK, alignment=TA_LEFT, spaceAfter=5)
S = {
"title": ParagraphStyle("title", parent=BASE, fontName="Sans-Bold", fontSize=22, leading=27, spaceAfter=8),
"subtitle": ParagraphStyle("subtitle", parent=BASE, fontSize=11.5, leading=16, textColor=MUTED, spaceAfter=14),
"h2": ParagraphStyle("h2", parent=BASE, fontName="Sans-Bold", fontSize=14.5, leading=19, spaceBefore=16, spaceAfter=6, keepWithNext=1),
"h3": ParagraphStyle("h3", parent=BASE, fontName="Sans-Bold", fontSize=11, leading=15, spaceBefore=9, spaceAfter=4, keepWithNext=1),
"body": BASE,
"bullet": ParagraphStyle("bullet", parent=BASE, spaceAfter=2.5),
"cell": ParagraphStyle("cell", parent=BASE, fontSize=8.1, leading=10.4, spaceAfter=0),
"cellh": ParagraphStyle("cellh", parent=BASE, fontName="Sans-Bold", fontSize=8.1, leading=10.4, spaceAfter=0),
"caption": ParagraphStyle("caption", parent=BASE, fontName="Sans-Italic", fontSize=8.2, leading=11, textColor=MUTED, spaceAfter=10),
"callout": ParagraphStyle("callout", parent=BASE, fontSize=9.6, leading=13.6, spaceAfter=0),
"tochead": ParagraphStyle("tochead", parent=BASE, fontName="Sans-Bold", fontSize=11, leading=15, spaceBefore=4, spaceAfter=6),
"toc1": ParagraphStyle("toc1", parent=BASE, fontSize=10, leading=15, leftIndent=0),
"toc2": ParagraphStyle("toc2", parent=BASE, fontSize=9, leading=12.5, leftIndent=14, textColor=MUTED),
}
def inline(t: str) -> str:
t = t.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
t = re.sub(r"`([^`]+)`", r'<font name="Mono" size="8.3">\1</font>', t)
t = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", t)
t = re.sub(r"(?<![\w*])\*(?!\s)(.+?)(?<!\s)\*(?![\w*])", r"<i>\1</i>", t)
t = re.sub(r"\[([^\]]+)\]\((https?://[^)]+)\)", r'<link href="\2" color="#2563eb">\1</link>', t)
return t
class Doc(BaseDocTemplate):
def __init__(self, path, title):
super().__init__(path, pagesize=A4, leftMargin=2 * cm, rightMargin=2 * cm, topMargin=2 * cm, bottomMargin=2 * cm,
title=title, author="Claude (Anthropic), for the decision-model project")
frame = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id="f")
self.addPageTemplates([PageTemplate(id="p", frames=[frame], onPage=self.decorate)])
self.short = title
def decorate(self, canv, doc):
if doc.page == 1:
return
canv.saveState()
canv.setFont("Sans", 7.5)
canv.setFillColor(MUTED)
canv.drawString(2 * cm, A4[1] - 1.3 * cm, self.short)
canv.drawRightString(A4[0] - 2 * cm, 1.2 * cm, str(doc.page))
canv.setStrokeColor(RULE)
canv.line(2 * cm, A4[1] - 1.45 * cm, A4[0] - 2 * cm, A4[1] - 1.45 * cm)
canv.restoreState()
def afterFlowable(self, f):
if isinstance(f, Paragraph) and f.style.name in ("h2", "h3"):
level = 0 if f.style.name == "h2" else 1
text = f.getPlainText()
key = f"h{id(f)}"
self.canv.bookmarkPage(key)
self.canv.addOutlineEntry(text, key, level=level, closed=level > 0)
if level == 0:
self.notify("TOCEntry", (level, text, self.page, key))
def table(rows: list[list[str]], width: float) -> Table:
ncol = len(rows[0])
data = [[Paragraph(inline(c), S["cellh"] if i == 0 else S["cell"]) for c in r] for i, r in enumerate(rows)]
# column widths: every column at least as wide as its longest word, the rest of the width shared by text length
plain = lambda x: re.sub(r"[*`]", "", x)
sw = lambda x, bold: pdfmetrics.stringWidth(x, "Sans-Bold" if bold else "Sans", 8.1) + 9
minw = [max(sw(w, i == 0) for i, r in enumerate(rows) for w in (plain(r[j]).split() or [""])) for j in range(ncol)]
pref = [max(sw(plain(r[j]), i == 0) for i, r in enumerate(rows)) for j in range(ncol)]
if sum(minw) >= width:
widths = [width * m / sum(minw) for m in minw]
else:
extra = [max(p - m, 0) for p, m in zip(pref, minw)]
room = width - sum(minw)
widths = [m + (room * e / sum(extra) if sum(extra) else room / ncol) for m, e in zip(minw, extra)]
widths = [min(w, p) for w, p in zip(widths, pref)]
if sum(widths) < width: # all fit on one line: spread the slack evenly
widths = [w + (width - sum(widths)) / ncol for w in widths]
t = Table(data, colWidths=widths, repeatRows=1, hAlign="LEFT")
t.setStyle(TableStyle([
("LINEBELOW", (0, 0), (-1, 0), 0.8, INK), ("LINEBELOW", (0, 1), (-1, -1), 0.3, RULE),
("VALIGN", (0, 0), (-1, -1), "TOP"), ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
("LEFTPADDING", (0, 0), (-1, -1), 4), ("RIGHTPADDING", (0, 0), (-1, -1), 4),
]))
return t
CAPTIONS = {}
for _f in glob.glob(f"{FIGS}/manifest_*.json"):
import json as _json
for _m in _json.load(open(_f)):
CAPTIONS[_m["slug"]] = _m["caption"]
def figure(slug: str, caption: str, width: float, n: int):
caption = caption or CAPTIONS.get(slug, "")
path = f"{FIGS}/{slug}.png"
if not os.path.exists(path):
return [Paragraph(f"[missing figure: {slug}]", S["caption"])]
from reportlab.lib.utils import ImageReader
iw, ih = ImageReader(path).getSize()
w = width
h = w * ih / iw
if h > 15 * cm: # keep tall figures on one page
h = 15 * cm
w = h * iw / ih
return [KeepTogether([Image(path, width=w, height=h), Spacer(1, 3),
Paragraph(f"<b>Figure {n}.</b> " + inline(caption), S["caption"])])]
def callout(lines: list[str], width: float) -> Table:
t = Table([[Paragraph("<br/>".join(inline(x) for x in lines), S["callout"])]], colWidths=[width])
t.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), SOFT), ("LINEBEFORE", (0, 0), (0, -1), 2.5, ACCENT),
("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9),
("TOPPADDING", (0, 0), (-1, -1), 7), ("BOTTOMPADDING", (0, 0), (-1, -1), 7)]))
return t
def build() -> None:
lines = open(SRC, encoding="utf-8").read().splitlines()
title = lines[0].lstrip("# ").strip()
doc = Doc(OUT, title)
W = doc.width
story, para, i, nfig = [], [], 1, 0
def flush():
nonlocal para
if para:
story.append(Paragraph(inline(" ".join(para)), S["body"]))
para = []
story.append(Spacer(1, 3.2 * cm))
story.append(Paragraph(inline(title), S["title"]))
while i < len(lines):
ln = lines[i]
s = ln.strip()
if s.startswith("%subtitle "):
story.append(Paragraph(inline(s[10:]), S["subtitle"])); i += 1; continue
if s == "%toc":
flush()
toc = TableOfContents()
toc.levelStyles = [S["toc1"], S["toc2"]]
story += [Paragraph("Contents", S["tochead"]), toc]
i += 1; continue
if s == "---pagebreak---":
flush(); story.append(PageBreak()); i += 1; continue
if not s:
flush(); i += 1; continue
if s.startswith("## "):
flush(); story.append(Paragraph(inline(s[3:]), S["h2"])); i += 1; continue
if s.startswith("### "):
flush(); story.append(Paragraph(inline(s[4:]), S["h3"])); i += 1; continue
m = re.match(r"!\[([\w\-]+)\]\((.*)\)$", s)
if m:
flush(); nfig += 1; story += figure(m.group(1), m.group(2), W, nfig); i += 1; continue
if s.startswith(">"):
flush(); block = []
while i < len(lines) and lines[i].strip().startswith(">"):
block.append(lines[i].strip()[1:].strip()); i += 1
story += [callout(block, W), Spacer(1, 8)]; continue
if s.startswith("|"):
flush(); rows = []
while i < len(lines) and lines[i].strip().startswith("|"):
cells = [c.strip() for c in lines[i].strip().strip("|").split("|")]
if not all(re.fullmatch(r":?-{2,}:?", c) for c in cells):
rows.append(cells)
i += 1
story += [table(rows, W), Spacer(1, 8)]; continue
if re.match(r"(- |\d+\. )", s):
flush(); items, numbered = [], bool(re.match(r"\d+\. ", s))
while i < len(lines) and re.match(r"\s*(- |\d+\. )", lines[i]):
txt = re.sub(r"^\s*(- |\d+\. )", "", lines[i]); i += 1
while i < len(lines) and lines[i].startswith(" ") and not re.match(r"\s*(- |\d+\. )", lines[i]) and lines[i].strip():
txt += " " + lines[i].strip(); i += 1
items.append(ListItem(Paragraph(inline(txt), S["bullet"]), leftIndent=12))
story += [ListFlowable(items, bulletType="1" if numbered else "bullet", start="1" if numbered else None,
bulletFontName="Sans", bulletFontSize=8 if not numbered else 9, leftIndent=12,
bulletColor=MUTED if not numbered else INK), Spacer(1, 4)]
continue
para.append(s); i += 1
flush()
doc.multiBuild(story)
print(f"wrote {OUT} ({os.path.getsize(OUT) / 1e6:.2f} MB), {nfig} figures")
if __name__ == "__main__":
build()

Xet Storage Details

Size:
11.4 kB
·
Xet hash:
e96feb506a3c6fdc3429a5255bc1edfc21430b28e7694c459d5b00d4789844e3

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.