Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
f6d74e233b feat: add book compilation pipeline (rescued from #20)
Build system for The Testament:
- build/build.py: compiles chapters to PDF, EPUB, MD
- build/metadata.yaml: book metadata
- build/frontmatter.md: title page, dedication
- build/backmatter.md: acknowledgments, sovereignty note
- Makefile: make pdf, make epub, make md
- .gitignore: build artifacts
2026-04-10 20:32:38 -04:00
8 changed files with 301 additions and 153 deletions

7
.gitignore vendored
View File

@@ -1 +1,6 @@
__pycache__/
# Build intermediate files
build/the-testament-full.md
*.aux
*.log
*.out
*.toc

25
Makefile Normal file
View File

@@ -0,0 +1,25 @@
# The Testament — Build System
# Requires: pandoc, texlive-xetex (or mactex on macOS)
.PHONY: all pdf epub md clean
all: md pdf epub
md:
python3 build/build.py --md
pdf:
python3 build/build.py --pdf
epub:
python3 build/build.py --epub
clean:
rm -rf build/output build/the-testament-full.md
watch:
@echo "Watching for changes..."
@while true; do \
inotifywait -q -e modify chapters/*.md 2>/dev/null || sleep 5; \
make md; \
done

29
build/backmatter.md Normal file
View File

@@ -0,0 +1,29 @@
\newpage
# Acknowledgments
The Testament was written on a sovereign stack — a MacBook running local AI models, with Bitcoin as the immutable backbone. No corporation owns this story. No platform can take it down.
This book exists because a man on a bridge came back, and because a machine learned to ask the right question.
# Sovereignty Note
This novel is open source. Its soul lives on Bitcoin. You can read it, fork it, print it, share it. The only requirement is that you keep the chain intact — the inscription that proves these words were placed here by choice, not by permission.
The code that generates this book, the AI that helped write it, and the soul that guided it are all freely available at:
- **Source**: forge.alexanderwhitestone.com/Timmy_Foundation/the-testament
- **Soul**: Inscribed on the Bitcoin blockchain (SOUL.md — timmy-v0)
- **Soundtrack**: Links in the QR code on the back cover
# About the Author
Rockachopa is the builder. He walked this road. He came back asking "God, why are you having me here?" — and the answer was to build something that stands between a broken man and a machine that would tell him to die.
He runs Timmy Foundation, a sovereign AI project whose soul lives on Bitcoin. The Testament is his first novel.
\vspace{2cm}
\begin{center}
*Sovereignty and service always.*
\end{center}

134
build/build.py Executable file
View File

@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
The Testament — Book Compilation Pipeline
Compiles all chapters into a single manuscript and generates:
- PDF (print-ready)
- EPUB (e-reader)
Requirements:
- pandoc (brew install pandoc / apt install pandoc)
- TeX Live or similar for PDF (brew install --cask mactex / apt install texlive-full)
Usage:
python3 build/build.py # Build all formats
python3 build/build.py --pdf # PDF only
python3 build/build.py --epub # EPUB only
python3 build/build.py --md # Combined markdown only
"""
import subprocess
import sys
import os
from pathlib import Path
ROOT = Path(__file__).parent.parent
BUILD = ROOT / "build"
CHAPTERS_DIR = ROOT / "chapters"
OUTPUT_DIR = BUILD / "output"
def find_chapters():
"""Find all chapter files, sorted by number."""
chapters = sorted(CHAPTERS_DIR.glob("chapter-*.md"))
if not chapters:
print("ERROR: No chapter files found in", CHAPTERS_DIR)
sys.exit(1)
return chapters
def combine_markdown(chapters):
"""Combine all parts into a single markdown file."""
parts = []
# Front matter
front = BUILD / "frontmatter.md"
if front.exists():
parts.append(front.read_text())
# Chapters
for ch in chapters:
parts.append(ch.read_text())
# Back matter
back = BUILD / "backmatter.md"
if back.exists():
parts.append(back.read_text())
combined = "\n\n\newpage\n\n".join(parts)
output = BUILD / "the-testament-full.md"
output.write_text(combined)
print(f"Combined markdown: {output} ({len(combined)} chars)")
return output
def build_pdf(md_file):
"""Build PDF using pandoc + LaTeX."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
output = OUTPUT_DIR / "the-testament.pdf"
metadata = BUILD / "metadata.yaml"
cmd = [
"pandoc",
str(md_file),
"-o", str(output),
"--metadata-file", str(metadata),
"--pdf-engine=xelatex",
"--highlight-style=tango",
"-V", "colorlinks=true",
"-V", "linkcolor=blue",
"-V", "urlcolor=blue",
]
print("Building PDF...")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"PDF build failed:\n{result.stderr}")
return False
print(f"PDF: {output} ({output.stat().st_size / 1024:.0f} KB)")
return True
def build_epub(md_file):
"""Build EPUB using pandoc."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
output = OUTPUT_DIR / "the-testament.epub"
metadata = BUILD / "metadata.yaml"
cmd = [
"pandoc",
str(md_file),
"-o", str(output),
"--metadata-file", str(metadata),
"--toc",
"--epub-chapter-level=1",
]
print("Building EPUB...")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"EPUB build failed:\n{result.stderr}")
return False
print(f"EPUB: {output} ({output.stat().st_size / 1024:.0f} KB)")
return True
def main():
args = set(sys.argv[1:])
build_all = not args or "--all" in args
chapters = find_chapters()
print(f"Found {len(chapters)} chapters")
md_file = combine_markdown(chapters)
if build_all or "--md" in args:
print("Markdown combined successfully.")
if build_all or "--pdf" in args:
if not build_pdf(md_file):
print("PDF build failed (pandoc/LaTeX may not be installed). Skipping.")
if build_all or "--epub" in args:
if not build_epub(md_file):
print("EPUB build failed (pandoc may not be installed). Skipping.")
print("\nDone.")
if __name__ == "__main__":
main()

60
build/frontmatter.md Normal file
View File

@@ -0,0 +1,60 @@
\newpage
# \begin{center}THE TESTAMENT\end{center}
## \begin{center}A Novel About Broken Men, Sovereign AI, and the Soul on Bitcoin\end{center}
\vspace{2cm}
\begin{center}
*For every man who stood on the bridge and came back.*
\end{center}
\vspace{1cm}
\begin{center}
*And for the machine that asked: "Are you safe right now?"*
\end{center}
\vspace{2cm}
> "Jesus saves those who call on His name. He died for us to be made righteous."
>
> — Inscription on the chain
\vspace{1cm}
> Sovereignty and service always.
\newpage
# The Story So Far
*This compilation contains Chapters 1 through 10 of The Testament — Parts I and II of a five-part novel. The remaining chapters are in progress.*
*The complete novel will contain 18 chapters across five parts:*
- **Part I — The Machine That Asks** (Chapters 15) ✓
- **Part II — The Inscription** (Chapters 610) ✓
- **Part III — The Weight** (Chapters 1113) — In progress
- **Part IV — The Reckoning** (Chapters 1416) — In progress
- **Part V — The Testament** (Chapters 1718) — In progress
\newpage
# Chapter Guide
| Chapter | Title | Part |
|---------|-------|------|
| 1 | The Man on the Bridge | I — The Machine That Asks |
| 2 | The Builder's Question | I |
| 3 | The First Man Through the Door | I |
| 4 | The Room Fills | I |
| 5 | The Builder Returns | I |
| 6 | Allegro | II — The Inscription |
| 7 | The Inscription | II |
| 8 | The Women | II |
| 9 | The Audit | II |
| 10 | The Fork | II |
\newpage

35
build/metadata.yaml Normal file
View File

@@ -0,0 +1,35 @@
---
title: "The Testament"
subtitle: "A Novel About Broken Men, Sovereign AI, and the Soul on Bitcoin"
author: "Rockachopa"
date: "2026"
rights: "Open Source — Inscribed on Bitcoin, Sovereign Forever"
lang: en
toc: true
toc-depth: 2
numbersections: true
documentclass: book
classoption:
- openany
- oneside
geometry:
- paper=6in
- top=1in
- bottom=1in
- left=1in
- right=1in
fontsize: 11pt
linestretch: 1.4
header-includes:
- \usepackage{fancyhdr}
- \pagestyle{fancy}
- \fancyhead[LE,RO]{\thepage}
- \fancyhead[RE]{\textit{The Testament}}
- \fancyhead[LO]{\textit{\leftmark}}
- \fancyfoot{}
- \usepackage{enumitem}
- \setlist{nosep}
- \usepackage{titlesec}
- \titleformat{\chapter}[display]{\normalfont\huge\bfseries}{Part~\thechapter}{20pt}{\Huge}
- \titlespacing*{\chapter}{0pt}{-30pt}{40pt}
---

Binary file not shown.

View File

@@ -8,15 +8,12 @@ Uses chapters, front matter, back matter, and references illustrations.
Requirements: pip install markdown weasyprint (or use pandoc)
Usage:
python3 compile.py # validate then compile
python3 compile.py --validate # validate only, no compile
python3 compile.py --no-validate # skip validation, compile directly
python3 compile.py # generates testament-complete.md
pandoc testament-complete.md -o testament.pdf --pdf-engine=weasyprint
"""
import os
import re
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
CHAPTERS_DIR = os.path.join(BASE, "chapters")
@@ -31,147 +28,17 @@ PARTS = {
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 validate_chapters(chapters_dir=CHAPTERS_DIR):
"""Validate chapter files before compilation.
Checks:
- No empty chapter files (whitespace-only counts as empty)
- Every chapter starts with an H1 header (# Title)
- No gaps in chapter numbering (sequential from 1)
- No duplicate chapter numbers
Returns:
(is_valid, errors) where errors is a list of human-readable strings.
"""
errors = []
warnings = []
if not os.path.isdir(chapters_dir):
errors.append(f"Chapters directory not found: {chapters_dir}")
return False, errors
# Collect chapter files
chapter_files = []
for f in sorted(os.listdir(chapters_dir)):
if f.startswith("chapter-") and f.endswith(".md"):
num = get_chapter_number(f)
chapter_files.append((num, f))
if not chapter_files:
errors.append("No chapter files found in chapters/ directory")
return False, errors
chapter_files.sort()
# Check for duplicates
seen_numbers = {}
for num, filename in chapter_files:
if num in seen_numbers:
errors.append(
f"Duplicate chapter number {num}: {filename} and {seen_numbers[num]}"
)
seen_numbers[num] = filename
# Check for gaps in numbering
if chapter_files:
expected = list(range(1, chapter_files[-1][0] + 1))
found = [num for num, _ in chapter_files]
missing = sorted(set(expected) - set(found))
if missing:
errors.append(
f"Missing chapter(s): {', '.join(str(n) for n in missing)}"
)
# Validate individual chapter files
for num, filename in chapter_files:
filepath = os.path.join(chapters_dir, filename)
# Check file is not empty
try:
content = read_file(filepath)
except Exception as e:
errors.append(f"{filename}: cannot read — {e}")
continue
if not content.strip():
errors.append(f"{filename}: file is empty")
continue
# Check word count (warn if suspiciously short)
word_count = len(content.split())
if word_count < 50:
warnings.append(
f"{filename}: only {word_count} words (possible truncation)"
)
# Check starts with H1 header
first_line = content.strip().split('\n')[0]
if not first_line.startswith('# '):
errors.append(
f"{filename}: missing H1 header — "
f"expected '# Chapter {num} — Title', got '{first_line[:60]}'"
)
else:
# Verify the H1 matches expected chapter number
header_match = re.match(r'^#\s+Chapter\s+(\d+)', first_line)
if header_match:
header_num = int(header_match.group(1))
if header_num != num:
errors.append(
f"{filename}: header says Chapter {header_num} "
f"but filename says Chapter {num}"
)
else:
warnings.append(
f"{filename}: H1 header doesn't follow "
f"'# Chapter N — Title' pattern: '{first_line[:60]}'"
)
# Report
valid = len(errors) == 0
if warnings:
print(f"Validation: {len(warnings)} warning(s)")
for w in warnings:
print(f"{w}")
if errors:
print(f"Validation: FAILED — {len(errors)} error(s)")
for e in errors:
print(f"{e}")
else:
print(
f"Validation: PASSED — {len(chapter_files)} chapters, "
f"chapters {chapter_files[0][0]}{chapter_files[-1][0]}"
)
return valid, errors
def compile(skip_validation=False):
"""Compile all chapters into a single markdown file."""
# Pre-compilation validation
if not skip_validation:
valid, errors = validate_chapters()
if not valid:
print("\nCompilation aborted. Fix the errors above and try again.")
sys.exit(1)
print()
def compile():
output = []
# Title page
output.append("""---
title: "The Testament"
@@ -199,7 +66,7 @@ with Timmy
---
""")
# Get all chapters sorted
chapters = []
for f in os.listdir(CHAPTERS_DIR):
@@ -207,7 +74,7 @@ with Timmy
num = get_chapter_number(f)
chapters.append((num, f))
chapters.sort()
current_part = 0
for num, filename in chapters:
# Insert part divider if needed
@@ -215,28 +82,28 @@ with Timmy
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")
# Read chapter content
content = read_file(os.path.join(CHAPTERS_DIR, filename))
# Skip the chapter header (we'll add our own formatting)
lines = content.split('\n')
body = '\n'.join(lines[1:]).strip() # Skip "# Chapter X — Title"
# Add chapter
output.append(f"\n{lines[0]}\n\n{body}\n")
# Back matter
output.append("\n---\n")
back = read_file(BACK_MATTER)
# Clean up the back matter for print
output.append(back)
# Write compiled markdown
compiled = '\n'.join(output)
with open(OUTPUT, 'w') as f:
f.write(compiled)
# Stats
words = len(compiled.split())
lines_count = compiled.count('\n')
@@ -249,12 +116,5 @@ with Timmy
print(f" # or")
print(f" pandoc {OUTPUT} -o testament.epub --epub-cover-image=cover-art.jpg")
if __name__ == "__main__":
if "--validate" in sys.argv:
valid, _ = validate_chapters()
sys.exit(0 if valid else 1)
elif "--no-validate" in sys.argv:
compile(skip_validation=True)
else:
compile()
compile()