318 lines
16 KiB
Python
318 lines
16 KiB
Python
#!/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() |