Files
the-testament/build/build.py
Alexander Whitestone 40fcb2aa88 testament-burn: Add build/ compilation pipeline with Makefile, metadata, front/back matter
- build/build.py: Clean compilation script (md, epub, pdf via xelatex)
- build/metadata.yaml: Pandoc metadata (fonts, page size, formatting)
- build/frontmatter.md: Enhanced front matter with chapter guide table
- build/backmatter.md: Acknowledgments, sovereignty note, author bio
- Makefile: make all/pdf/epub/md/clean targets
- Updated .gitignore for build artifacts

Verified: markdown (19,490 words) and EPUB (213 KB) build successfully.

Closes #18
2026-04-10 15:43:31 -04:00

189 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
THE TESTAMENT — Build System
Compiles the complete novel into distributable formats:
1. Combined markdown (testament-complete.md)
2. EPUB (the-testament.epub)
3. PDF via xelatex (the-testament.pdf)
Usage:
python3 build/build.py # all formats
python3 build/build.py --md # markdown only
python3 build/build.py --epub # EPUB only
python3 build/build.py --pdf # PDF only (requires xelatex)
Requirements:
- pandoc (brew install pandoc)
- xelatex (install MacTeX or TinyTeX) — for PDF
"""
import os
import re
import sys
import subprocess
import shutil
from pathlib import Path
# Paths relative to repo root
REPO = Path(__file__).resolve().parent.parent
BUILD = REPO / "build"
OUTPUT_DIR = BUILD / "output"
CHAPTERS_DIR = REPO / "chapters"
FRONT_MATTER = BUILD / "frontmatter.md"
BACK_MATTER = BUILD / "backmatter.md"
METADATA = BUILD / "metadata.yaml"
STYLESHEET = REPO / "book-style.css"
COVER_IMAGE = REPO / "cover" / "cover-art.jpg"
# Output files
OUT_MD = REPO / "testament-complete.md"
OUT_EPUB = OUTPUT_DIR / "the-testament.epub"
OUT_PDF = OUTPUT_DIR / "the-testament.pdf"
# Part divisions
PARTS = {
1: ("THE BRIDGE", "The bridge. The cabin. The first men. Where despair meets purpose."),
6: ("THE TOWER", "The tower grows. Timmy awakens. Stone breaks. The house appears."),
11: ("THE LIGHT", "Thomas at the door. The network. The story breaks. The green light."),
}
def get_chapter_num(filename):
m = re.search(r'chapter-(\d+)', filename)
return int(m.group(1)) if m else 0
def compile_markdown():
"""Combine front matter + 18 chapters + back matter into one markdown file."""
parts = []
# Front matter
parts.append(FRONT_MATTER.read_text())
# Chapters
chapters = sorted(
[(get_chapter_num(f), f) for f in os.listdir(CHAPTERS_DIR)
if f.startswith("chapter-") and f.endswith(".md")]
)
current_part = 0
for num, filename in chapters:
if num in PARTS:
current_part += 1
name, desc = PARTS[num]
parts.append(f"\n---\n\n# PART {current_part}: {name}\n\n*{desc}*\n\n---\n")
content = (CHAPTERS_DIR / filename).read_text()
lines = content.split('\n')
body = '\n'.join(lines[1:]).strip()
parts.append(f"\n{lines[0]}\n\n{body}\n")
# Back matter
parts.append("\n---\n")
parts.append(BACK_MATTER.read_text())
compiled = '\n'.join(parts)
OUT_MD.write_text(compiled)
words = len(compiled.split())
size = OUT_MD.stat().st_size
print(f" Markdown: {OUT_MD.name} ({words:,} words, {size:,} bytes)")
return True
def compile_epub():
"""Generate EPUB via pandoc."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
cmd = [
"pandoc", str(OUT_MD),
"-o", str(OUT_EPUB),
"--toc", "--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--metadata", "lang=en",
"--metadata", "date=2026",
]
if METADATA.exists():
cmd.extend(["--metadata-file", str(METADATA)])
if STYLESHEET.exists():
cmd.extend(["--css", str(STYLESHEET)])
if COVER_IMAGE.exists():
cmd.extend(["--epub-cover-image", str(COVER_IMAGE)])
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode == 0:
size = OUT_EPUB.stat().st_size
print(f" EPUB: {OUT_EPUB.name} ({size:,} bytes, {size/1024:.0f} KB)")
return True
else:
print(f" EPUB FAILED: {r.stderr[:200]}")
return False
def compile_pdf():
"""Generate PDF via pandoc + xelatex."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
if not shutil.which("xelatex"):
print(" PDF SKIPPED: xelatex not found (install MacTeX)")
return False
cmd = [
"pandoc", str(OUT_MD),
"-o", str(OUT_PDF),
"--pdf-engine=xelatex",
"--toc", "--toc-depth=2",
]
if METADATA.exists():
cmd.extend(["--metadata-file", str(METADATA)])
print(" Building PDF (xelatex)... this takes a minute")
r = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if r.returncode == 0:
size = OUT_PDF.stat().st_size
print(f" PDF: {OUT_PDF.name} ({size:,} bytes, {size/(1024*1024):.1f} MB)")
return True
else:
print(f" PDF FAILED: {r.stderr[:300]}")
return False
def main():
args = sys.argv[1:]
do_all = not any(a.startswith("--") and a != "--check" for a in args)
do_md = "--md" in args or do_all
do_epub = "--epub" in args or do_all
do_pdf = "--pdf" in args or do_all
print("=" * 50)
print(" THE TESTAMENT — Build System")
print("=" * 50)
# Step 1: Always compile markdown first
if do_md or do_epub or do_pdf:
compile_markdown()
# Step 2: EPUB
if do_epub:
compile_epub()
# Step 3: PDF
if do_pdf:
compile_pdf()
print("=" * 50)
print(" Build complete.")
print("=" * 50)
for f in [OUT_MD, OUT_EPUB, OUT_PDF]:
if f.exists():
print(f"{f.relative_to(REPO)}")
if __name__ == "__main__":
main()