116 lines
4.3 KiB
Python
116 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""The reading order of the decision records: its topics are written, its list is generated.
|
|
|
|
A number identifies a record and never changes, so the folder listing is creation order rather
|
|
than reading order. The reading order is the six topics `02-DECISIONS/README.md` writes down, in
|
|
order, and each record's own `topic:`. The list of records under each topic is generated from
|
|
that frontmatter when somebody reads it, and never stored (ADR 0248, hq issue 297): a stored
|
|
list was a line every decision pull request added to one file, so any two open at once conflicted.
|
|
|
|
python3 00-META/checks/index.py check
|
|
python3 00-META/checks/index.py --print print the list, by topic, in reading order
|
|
|
|
It fails when the README's reading order is missing, when a record's topic is not one of its
|
|
topics, and when the README stores a list of records again — the index markers, or a line of
|
|
the generated list. The last one is what keeps the conflict from coming back.
|
|
"""
|
|
|
|
import glob
|
|
import io
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
README = "02-DECISIONS/README.md"
|
|
|
|
# A topic in the README's reading order, e.g.
|
|
# 1. **What the mesh is** — `topic: the mesh`. What it is for, ...
|
|
# The records module's `records_decisions` reads the same line, so the form is a contract.
|
|
TOPIC_LINE = re.compile(r"^\d+\.\s+\*\*(?P<label>[^*]+)\*\*\s+—\s+`topic:\s*(?P<topic>[^`]+)`", re.M)
|
|
|
|
# What a stored list looks like: the old markers, or a line of the list this script prints.
|
|
STORED = [
|
|
(re.compile(r"<!--\s*index:(start|end)\s*-->"), "an index marker"),
|
|
(re.compile(r"^- \*\*\d{4}\*\* — \[", re.M), "a line of the generated list"),
|
|
]
|
|
|
|
|
|
def reading_order(text=None):
|
|
"""The topics, in order, as (topic, label) pairs, from the README."""
|
|
if text is None:
|
|
text = io.open(README, encoding='utf-8').read()
|
|
return [(m.group("topic").strip(), m.group("label").strip()) for m in TOPIC_LINE.finditer(text)]
|
|
|
|
|
|
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(topics, rs):
|
|
lines = []
|
|
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("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
text = io.open(README, encoding='utf-8').read()
|
|
topics = reading_order(text)
|
|
rs = records()
|
|
|
|
if "--print" in sys.argv:
|
|
print(render(topics, rs))
|
|
return 0
|
|
|
|
problems = []
|
|
if not topics:
|
|
problems.append("%s writes no reading order: no line of the form "
|
|
"'1. **Label** — `topic: name`. ...'" % README)
|
|
names = [t for t, _ in topics]
|
|
if len(set(names)) != len(names):
|
|
problems.append("%s names a topic twice in its reading order" % README)
|
|
for pattern, what in STORED:
|
|
if pattern.search(text):
|
|
problems.append("%s stores the list of records (%s). The list is generated when read "
|
|
"(ADR 0248): `python3 00-META/checks/index.py --print`" % (README, what))
|
|
known = set(names)
|
|
for r in rs if known else []: # with no reading order, every record would be named; one line says why
|
|
if r["topic"] not in known:
|
|
problems.append("%s: topic %r is not one of the reading order's: %s"
|
|
% (r["file"], r["topic"], ", ".join(names)))
|
|
|
|
for p in problems:
|
|
print("index: " + p)
|
|
if problems:
|
|
return 1
|
|
print("index: %d topics, %d records, no stored list" % (len(topics), len(rs)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|