114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""CHECK for recoveries-index.csv / recoveries-index.md (Extract lane, ledger).
|
|
|
|
Independent re-derivation:
|
|
- file list: subprocess `find recoveries -name '*.md' -not -path '*/.git/*'` (sorted)
|
|
- bytes: `wc -c` per file
|
|
- lines: `wc -l` per file
|
|
- headings: re.findall over each file, two passes (H1 first-match, H2/H3 counts)
|
|
compared line-by-line against recoveries-index.csv. Exits 0 iff every cell matches.
|
|
"""
|
|
import csv
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
BASE = "/root/troupe-standup/recoveries"
|
|
CSV_PATH = "/root/troupe-standup/wizard-work/ledger/recoveries-index.csv"
|
|
|
|
def sh(args):
|
|
return subprocess.run(args, check=True, capture_output=True, text=True).stdout
|
|
|
|
def find_files():
|
|
out = sh(["find", BASE, "-name", "*.md", "-not", "-path", "*/.git/*"]).splitlines()
|
|
return sorted(os.path.relpath(os.path.join(root, f), BASE) for root, _, files in os.walk(BASE) for f in files if f.endswith(".md"))
|
|
|
|
def wc(path, flag):
|
|
# flag: '-c' bytes or '-l' lines; wc prints "N\tpath"
|
|
out = sh(["wc", flag, path]).splitlines()
|
|
return int(out[0].split()[0])
|
|
|
|
def heading_counts(text):
|
|
h1first = ""
|
|
h2 = h3 = 0
|
|
for raw in text.split("\n"):
|
|
s = raw.rstrip("\n")
|
|
if not s.startswith("#"):
|
|
continue
|
|
txt = s.lstrip("#").strip()
|
|
if txt == "":
|
|
continue
|
|
lvl = len(s) - len(s.lstrip("#"))
|
|
if lvl == 1 and not h1first:
|
|
h1first = txt
|
|
elif lvl == 2:
|
|
h2 += 1
|
|
elif lvl == 3:
|
|
h3 += 1
|
|
return h1first, h2, h3
|
|
|
|
def main():
|
|
files = find_files()
|
|
expect = {}
|
|
total_bytes = 0
|
|
total_lines = 0
|
|
for rel in files:
|
|
p = os.path.join(BASE, rel)
|
|
b = wc(p, "-c")
|
|
l = wc(p, "-l")
|
|
text = open(p, encoding="utf-8", errors="replace").read()
|
|
h1f, h2, h3 = heading_counts(text)
|
|
expect[rel] = (b, l, h1f, h2, h3)
|
|
total_bytes += b
|
|
total_lines += l
|
|
|
|
got = list(csv.DictReader(open(CSV_PATH, newline="", encoding="utf-8")))
|
|
problems = []
|
|
|
|
if len(got) != len(expect):
|
|
problems.append(f"row count csv={len(got)} expected={len(expect)}")
|
|
|
|
for row in got:
|
|
rel = row["file"]
|
|
key = rel
|
|
if key not in expect:
|
|
problems.append(f"csv has unexpected file {key!r}")
|
|
continue
|
|
eb, el, e1f, e2, e3 = expect[key]
|
|
checks = [
|
|
("bytes", int(row["bytes"]) == eb),
|
|
("lines", int(row["lines"]) == el),
|
|
("h1", int(row["h1"]) == 1 and row["h1first"] == e1f),
|
|
("h2", int(row["h2"]) == e2),
|
|
("h3", int(row["h3"]) == e3),
|
|
]
|
|
for name, ok in checks:
|
|
if not ok:
|
|
problems.append(f"{rel}: {name} csv={row.get(name)} expected={ {'bytes':eb,'lines':el,'h2':e2,'h3':e3}.get(name) if name!='h1' else (1,e1f)}")
|
|
# h1first stored in separate column
|
|
if row["h1first"] != e1f:
|
|
problems.append(f"{rel}: h1first mismatch {row['h1first']!r} != {e1f!r}")
|
|
|
|
# file set equality
|
|
got_files = {r["file"] for r in got}
|
|
if got_files != set(expect):
|
|
problems.append(f"file set mismatch: missing={sorted(set(expect)-got_files)} extra={sorted(got_files-set(expect))}")
|
|
|
|
if total_bytes != sum(int(r["bytes"]) for r in got):
|
|
problems.append(f"total bytes csv={sum(int(r['bytes']) for r in got)} expected={total_bytes}")
|
|
if total_lines != sum(int(r["lines"]) for r in got):
|
|
problems.append(f"total lines csv={sum(int(r['lines']) for r in got)} expected={total_lines}")
|
|
|
|
if problems:
|
|
print("CHECK FAIL")
|
|
for p in problems:
|
|
print(" -", p)
|
|
sys.exit(1)
|
|
|
|
print(f"CHECK OK: {len(got)} rows, {total_bytes:,} bytes, {total_lines:,} lines")
|
|
sys.exit(0)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|