Files
cl-tty/scripts/tangle.py
Hermes Agent 0f408eeff7 Add CI test runner: run-all-tests.sh + verify-api.py + verify-demo-pty.py
Three-tier verification suite:
  - Tier 1: FiveAM unit tests (392 tests, 12 suites)
  - Tier 2: API feature verification (29 checks across 20 components)
  - Tier 3: PTY demo integration test (17 checks through real terminal)

Webhook subscription 'cl-tty-ci' configured to run on push.
Gitea repo webhook configured at amr/cl-tui → Hermes gateway.
2026-05-12 11:36:16 +00:00

75 lines
2.4 KiB
Python
Executable File

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