Skip to content

Commit c50ec8e

Browse files
committed
Add production quality checks
1 parent fe74961 commit c50ec8e

5 files changed

Lines changed: 306 additions & 34 deletions

File tree

.github/workflows/quality.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Quality
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
validate:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-python@v5
13+
with:
14+
python-version: "3.12"
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: "22"
18+
- name: Install Python quality dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
python -m pip install -r requirements.txt ruff pyyaml
22+
- name: Install diagram and type-check tools
23+
run: npm install -g @mermaid-js/mermaid-cli pyright
24+
- name: Run quality validation
25+
run: python scripts/validate_quality.py

QUALITY.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Quality Checks
2+
3+
Run the production-readiness checks from this repository root:
4+
5+
```bash
6+
python3 scripts/validate_quality.py
7+
```
8+
9+
The validator compiles Python files, runs Ruff, parses YAML/JSON/INI files, renders Mermaid sources when Mermaid tooling is available, runs generator `--help` smoke checks where generator scripts exist, runs catalog `--list` smoke checks where renderer catalog scripts exist, and runs Pyright when a `pyrightconfig.json` file is present.
10+
11+
GitHub Actions runs the same validator on push and pull request.

pyproject.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[tool.ruff]
2+
line-length = 120
3+
target-version = "py312"
4+
exclude = [
5+
".git",
6+
".venv",
7+
"venv",
8+
"env",
9+
"ENV",
10+
"output",
11+
"preview",
12+
]
13+
14+
[tool.ruff.lint]
15+
select = ["E4", "E7", "E9", "F"]

scripts/render_pdf.py

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import argparse
2525
import html
2626
import re
27-
from datetime import date
2827
from pathlib import Path
2928
from typing import Any, Dict, List, Optional, Sequence
3029

@@ -35,7 +34,7 @@
3534
from reportlab.lib.utils import ImageReader
3635
from reportlab.pdfgen import canvas as pdf_canvas
3736
from reportlab.platypus import (
38-
Flowable, KeepTogether, ListFlowable, ListItem, PageBreak, Paragraph,
37+
Flowable, ListFlowable, ListItem, PageBreak, Paragraph,
3938
SimpleDocTemplate, Spacer, Table, TableStyle,
4039
)
4140
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
@@ -271,7 +270,7 @@ def is_block_start(line: str) -> bool:
271270

272271

273272
def paragraph_text(lines: Sequence[str]) -> str:
274-
parts = [(l.strip() + "<br/>") if l.endswith(" ") else l.strip() for l in lines]
273+
parts = [(line.strip() + "<br/>") if line.endswith(" ") else line.strip() for line in lines]
275274
return re.sub(r"<br/>\s+", "<br/>", " ".join(parts))
276275

277276

@@ -286,19 +285,29 @@ def markdown_to_story(md, styles, available, pal) -> List[Any]:
286285
i += 1
287286
continue
288287
if line == "[PAGE_BREAK]":
289-
story.append(PageBreak()); i += 1; continue
288+
story.append(PageBreak())
289+
i += 1
290+
continue
290291
if line.startswith("# "):
291292
story.append(Paragraph(inline_markup(line[2:].strip()), styles["Title"]))
292-
story.append(Spacer(1, 0.05 * inch)); i += 1; continue
293+
story.append(Spacer(1, 0.05 * inch))
294+
i += 1
295+
continue
293296
if line.startswith("## "):
294-
story.append(Paragraph(numbered_heading_markup(line[3:].strip(), pal), styles["Heading2"])); i += 1; continue
297+
story.append(Paragraph(numbered_heading_markup(line[3:].strip(), pal), styles["Heading2"]))
298+
i += 1
299+
continue
295300
if line.startswith("### "):
296-
story.append(Paragraph(inline_markup(line[4:].strip()), styles["Heading3"])); i += 1; continue
301+
story.append(Paragraph(inline_markup(line[4:].strip()), styles["Heading3"]))
302+
i += 1
303+
continue
297304
if line.startswith("> "):
298305
q = []
299306
while i < len(lines) and lines[i].strip().startswith("> "):
300-
q.append(lines[i].strip()[2:]); i += 1
301-
story.append(make_callout(q, styles, available, pal)); continue
307+
q.append(lines[i].strip()[2:])
308+
i += 1
309+
story.append(make_callout(q, styles, available, pal))
310+
continue
302311
if line.startswith("- "):
303312
items = []
304313
while i < len(lines) and lines[i].strip().startswith("- "):
@@ -307,16 +316,21 @@ def markdown_to_story(md, styles, available, pal) -> List[Any]:
307316
i += 1
308317
story.append(ListFlowable(items, bulletType="bullet", bulletFontName="Helvetica-Bold",
309318
bulletFontSize=6, leftIndent=20, bulletColor=pal["accent"]))
310-
story.append(Spacer(1, 0.04 * inch)); continue
319+
story.append(Spacer(1, 0.04 * inch))
320+
continue
311321
if line.startswith("|"):
312322
tl = []
313323
while i < len(lines) and lines[i].strip().startswith("|"):
314-
tl.append(lines[i]); i += 1
324+
tl.append(lines[i])
325+
i += 1
315326
story.append(make_table(tl, styles, available, pal))
316-
story.append(Spacer(1, 0.10 * inch)); continue
317-
para = [raw]; i += 1
327+
story.append(Spacer(1, 0.10 * inch))
328+
continue
329+
para = [raw]
330+
i += 1
318331
while i < len(lines) and lines[i].strip() and not is_block_start(lines[i]):
319-
para.append(lines[i]); i += 1
332+
para.append(lines[i])
333+
i += 1
320334
story.append(Paragraph(inline_markup(paragraph_text(para)), styles["Body"]))
321335
return story
322336

