#!/usr/bin/env python3 """tangle.py — Extract code blocks from .org files into .lisp files. Reads all .org files in org/ directory, finds #+BEGIN_SRC lisp :tangle blocks, and writes/concatenates them to the specified target paths. Blocks with the same :tangle target are concatenated in file order. Usage: python3 scripts/tangle.py # tangle all org/ files python3 scripts/tangle.py org/specific.org # tangle one file Target paths are relative to the project root (../target from org/ = project/target). """ import re import os import sys from collections import OrderedDict PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ORG_DIR = os.path.join(PROJECT_ROOT, 'org') def tangle_file(org_path): """Extract tangle blocks from one .org file.""" with open(org_path) as f: content = f.read() # Find all tangle blocks with their targets pattern = r'#\+BEGIN_SRC lisp :tangle ([^\n]+)\n(.*?)\n#\+END_SRC' blocks = re.findall(pattern, content, re.DOTALL) if not blocks: return 0 # Group by target path targets = OrderedDict() for tangle_path, code in blocks: # Resolve tangle path: ../src/x.lisp -> src/x.lisp resolved = tangle_path.replace('../', '') full_path = os.path.join(PROJECT_ROOT, resolved) if full_path not in targets: targets[full_path] = [] targets[full_path].append(code.strip()) for full_path, codes in targets.items(): os.makedirs(os.path.dirname(full_path), exist_ok=True) combined = '\n\n'.join(codes) + '\n' with open(full_path, 'w') as f: f.write(combined) print(f" {os.path.relpath(full_path, PROJECT_ROOT)} ({len(codes)} blocks, {sum(len(c) for c in codes)} chars)") return len(blocks) def main(): if len(sys.argv) > 1: org_files = [f for f in sys.argv[1:] if f.endswith('.org')] else: org_files = [os.path.join(ORG_DIR, f) for f in os.listdir(ORG_DIR) if f.endswith('.org')] total_blocks = 0 for org_file in sorted(org_files): name = os.path.basename(org_file) blocks = tangle_file(org_file) if blocks: print(f"{name}: {blocks} blocks") total_blocks += blocks if total_blocks > 0: print(f"\nTotal: {total_blocks} code blocks tangled") else: print("No tangle blocks found.") if __name__ == '__main__': main()