#!/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()