@@ -332,33 +346,42 @@ def draw_watermark(c, w, h, text):
332346
c.setFillColor(colors.Color(0.7, 0.7, 0.7, alpha=0.16))
333347
except TypeError:
334348
c.setFillColor(colors.HexColor("#D1D5DB"))
335-
c.translate(w / 2.0, h / 2.0); c.rotate(42)
349+
c.translate(w / 2.0, h / 2.0)
350+
c.rotate(42)
336351
c.setFont("Helvetica-Bold", 58)
337-
c.drawCentredString(0, 0, text); c.restoreState()
352+
c.drawCentredString(0, 0, text)
353+
c.restoreState()
338354

339355

340356
def draw_letterhead_band(c, meta, w, h, pal, logo_path):
341357
band = 0.95 * inch
342358
c.saveState()
343-
c.setFillColor(pal["accent_dark"]); c.rect(0, h - band, w, band, stroke=0, fill=1)
344-
c.setFillColor(pal["accent"]); c.rect(0, h - band - 0.04 * inch, w, 0.04 * inch, stroke=0, fill=1)
359+
c.setFillColor(pal["accent_dark"])
360+
c.rect(0, h - band, w, band, stroke=0, fill=1)
361+
c.setFillColor(pal["accent"])
362+
c.rect(0, h - band - 0.04 * inch, w, 0.04 * inch, stroke=0, fill=1)
345363
pad = 0.72 * inch
346364
ls = 0.52 * inch
347365
draw_logo(c, pad, h - band + (band - ls) / 2.0, ls, logo_path, pal, on_dark=True)
348366
nx = pad + ls + 0.18 * inch
349-
c.setFillColor(pal["white"]); c.setFont("Helvetica-Bold", 13.5)
367+
c.setFillColor(pal["white"])
368+
c.setFont("Helvetica-Bold", 13.5)
350369
c.drawString(nx, h - 0.42 * inch, BRAND["brand_name"])
351-
c.setFont("Helvetica", 8.5); c.setFillColor(colors.Color(1, 1, 1, alpha=0.78))
370+
c.setFont("Helvetica", 8.5)
371+
c.setFillColor(colors.Color(1, 1, 1, alpha=0.78))
352372
c.drawString(nx, h - 0.58 * inch, BRAND["brand_tagline"])
353-
c.setFont("Helvetica", 7.5); c.setFillColor(colors.Color(1, 1, 1, alpha=0.62))
373+
c.setFont("Helvetica", 7.5)
374+
c.setFillColor(colors.Color(1, 1, 1, alpha=0.62))
354375
c.drawString(nx, h - 0.74 * inch, BRAND["contact"])
355376
right = w - pad
356377
pairs = meta[:2]
357378
ys = [(0.36, 0.50), (0.66, 0.80)]
358379
for (label, value), (ly, vy) in zip(pairs, ys):
359-
c.setFillColor(colors.Color(1, 1, 1, alpha=0.7)); c.setFont("Helvetica", 7.2)
380+
c.setFillColor(colors.Color(1, 1, 1, alpha=0.7))
381+
c.setFont("Helvetica", 7.2)
360382
c.drawRightString(right, h - ly * inch, label.upper())
361-
c.setFillColor(pal["white"]); c.setFont("Helvetica-Bold", 10.5)
383+
c.setFillColor(pal["white"])
384+
c.setFont("Helvetica-Bold", 10.5)
362385
c.drawRightString(right, h - vy * inch, value)
363386
c.restoreState()
364387

