#!/usr/bin/env python3 """Generate the decision index, and check the written one still matches. A number identifies a record and never changes, so the folder listing is creation order rather than reading order. The index is what carries the path — and it is written rather than only generated on demand, because a reader on a forge sees the folder and not a command. The objection to a written index is that it drifts. That objection is answered by checking it rather than by refusing to write one, which is `how-we-build` §5: a rule states how it is checked. python3 00-META/checks/index.py --write regenerate it python3 00-META/checks/index.py fail if it is stale """ import glob import io import os import re import sys README = "02-DECISIONS/README.md" START = "" END = "" # The reading order. Topics a record may belong to, in the order somebody would learn the system. TOPICS = [ ("the mesh", "What the mesh is"), ("the tiers", "Its tiers, from the bottom up"), ("what runs on it", "What runs on them, and how it gets there"), ("building it", "How it is built"), ("checking it", "How it is checked"), ("how we work", "How we work"), ] def field(text, name): m = re.search(r'^%s:\s*(.+)$' % name, text, re.M) return m.group(1).strip() if m else None def records(): out = [] for path in sorted(glob.glob('02-DECISIONS/0*.md')): text = io.open(path, encoding='utf-8').read() heading = re.search(r'^# \d+\.\s*(.+)$', text, re.M) out.append({ "file": os.path.basename(path), "number": os.path.basename(path)[:4], "title": heading.group(1).strip() if heading else "(no heading)", "topic": field(text, "topic"), "status": field(text, "status"), }) return out def render(rs): known = {t for t, _ in TOPICS} lines = [START, ""] for topic, label in TOPICS: rows = [r for r in rs if r["topic"] == topic] if not rows: continue lines.append("### %s" % label) lines.append("") for r in rows: mark = "" if r["status"] == "accepted" else " *(%s)*" % r["status"] lines.append("- **%s** — [%s](%s)%s" % (r["number"], r["title"], r["file"], mark)) lines.append("") stray = [r for r in rs if r["topic"] not in known] if stray: lines.append("### Unfiled") lines.append("") for r in stray: lines.append("- **%s** — [%s](%s) — `topic:` is %r, which is not one of %s" % ( r["number"], r["title"], r["file"], r["topic"], ", ".join(sorted(known)))) lines.append("") lines.append(END) return "\n".join(lines) def main(): text = io.open(README, encoding='utf-8').read() wanted = render(records()) if START not in text or END not in text: print("index: %s has no index markers (%s / %s)" % (README, START, END)) return 1 current = text[text.index(START):text.index(END) + len(END)] if "--write" in sys.argv: if current == wanted: print("index: already current") return 0 io.open(README, 'w', encoding='utf-8').write(text.replace(current, wanted, 1)) print("index: written") return 0 if current != wanted: print("index: %s is stale. Regenerate it:\n" " python3 00-META/checks/index.py --write" % README) return 1 print("index: current") return 0 if __name__ == "__main__": sys.exit(main())