Files
shah-kiyosaki/assemble_pdf.py
T

318 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Assemble 'The Sufi Who Fooled the Algorithm' PDF — Kiyosaki × Shah × Achebe style, Penguin 6×9in."""
import os, re, sys
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
Paragraph, Spacer, PageBreak, Frame, PageTemplate, BaseDocTemplate
)
OUTPUT_DIR = os.path.expanduser("~/odysseus_book/shah_kiyosaki")
CHAPTERS_DIR = os.path.join(OUTPUT_DIR, "chapters")
PDF_PATH = os.path.join(OUTPUT_DIR, "pdf", "The_Sufi_Who_Fooled_the_Algorithm.pdf")
MARGIN = 0.85 * inch
FOOTER_H = 0.4 * inch
PAGE_W = 6 * inch
PAGE_H = 9 * inch
DARK_TEAL = HexColor('#0D4F4F')
GOLD = HexColor('#C8A951')
DARK = HexColor('#1a1a1a')
GREY = HexColor('#666666')
LIGHT_GREY = HexColor('#f0f0f0')
ACCENT_ORANGE = HexColor('#E67E22')
style_title = ParagraphStyle('Title', fontName='Helvetica-Bold', fontSize=26,
leading=32, alignment=TA_CENTER, textColor=DARK_TEAL, spaceAfter=8)
style_subtitle = ParagraphStyle('Subtitle', fontName='Helvetica-Oblique', fontSize=13,
leading=17, alignment=TA_CENTER, textColor=GOLD, spaceAfter=20)
style_author = ParagraphStyle('Author', fontName='Helvetica', fontSize=12,
leading=16, alignment=TA_CENTER, textColor=GREY, spaceAfter=28)
style_copyright = ParagraphStyle('Copyright', fontName='Helvetica', fontSize=9,
leading=12, alignment=TA_CENTER, textColor=GREY)
style_ch_num = ParagraphStyle('ChNum', fontName='Helvetica-Bold', fontSize=10,
leading=13, alignment=TA_CENTER, textColor=GOLD, spaceAfter=4, spaceBefore=18)
style_ch_title = ParagraphStyle('ChTitle', fontName='Helvetica-Bold', fontSize=20,
leading=26, alignment=TA_LEFT, textColor=DARK_TEAL, spaceAfter=8, spaceBefore=4)
style_subtitle_ch = ParagraphStyle('SubtitleCh', fontName='Helvetica-Oblique', fontSize=12,
leading=16, alignment=TA_LEFT, textColor=ACCENT_ORANGE, spaceAfter=14, spaceBefore=2)
style_framework = ParagraphStyle('Framework', fontName='Helvetica-Bold', fontSize=11,
leading=15, alignment=TA_LEFT, textColor=DARK_TEAL, spaceAfter=6, spaceBefore=14,
backColor=LIGHT_GREY, borderColor=GOLD, borderWidth=1, borderPadding=8)
style_body = ParagraphStyle('Body', fontName='Times-Roman', fontSize=10,
leading=14.5, alignment=TA_JUSTIFY, textColor=DARK, spaceAfter=7, firstLineIndent=0)
style_dialogue = ParagraphStyle('Dialogue', fontName='Times-Roman', fontSize=10,
leading=14.5, alignment=TA_LEFT, textColor=DARK, spaceAfter=5, leftIndent=24)
style_shah = ParagraphStyle('Shah', fontName='Times-Bold', fontSize=10,
leading=14.5, alignment=TA_LEFT, textColor=DARK_TEAL, spaceAfter=3, leftIndent=24)
style_cfo = ParagraphStyle('CFO', fontName='Times-Bold', fontSize=10,
leading=14.5, alignment=TA_LEFT, textColor=GOLD, spaceAfter=3, leftIndent=24)
style_lie = ParagraphStyle('Lie', fontName='Times-Bold', fontSize=10.5,
leading=15, alignment=TA_LEFT, textColor=HexColor('#C0392B'), spaceAfter=6, spaceBefore=10,
backColor=HexColor('#FDF2F2'), borderColor=HexColor('#C0392B'), borderWidth=1, borderPadding=10)
style_protocol = ParagraphStyle('Protocol', fontName='Times-Roman', fontSize=10,
leading=14.5, alignment=TA_LEFT, textColor=DARK, spaceAfter=6, leftIndent=36)
style_reflection = ParagraphStyle('Reflection', fontName='Times-Italic', fontSize=10.5,
leading=15, alignment=TA_CENTER, textColor=GREY, spaceAfter=16, spaceBefore=16,
backColor=HexColor('#F8F9FA'), borderColor=GREY, borderWidth=0.5, borderPadding=12)
style_final = ParagraphStyle('Final', fontName='Times-Italic', fontSize=10,
leading=14, alignment=TA_LEFT, textColor=GREY, spaceAfter=4, leftIndent=24)
style_toc_ch = ParagraphStyle('TOCCh', fontName='Helvetica-Bold', fontSize=11,
leading=17, alignment=TA_LEFT, textColor=DARK_TEAL)
style_toc_sub = ParagraphStyle('TOCSub', fontName='Helvetica', fontSize=9.5,
leading=13, alignment=TA_LEFT, textColor=GREY)
style_hook = ParagraphStyle('Hook', fontName='Times-Italic', fontSize=10.5,
leading=15, alignment=TA_LEFT, textColor=DARK, spaceAfter=10, spaceBefore=6,
backColor=HexColor('#EBF5FB'), borderColor=DARK_TEAL, borderWidth=1, borderPadding=10)
CHAPTERS = [
("The Man Who Watched the Shadow", "Attention Economy, Doomscrolling, and the Shadow You Feed", "Attention Quadrant: Consumption / Curation / Creation / Contemplation"),
("Nasrudin's Lost Key", "Looking Where the Light Is — Data Bias, Streetlight Effect, and the Answers You'll Never Find", "Epistemic Quadrant: Known Knowns / Known Unknowns / Unknown Knowns / Unknown Unknowns"),
("The Man Who Ate the Menu", "Consuming Content vs. Digesting Wisdom — Information Obesity, Malnourished Minds", "Consumption Quadrant: Input / Filter / Digest / Apply"),
("The Commanding Self", "Social Media Ego, Performative Identity, and the Tyrant Inside", "Identity Quadrant: Persona / Shadow / Essence / Witness"),
("Learning How to Unlearn", "AI Era: Unlearning > Learning — Cognitive Flexibility as Survival Skill", "Unlearning Loop: Identify Assumption → Test → Discard → Replace → Test Again"),
("The Assumption of Knowledge", "Dunning-Kruger in the TikTok Era — The Illusion of Competence", "Competence Quadrant: Unconscious Incompetence → Conscious Incompetence → Conscious Competence → Unconscious Competence + Shah's Fifth"),
("The Teacher Who Refused to Teach", "Mentorship Crisis, Credentialism, and the Parrot Who Quoted Rumi", "Transmission Loop: Embody → Transmit → Verify → Release"),
("The Parrot and the Scholar", "Mimicry vs. Understanding — LLMs, Cargo Cult Science, and the Illusion of Intelligence", "Understanding Ladder: Pattern Match → Compress → Predict → Generate → Embody → Transmit"),
("The Dream That Woke the Dreamer", "Purpose, Midlife Crisis, and the Nightmare of a Life Not Lived", "Purpose Loop: Disorient → Question → Experiment → Integrate → Question Again"),
("The Story That Has No Ending", "Legacy, Algorithmic Immortality, and the Art of Leaving Incomplete", "Legacy Loop: Create → Release → Let Go → Trust → Begin Again"),
]
LIGHT_GREY = HexColor('#f0f0f0')
DARK_GREY = HexColor('#333333')
def clean_text(text):
text = text.replace('&', '&').replace('<', '<').replace('>', '>')
parts = text.split('**')
for i in range(1, len(parts), 2):
parts[i] = f'<b>{parts[i]}</b>'
text = ''.join(parts)
parts = text.split('*')
for i in range(1, len(parts), 2):
parts[i] = f'<i>{parts[i]}</i>'
text = ''.join(parts)
return text
def parse_chapter(md_text):
lines = md_text.split('\n')
sections = {'hook': [], 'dialogue': [], 'framework': [], 'lie': [], 'protocol': [], 'final': [], 'reflection': []}
current = None
for line in lines:
stripped = line.strip()
if not stripped:
continue
# Detect section headers
low = stripped.lower()
if low.startswith('# hook') or low.startswith('## hook') or low == 'hook':
current = 'hook'
continue
elif low.startswith('# shah/cfo') or low.startswith('## shah/cfo') or low.startswith('shah/cfo') or low.startswith('dialogue'):
current = 'dialogue'
continue
elif low.startswith('# framework') or low.startswith('## framework') or low.startswith('framework'):
current = 'framework'
continue
elif low.startswith('# the lie') or low.startswith('## the lie') or low.startswith('the lie'):
current = 'lie'
continue
elif low.startswith('# the protocol') or low.startswith('## the protocol') or low.startswith('protocol'):
current = 'protocol'
continue
elif low.startswith('# shah/cfo final') or low.startswith('## shah/cfo final') or low.startswith('final word') or 'final word' in low:
current = 'final'
continue
elif low.startswith('# reflection') or low.startswith('## reflection') or low.startswith('reflection'):
current = 'reflection'
continue
if stripped.startswith('#'):
continue
if stripped == '---' or stripped == '***':
continue
if current:
sections[current].append(stripped)
for k in sections:
sections[k] = '\n\n'.join(sections[k])
return sections
class BookDoc(BaseDocTemplate):
def __init__(self, *args, **kw):
BaseDocTemplate.__init__(self, *args, **kw)
self.page_count = 0
def footer_fn(canvas, doc):
canvas.saveState()
canvas.setFont('Helvetica', 8)
canvas.setFillColor(HexColor('#999999'))
canvas.drawCentredString(PAGE_W / 2, 0.35 * inch, f'— {doc.page} —')
canvas.restoreState()
def build_pdf():
os.makedirs(os.path.join(OUTPUT_DIR, 'pdf'), exist_ok=True)
frame = Frame(MARGIN, MARGIN + FOOTER_H,
PAGE_W - 2 * MARGIN,
PAGE_H - 2 * MARGIN - FOOTER_H, id='normal')
doc = BookDoc(PDF_PATH, pagesize=(PAGE_W, PAGE_H),
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="The Sufi Who Fooled the Algorithm",
author='Jauhari Che Wan')
doc.addPageTemplates([PageTemplate(id='main', frames=frame, onPage=footer_fn)])
story = []
# Title page
story.append(Spacer(1, PAGE_H * 0.18))
story.append(Paragraph("The Sufi Who Fooled the Algorithm", style_title))
story.append(Paragraph("Idries Shah for the Attention Age", style_subtitle))
story.append(Paragraph("10 Teaching Stories Reimagined for AI, Anxiety, and the Art of Not Being Fooled", ParagraphStyle('Tagline',
fontName='Helvetica-Oblique', fontSize=12, leading=15, alignment=TA_CENTER, textColor=HexColor('#666666'), spaceAfter=24)))
story.append(Spacer(1, 14))
story.append(Paragraph('Kiyosaki-Style × Shah\'s Teaching Stories × Achebe Oral Tradition', ParagraphStyle('StyleLine',
fontName='Helvetica', fontSize=10, leading=13, alignment=TA_CENTER, textColor=HexColor('#888888'), spaceAfter=20)))
story.append(Spacer(1, 14))
story.append(Paragraph('Jauhari Che Wan', style_author))
story.append(Spacer(1, PAGE_H * 0.1))
story.append(Paragraph('SHAH × KIYOSAKI × ACHEBE',
ParagraphStyle('Tag', parent=style_copyright, fontName='Helvetica-Bold', fontSize=9, textColor=GOLD)))
story.append(PageBreak())
# Copyright
story.append(Spacer(1, PAGE_H * 0.28))
for line in ['SHAH × KIYOSAKI × ACHEBE', '', 'The Sufi Who Fooled the Algorithm', 'Jauhari Che Wan',
'', 'Based on Idries Shah\'s Teaching Stories', 'and Robert Kiyosaki\'s Rich Dad Framework',
'', 'Copyright \u00a9 2025', 'All rights reserved',
'', 'Typeset in Times / Helvetica', 'Printed in Malaysia']:
if line == '':
story.append(Spacer(1, 4))
else:
story.append(Paragraph(line, style_copyright))
story.append(PageBreak())
# Dedication
story.append(Spacer(1, PAGE_H * 0.28))
story.append(Paragraph('For every mind that\'s ever been fooled by its own shadow<br/>and had the courage to stop running.', style_dedication := ParagraphStyle('Ded', fontName='Times-Italic', fontSize=11, leading=16, alignment=TA_CENTER, textColor=DARK, spaceAfter=24)))
story.append(PageBreak())
# TOC
story.append(Paragraph('Contents', ParagraphStyle('TOCHeader',
fontName='Helvetica-Bold', fontSize=18, leading=24, alignment=TA_LEFT, textColor=DARK_TEAL, spaceAfter=20)))
for i, (title, subtitle, _) in enumerate(CHAPTERS, 1):
story.append(Paragraph(f'<b>Chapter {i}</b>: {title}', style_toc_ch))
story.append(Paragraph(f' <i>{subtitle}</i>', style_toc_sub))
story.append(Spacer(1, 4))
story.append(PageBreak())
# Chapters
for ch_num, (title, subtitle, framework) in enumerate(CHAPTERS, 1):
fname = os.path.join(CHAPTERS_DIR, f"Chapter_0{ch_num}.md") if ch_num < 10 else os.path.join(CHAPTERS_DIR, f"Chapter_{ch_num}.md")
if not os.path.exists(fname):
print(f" WARNING: {fname} not found")
continue
with open(fname, 'r', encoding='utf-8') as f:
md = f.read()
print(f" Adding Chapter {ch_num}: {title}...")
sections = parse_chapter(md)
# Chapter header
story.append(Paragraph(f'Chapter {ch_num}', style_ch_num))
story.append(Paragraph(title, style_ch_title))
story.append(Paragraph(subtitle, style_subtitle_ch))
story.append(Spacer(1, 10))
# HOOK
if sections['hook']:
hook_text = clean_text(' '.join(sections['hook'].split('\n')))
story.append(Paragraph(hook_text, style_hook))
story.append(Spacer(1, 6))
# SHAH/CFO DIALOGUE
if sections['dialogue']:
for para in sections['dialogue'].split('\n\n'):
clean = clean_text(para.strip())
if not clean:
continue
low = clean.lower()
if low.startswith('shah:'):
story.append(Paragraph(f'<b>SHAH:</b> {clean[5:].strip()}', style_shah))
elif low.startswith('cfo:'):
story.append(Paragraph(f'<b>CFO:</b> {clean[4:].strip()}', style_cfo))
else:
story.append(Paragraph(clean, style_dialogue))
story.append(Spacer(1, 6))
# FRAMEWORK
if sections['framework']:
story.append(Paragraph(f'FRAMEWORK: {framework}', style_framework))
story.append(Spacer(1, 4))
for para in sections['framework'].split('\n\n'):
clean = clean_text(para.strip())
if clean:
story.append(Paragraph(clean, style_body))
story.append(Spacer(1, 6))
# THE LIE
if sections['lie']:
lie_lines = sections['lie'].split('\n')
lie_title = lie_lines[0] if lie_lines else "THE LIE WE'RE TOLD"
story.append(Paragraph(lie_title, style_lie))
lie_body = '\n'.join(lie_lines[1:]) if len(lie_lines) > 1 else ''
for para in lie_body.split('\n\n'):
clean = clean_text(para.strip())
if clean:
story.append(Paragraph(clean, style_body))
story.append(Spacer(1, 6))
# PROTOCOL
if sections['protocol']:
story.append(Paragraph('THE PROTOCOL — Do This This Week', ParagraphStyle('ProtoHeader',
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=DARK_TEAL, spaceAfter=6, spaceBefore=10)))
for i, para in enumerate(sections['protocol'].split('\n\n'), 1):
clean = clean_text(para.strip())
if clean:
if clean.lower().startswith(f'step {i}') or clean.lower().startswith(f'step {i}:'):
clean = clean.split(':', 1)[-1].strip()
story.append(Paragraph(f'<b>STEP {i}:</b> {clean}', style_protocol))
story.append(Spacer(1, 6))
# SHAH/CFO FINAL WORD
if sections['final']:
for para in sections['final'].split('\n\n'):
clean = clean_text(para.strip())
low = clean.lower()
if low.startswith('shah:'):
story.append(Paragraph(f'<b>SHAH:</b> {clean[5:].strip()}', style_shah))
elif low.startswith('cfo:'):
story.append(Paragraph(f'<b>CFO:</b> {clean[4:].strip()}', style_cfo))
else:
story.append(Paragraph(clean, style_final))
story.append(Spacer(1, 6))
# REFLECTION
if sections['reflection']:
clean = clean_text(' '.join(sections['reflection'].split('\n')))
story.append(Paragraph(clean, style_reflection))
story.append(PageBreak())
print('Building PDF...')
doc.build(story)
print(f'Done: {PDF_PATH}')
# Stats
import subprocess
result = subprocess.run(['wc', '-c', PDF_PATH], capture_output=True, text=True)
size_kb = int(result.stdout.split()[0]) / 1024
with open(PDF_PATH, 'rb') as f:
data = f.read()
pages = len(re.findall(rb'/Type\s*/Page[^s]', data))
print(f'Pages: {pages}, Size: {size_kb:.0f} KB')
if __name__ == '__main__':
build_pdf()