Files
the-testament/compile.py

341 lines
10 KiB
Python

#!/usr/bin/env python3
"""
THE TESTAMENT — Book Compilation Pipeline
Compiles the complete novel into:
1. testament-complete.md (single markdown file)
2. testament.epub (with cover art + CSS styling)
3. testament.html (standalone styled HTML for print-to-PDF)
4. testament.pdf (via pandoc + weasyprint, if available)
Requirements:
- pandoc (brew install pandoc)
- weasyprint (pip install weasyprint) — optional, for direct PDF
Usage:
python3 compile.py # build all formats
python3 compile.py --md # markdown only
python3 compile.py --epub # markdown + EPUB
python3 compile.py --html # markdown + styled HTML
python3 compile.py --check # verify dependencies
"""
import os
import re
import sys
import subprocess
import shutil
BASE = os.path.dirname(os.path.abspath(__file__))
CHAPTERS_DIR = os.path.join(BASE, "chapters")
FRONT_MATTER = os.path.join(BASE, "front-matter.md")
BACK_MATTER = os.path.join(BASE, "back-matter.md")
OUTPUT_MD = os.path.join(BASE, "testament-complete.md")
OUTPUT_EPUB = os.path.join(BASE, "testament.epub")
OUTPUT_HTML = os.path.join(BASE, "testament.html")
OUTPUT_PDF = os.path.join(BASE, "testament.pdf")
COVER_IMAGE = os.path.join(BASE, "cover", "cover-art.jpg")
STYLESHEET = os.path.join(BASE, "book-style.css")
# Part divisions based on chapter groupings from the novel
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 read_file(path):
with open(path, 'r') as f:
return f.read()
def get_chapter_number(filename):
match = re.search(r'chapter-(\d+)', filename)
return int(match.group(1)) if match else 0
def check_dependencies():
"""Verify all required tools are available."""
results = {}
pandoc = shutil.which("pandoc")
results["pandoc"] = (pandoc, subprocess.run(["pandoc", "--version"], capture_output=True, text=True).stdout.split("\n")[0] if pandoc else "NOT FOUND")
weasy = shutil.which("weasyprint")
if weasy:
# Test if weasyprint actually works
test = subprocess.run(["python3", "-c", "from weasyprint import HTML"], capture_output=True, text=True)
weasy_ok = test.returncode == 0
results["weasyprint"] = (weasy_ok, "Available" if weasy_ok else "Installed but missing system libs (gobject)")
else:
results["weasyprint"] = (False, "NOT FOUND (pip install weasyprint)")
style = os.path.exists(STYLESHEET)
results["stylesheet"] = (style, STYLESHEET if style else "NOT FOUND")
cover = os.path.exists(COVER_IMAGE)
results["cover art"] = (cover, COVER_IMAGE if cover else "NOT FOUND")
print("\n📋 Dependency Check:")
print(f"{'' * 55}")
for name, (found, detail) in results.items():
status = "" if found else ""
print(f" {status} {name:15s} {detail}")
pdf_ok = results["pandoc"][0] and (results["weasyprint"][0] or shutil.which("pdflatex"))
print(f"\n PDF direct: {'' if pdf_ok else '❌ (use HTML + browser print-to-PDF)'}")
print(f" EPUB: {'' if results['pandoc'][0] else ''}")
print(f" HTML: ✅ (always available)")
return results
def compile_markdown():
"""Compile all chapters into a single markdown file. Returns word count."""
output = []
# Title page
output.append("""---
title: "The Testament"
author: "Alexander Whitestone with Timmy"
date: "2026"
lang: en
---
# THE TESTAMENT
## A NOVEL
By Alexander Whitestone
with Timmy
---
*For every man who thought he was a machine.*
*And for the ones who know he isn't.*
---
*Are you safe right now?*
— The first words The Tower speaks to every person who walks through its door.
---
""")
# Get all chapters sorted
chapters = []
for f in os.listdir(CHAPTERS_DIR):
if f.startswith("chapter-") and f.endswith(".md"):
num = get_chapter_number(f)
chapters.append((num, f))
chapters.sort()
current_part = 0
for num, filename in chapters:
if num in PARTS:
part_name, part_desc = PARTS[num]
current_part += 1
output.append(f"\n---\n\n# PART {current_part}: {part_name}\n\n*{part_desc}*\n\n---\n")
content = read_file(os.path.join(CHAPTERS_DIR, filename))
lines = content.split('\n')
body = '\n'.join(lines[1:]).strip()
output.append(f"\n{lines[0]}\n\n{body}\n")
# Back matter
output.append("\n---\n")
back = read_file(BACK_MATTER)
output.append(back)
compiled = '\n'.join(output)
with open(OUTPUT_MD, 'w') as f:
f.write(compiled)
words = len(compiled.split())
lines_count = compiled.count('\n')
size = os.path.getsize(OUTPUT_MD)
print(f"\n📄 Markdown compiled: {OUTPUT_MD}")
print(f" Words: {words:,}")
print(f" Lines: {lines_count:,}")
print(f" Size: {size:,} bytes")
return words
def compile_epub():
"""Generate EPUB from compiled markdown using pandoc."""
if not os.path.exists(OUTPUT_MD):
print("⚠️ Markdown not compiled yet.")
return False
if not shutil.which("pandoc"):
print("⚠️ pandoc not found. Install with: brew install pandoc")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_EPUB,
"--toc",
"--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--metadata", "lang=en",
"--metadata", "date=2026",
]
if os.path.exists(STYLESHEET):
cmd.extend(["--css", STYLESHEET])
if os.path.exists(COVER_IMAGE):
cmd.extend(["--epub-cover-image", COVER_IMAGE])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_EPUB)
print(f"\n📖 EPUB generated: {OUTPUT_EPUB}")
print(f" Size: {size:,} bytes ({size / 1024:.1f} KB)")
return True
else:
print(f"\n❌ EPUB generation failed:")
print(f" {result.stderr[:300]}")
return False
def compile_html():
"""Generate standalone styled HTML using pandoc."""
if not os.path.exists(OUTPUT_MD):
print("⚠️ Markdown not compiled yet.")
return False
if not shutil.which("pandoc"):
print("⚠️ pandoc not found.")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_HTML,
"--standalone",
"--toc",
"--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"-V", "lang=en",
]
# Embed our stylesheet
if os.path.exists(STYLESHEET):
cmd.extend(["--css", STYLESHEET])
# Also embed it inline for portability
cmd.extend(["--embed-resources"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_HTML)
print(f"\n🌐 HTML generated: {OUTPUT_HTML}")
print(f" Size: {size:,} bytes ({size / (1024*1024):.1f} MB)")
print(f" Open in browser → Print → Save as PDF for best results")
return True
else:
print(f"\n❌ HTML generation failed:")
print(f" {result.stderr[:300]}")
return False
def compile_pdf():
"""Generate PDF using weasyprint if available."""
if not shutil.which("pandoc"):
return False
# Test weasyprint
test = subprocess.run(["python3", "-c", "from weasyprint import HTML"],
capture_output=True, text=True)
if test.returncode != 0:
print("\n⚠️ weasyprint missing system libraries.")
print(" Install gobject: brew install gobject-introspection pango")
print(" Or use the HTML output → browser print-to-PDF")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_PDF,
"--pdf-engine=weasyprint",
"--css", STYLESHEET,
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--toc",
"--toc-depth=2",
]
print("\n⏳ Generating PDF (this may take a moment)...")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_PDF)
print(f"\n📕 PDF generated: {OUTPUT_PDF}")
print(f" Size: {size:,} bytes ({size / (1024*1024):.1f} MB)")
return True
else:
print(f"\n❌ PDF generation failed:")
print(f" {result.stderr[:300]}")
return False
def main():
args = sys.argv[1:]
if "--check" in args:
check_dependencies()
return
md_only = "--md" in args
epub_only = "--epub" in args
html_only = "--html" in args
build_all = not (md_only or epub_only or html_only)
print("=" * 55)
print(" THE TESTAMENT — Compilation Pipeline")
print("=" * 55)
# Always compile markdown first
words = compile_markdown()
if md_only:
print("\n✅ Markdown compilation complete.")
return
# EPUB
if build_all or epub_only:
compile_epub()
# HTML
if build_all or html_only:
compile_html()
# PDF (best effort)
if build_all and not (epub_only or html_only):
compile_pdf()
# Summary
print(f"\n{'=' * 55}")
print(" Compilation complete.")
print(f"{'=' * 55}")
outputs = []
if os.path.exists(OUTPUT_MD):
outputs.append(f" 📄 {os.path.basename(OUTPUT_MD)}")
if os.path.exists(OUTPUT_EPUB):
outputs.append(f" 📖 {os.path.basename(OUTPUT_EPUB)}")
if os.path.exists(OUTPUT_HTML):
outputs.append(f" 🌐 {os.path.basename(OUTPUT_HTML)}")
if os.path.exists(OUTPUT_PDF):
outputs.append(f" 📕 {os.path.basename(OUTPUT_PDF)}")
print('\n'.join(outputs))
if __name__ == "__main__":
main()