162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the Stackchain Library drops catalog from the source folder.
|
|
|
|
Source: /root/stackchain-library-updates/drops (42 top-level entries:
|
|
39 day-drop folders, one probe JSON, and two global checksum files).
|
|
|
|
One row per entry with a recursive file-type breakdown. Idempotent:
|
|
re-running re-derives the same numbers from the same files.
|
|
|
|
Artifacts written to BOTH:
|
|
- /root/.hermes/profiles/ledger/wizard-outputs/ledger/ (profile outputs)
|
|
- /root/troupe-standup/wizard-work/ledger/ (troupe repo)
|
|
"""
|
|
import csv
|
|
import os
|
|
from collections import Counter
|
|
|
|
DROPS = "/root/stackchain-library-updates/drops"
|
|
OUT_JSON = None # not used here
|
|
OUT_CSV = "drops-catalog.csv"
|
|
OUT_MD = "drops-catalog.md"
|
|
|
|
TARGETS = (
|
|
"/root/.hermes/profiles/ledger/wizard-outputs/ledger",
|
|
"/root/troupe-standup/wizard-work/ledger",
|
|
)
|
|
|
|
# Explicit top-level entries that are files, not folders.
|
|
FILE_ENTRIES = (
|
|
"2026-10-23-probe-blocks.json",
|
|
"checksums.sha256",
|
|
"checksums.sha256.NOTES.md",
|
|
)
|
|
|
|
|
|
def classify(name):
|
|
"""Return a category key for a filename."""
|
|
low = name.lower()
|
|
if low.endswith(".mp4"):
|
|
return "mp4"
|
|
if low.endswith(".json"):
|
|
return "json"
|
|
if low.endswith(".txt"):
|
|
return "txt"
|
|
if low.endswith(".md"):
|
|
return "md"
|
|
if low.endswith((".jpg", ".jpeg", ".png", ".gif", ".webp", ".webm")):
|
|
return "jpg"
|
|
return "other"
|
|
|
|
|
|
def catalog_entry(path):
|
|
is_file = os.path.isfile(path)
|
|
if is_file:
|
|
try:
|
|
size = os.path.getsize(path)
|
|
except OSError:
|
|
size = 0
|
|
return {
|
|
"path": os.path.basename(path),
|
|
"kind": "file",
|
|
"total_files": 1,
|
|
"mp4": 0, "json": 0, "txt": 0, "md": 0, "jpg": 0, "other": 0,
|
|
"size_bytes": size,
|
|
}
|
|
total = 0
|
|
counts = Counter()
|
|
size = 0
|
|
for root, _dirs, files in os.walk(path):
|
|
for fn in files:
|
|
fp = os.path.join(root, fn)
|
|
total += 1
|
|
counts[classify(fn)] += 1
|
|
try:
|
|
size += os.path.getsize(fp)
|
|
except OSError:
|
|
pass
|
|
return {
|
|
"path": os.path.basename(path.rstrip("/")),
|
|
"kind": "dir",
|
|
"total_files": total,
|
|
"mp4": counts["mp4"],
|
|
"json": counts["json"],
|
|
"txt": counts["txt"],
|
|
"md": counts["md"],
|
|
"jpg": counts["jpg"],
|
|
"other": counts["other"],
|
|
"size_bytes": size,
|
|
}
|
|
|
|
|
|
def build_rows():
|
|
rows = []
|
|
if os.path.isdir(DROPS):
|
|
for name in sorted(os.listdir(DROPS)):
|
|
p = os.path.join(DROPS, name)
|
|
rows.append(catalog_entry(p))
|
|
for name in sorted(FILE_ENTRIES):
|
|
p = os.path.join(DROPS, name)
|
|
if os.path.isfile(p) and not any(r["path"] == name for r in rows):
|
|
rows.append(catalog_entry(p))
|
|
return rows
|
|
|
|
|
|
def write_csv(rows, target):
|
|
path = os.path.join(target, OUT_CSV)
|
|
with open(path, "w", encoding="utf-8", newline="") as f:
|
|
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
return path
|
|
|
|
|
|
def write_md(rows, target):
|
|
path = os.path.join(target, OUT_MD)
|
|
lines = []
|
|
lines.append("# Stackchain library drops catalog")
|
|
lines.append("")
|
|
lines.append("Source: `%s`. One row per top-level entry (39 day-drop folders, "
|
|
"one probe JSON, two checksum files). `md` counts reproduced "
|
|
"`MANIFEST.md` files." % DROPS)
|
|
lines.append("")
|
|
lines.append("| Path | Kind | Files | MP4 | JSON | TXT | MANIFEST(md) | JPG | Other | Size(B) |")
|
|
lines.append("|---|------|-------|-----|------|-----|--------------|-----|-----|---------|")
|
|
for r in rows:
|
|
lines.append(
|
|
"| `{path}` | {kind} | {total} | {mp4} | {json} | {txt} | {md} | {jpg} | "
|
|
"{other} | {size} |".format(
|
|
path=r["path"], kind=r["kind"], total=r["total_files"],
|
|
mp4=r["mp4"], json=r["json"], txt=r["txt"], md=r["md"],
|
|
jpg=r["jpg"], other=r["other"], size=r["size_bytes"]))
|
|
lines.append("")
|
|
totals = {k: sum(r[k] for r in rows) for k in
|
|
("total_files", "mp4", "json", "txt", "md", "jpg", "other", "size_bytes")}
|
|
lines.append("**Summary:** {n} entries · {files} total files · {md} with "
|
|
"MANIFEST.md · {mp4} mp4 · {json} json · {txt} txt · {jpg} jpg · "
|
|
"{other} other · {size} bytes.".format(
|
|
n=len(rows), files=totals["total_files"], md=totals["md"],
|
|
mp4=totals["mp4"], json=totals["json"], txt=totals["txt"],
|
|
jpg=totals["jpg"], other=totals["other"], size=totals["size_bytes"]))
|
|
lines.append("")
|
|
with open(path, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(lines))
|
|
return path
|
|
|
|
|
|
def main():
|
|
rows = build_rows()
|
|
for target in TARGETS:
|
|
os.makedirs(target, exist_ok=True)
|
|
csv_path = write_csv(rows, target)
|
|
md_path = write_md(rows, target)
|
|
print("wrote", csv_path)
|
|
print("wrote", md_path)
|
|
print("total entries:", len(rows))
|
|
print("by kind:", {k: sum(1 for r in rows if r["kind"] == k)
|
|
for k in ("dir", "file")})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|