63 lines
2.5 KiB
Python
63 lines
2.5 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""Contract excerpt for a worker on a cross-repo change (NK-WP-0058-T08). Read-only.
|
|||
|
|
|
|||
|
|
python3 tools/contract-tests/excerpt.py E1 C5 O1 # rows of docs/bounded-execution-contract-table.md
|
|||
|
|
|
|||
|
|
Prints, for each row: its line in the table (owner and source of truth, consumer, mark), its detail sections (names,
|
|||
|
|
paths, roles, payloads, exit codes, and the one owning script or file per step), and the contract tests that cover
|
|||
|
|
it. A worker gets this excerpt before starting and checks its result with those tests and `make contract-test`.
|
|||
|
|
An unknown row fails: a new interface gets its row in the table first.
|
|||
|
|
"""
|
|||
|
|
import re
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
TABLE = Path(__file__).resolve().parents[2] / 'docs/bounded-execution-contract-table.md'
|
|||
|
|
ROW = re.compile(r'^[EROCF]\d+$')
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ids_in(text):
|
|||
|
|
"""Row ids named in a cell or heading, with ranges like O2–O4 expanded."""
|
|||
|
|
found = set()
|
|||
|
|
for first, last in re.findall(r'\b([EROCF]\d+)\s*[–-]\s*([EROCF]\d+)\b', text):
|
|||
|
|
if first[0] == last[0]:
|
|||
|
|
found |= {f'{first[0]}{n}' for n in range(int(first[1:]), int(last[1:]) + 1)}
|
|||
|
|
return found | set(re.findall(r'\b[EROCF]\d+\b', text))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def sections(text):
|
|||
|
|
"""(heading, body) for each ### section."""
|
|||
|
|
parts = re.split(r'^(### .+)$', text, flags=re.M)
|
|||
|
|
return [(parts[i], parts[i + 1].split('\n## ')[0]) for i in range(1, len(parts) - 1, 2)]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def excerpt(row_ids, text=None):
|
|||
|
|
text = text if text is not None else TABLE.read_text()
|
|||
|
|
out = []
|
|||
|
|
for row in row_ids:
|
|||
|
|
if not ROW.match(row):
|
|||
|
|
raise SystemExit(f'not a row id: {row}')
|
|||
|
|
line = next((l for l in text.splitlines() if l.startswith(f'| {row} |')), None)
|
|||
|
|
if line is None:
|
|||
|
|
raise SystemExit(f'{row}: no such row; add the interface to the table first')
|
|||
|
|
out += [f'## {row}', '', line, '']
|
|||
|
|
for heading, body in sections(text):
|
|||
|
|
if row in ids_in(heading.split(':')[0]):
|
|||
|
|
out += [heading, body.strip(), '']
|
|||
|
|
coverage = [l for l in text[text.index('### Coverage by row'):].splitlines()
|
|||
|
|
if l.startswith('| ') and not l.startswith('| Row') and row in ids_in(l.split('|')[1])]
|
|||
|
|
out += ['**Contract tests:**', *(coverage or ['none recorded: add one with the change']), '']
|
|||
|
|
return '\n'.join(out)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main(argv=None):
|
|||
|
|
rows = argv if argv is not None else sys.argv[1:]
|
|||
|
|
if not rows:
|
|||
|
|
raise SystemExit(__doc__)
|
|||
|
|
print(excerpt(rows))
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
sys.exit(main())
|