# -*- coding: utf-8 -*- """Assemble the draft sections into one paper, resolving generated tables. python assemble.py writes draft/paper.md Sections are `draft/sNN-*.md`, in filename order. A line containing only `{{table:name}}` is replaced by the contents of `draft/tables/name.md`, which is written by `tables.py` straight from the session logs. That indirection is the point. The recurring failure in this programme is not arithmetic, it is a correct number printed on the wrong page: three earlier papers shipped with another paper's cover statistics and every audit passed, because each number was right somewhere. A number that cannot be typed into the prose cannot be typed into the wrong prose. Two gates, both fatal: * an unresolved `{{table:...}}` marker, so a renamed table cannot silently leave a hole; * a house-rule violation: an em dash, an en dash, a spaced hyphen used as punctuation, or an emoji. """ import glob import io import os import re import sys HERE = os.path.dirname(os.path.abspath(__file__)) DRAFT = os.path.abspath(os.path.join(HERE, '..', 'draft')) TABLES = os.path.join(DRAFT, 'tables') OUT = os.path.join(DRAFT, 'paper.md') MARKER = re.compile(r'^\{\{table:([a-z0-9\-]+)\}\}\s*$', re.M) BANNED = { '—': 'em dash', '–': 'en dash', '‒': 'figure dash', '−': 'minus sign used as a dash', } EMOJI = re.compile('[\U0001F300-\U0001FAFF☀-➿️]') def resolve(body, missing): def sub(m): name = m.group(1) path = os.path.join(TABLES, name + '.md') if not os.path.exists(path): missing.append(name) return m.group(0) with io.open(path, encoding='utf-8') as f: return f.read().rstrip() + '\n' return MARKER.sub(sub, body) def house_rules(text): problems = [] for ch, label in BANNED.items(): n = text.count(ch) if n: problems.append('%d %s(s)' % (n, label)) spaced = len(re.findall(r'\S \- \S', text)) if spaced: problems.append('%d spaced hyphen(s) used as punctuation' % spaced) emo = EMOJI.findall(text) if emo: problems.append('%d emoji' % len(emo)) return problems def main(): sections = sorted(glob.glob(os.path.join(DRAFT, 's[0-9][0-9]-*.md'))) if not sections: print('no sections in %s' % DRAFT) return 1 missing = [] parts = [] for path in sections: with io.open(path, encoding='utf-8') as f: parts.append(resolve(f.read().rstrip(), missing)) text = '\n\n---\n\n'.join(parts) + '\n' problems = house_rules(text) if missing: problems.append('unresolved table marker(s): %s' % ', '.join(sorted(set(missing)))) print('assembled %d section(s), %d words' % (len(sections), len(text.split()))) for p in problems: print(' !!', p) if problems: print('refusing to write paper.md') return 1 with io.open(OUT, 'w', encoding='utf-8') as f: f.write(text) print('wrote %s' % OUT) return 0 if __name__ == '__main__': raise SystemExit(main())