@@ -369,16 +392,21 @@ def draw_compact_header(c, doc_title, doc_id, w, h, pal, logo_path):
369392
draw_logo(c, pad, h - 0.62 * inch, ls, logo_path, pal, on_dark=False)
370393
tx = pad + ls + 0.12 * inch
371394
c.saveState()
372-
c.setFillColor(pal["ink"]); c.setFont("Helvetica-Bold", 9)
395+
c.setFillColor(pal["ink"])
396+
c.setFont("Helvetica-Bold", 9)
373397
c.drawString(tx, h - 0.46 * inch, BRAND["brand_name"])
374-
c.setFillColor(pal["muted"]); c.setFont("Helvetica", 7.5)
398+
c.setFillColor(pal["muted"])
399+
c.setFont("Helvetica", 7.5)
375400
c.drawString(tx, h - 0.58 * inch, BRAND["brand_tagline"])
376401
right = w - pad
377-
c.setFillColor(pal["ink"]); c.setFont("Helvetica-Bold", 8.8)
402+
c.setFillColor(pal["ink"])
403+
c.setFont("Helvetica-Bold", 8.8)
378404
c.drawRightString(right, h - 0.46 * inch, doc_title)
379-
c.setFillColor(pal["muted"]); c.setFont("Helvetica", 7.5)
405+
c.setFillColor(pal["muted"])
406+
c.setFont("Helvetica", 7.5)
380407
c.drawRightString(right, h - 0.58 * inch, doc_id)
381-
c.setStrokeColor(pal["accent"]); c.setLineWidth(0.8)
408+
c.setStrokeColor(pal["accent"])
409+
c.setLineWidth(0.8)
382410
c.line(pad, h - 0.74 * inch, right, h - 0.74 * inch)
383411
c.restoreState()
384412

@@ -388,13 +416,17 @@ def draw_footer(c, doc, footer_text, doc_id, w, pal):
388416
right = w - pad
389417
y = 0.46 * inch
390418
c.saveState()
391-
c.setStrokeColor(pal["border"]); c.setLineWidth(0.5)
419+
c.setStrokeColor(pal["border"])
420+
c.setLineWidth(0.5)
392421
c.line(pad, y + 0.18 * inch, right, y + 0.18 * inch)
393-
c.setFillColor(pal["accent"]); c.rect(pad, y + 0.18 * inch, 0.32 * inch, 0.012 * inch, stroke=0, fill=1)
394-
c.setFillColor(pal["muted"]); c.setFont("Helvetica", 7.4)
422+
c.setFillColor(pal["accent"])
423+
c.rect(pad, y + 0.18 * inch, 0.32 * inch, 0.012 * inch, stroke=0, fill=1)
424+
c.setFillColor(pal["muted"])
425+
c.setFont("Helvetica", 7.4)
395426
c.drawString(pad, y, doc_id)
396427
c.drawCentredString(w / 2, y, footer_text[:120])
397-
c.setFillColor(pal["ink"]); c.setFont("Helvetica-Bold", 7.6)
428+
c.setFillColor(pal["ink"])
429+
c.setFont("Helvetica-Bold", 7.6)
398430
c.drawRightString(right, y, f"Page {doc.page}")
399431
c.restoreState()
400432

@@ -405,7 +437,8 @@ def draw(c, doc):
405437
draw_watermark(c, w, h, cfg["watermark"])
406438
if first_page:
407439
if cover_first:
408-
draw_footer(c, doc, cfg["footer"], cfg["doc_id"], w, pal); return
440+
draw_footer(c, doc, cfg["footer"], cfg["doc_id"], w, pal)
441+
return
409442
draw_letterhead_band(c, cfg["meta"], w, h, pal, cfg["logo"])
410443
else:
411444
draw_compact_header(c, cfg["title"], cfg["doc_id"], w, h, pal, cfg["logo"])
@@ -434,7 +467,7 @@ def field(label, value):
434467

435468
meta = cfg["meta"][:3]
436469
if meta:
437-
row = [[field(l, v) for l, v in meta]]
470+
row = [[field(label, value) for label, value in meta]]
438471
cw = (available * 0.9) / len(meta)
439472
chip = Table(row, colWidths=[cw] * len(meta), hAlign="CENTER")
440473
chip.setStyle(TableStyle([

0 commit comments

Comments
 (0)