"""Snapshot the playtest tracker sheet into the repo so tester edits are a git diff. python tools/tracker_snapshot.py # write docs/tracker/{OPEN,CLOSED}.csv python tools/tracker_snapshot.py --diff # show what changed vs the committed snapshot Rows are written sorted by TICKET NUMBER, not sheet order, so re-prioritising the sheet produces no diff noise -- only real content changes show up. CREDENTIALS ARE NEVER STORED HERE. The bridge URL + token together grant write access to a live shared document, so they come from the environment: set BT_TRACKER_URL=https://script.google.com/macros/s/..../exec set BT_TRACKER_TOKEN=.... or from an untracked file `scratchpad/tracker_bridge.local` holding those two lines as KEY=VALUE. See context/playtest-tracker.md. """ import csv import io import json import os import subprocess import sys import urllib.request REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) OUTDIR = os.path.join(REPO, "docs", "tracker") LOCAL = os.path.join(REPO, "scratchpad", "tracker_bridge.local") TABS = { "OPEN": ["#", "Priority", "Test Status", "Dev Status", "Dev Comments", "Tester Comments"], "CLOSED": ["#", "Test Status", "Dev Status", "Dev Comments", "Tester Comments"], } def creds(): url = os.environ.get("BT_TRACKER_URL") tok = os.environ.get("BT_TRACKER_TOKEN") if (not url or not tok) and os.path.exists(LOCAL): for line in io.open(LOCAL, encoding="utf-8"): if "=" in line: k, v = line.split("=", 1) k, v = k.strip(), v.strip() if k == "BT_TRACKER_URL" and not url: url = v if k == "BT_TRACKER_TOKEN" and not tok: tok = v if not url or not tok: sys.exit("no bridge credentials -- set BT_TRACKER_URL / BT_TRACKER_TOKEN, or create " "scratchpad/tracker_bridge.local (untracked). See context/playtest-tracker.md.") return url, tok def fetch(url, tok, tab, ncols): body = json.dumps({"token": tok, "action": "get", "sheet": tab, "range": "A1:%s300" % chr(ord('A') + ncols - 1)}).encode() req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req) as r: out = json.load(r) if not out.get("ok"): sys.exit("bridge error: %s" % out) rows = [] for raw in out["values"][1:]: row = [str(x).strip() for x in raw] + [""] * ncols if row[0]: rows.append(row[:ncols]) rows.sort(key=lambda r: (0, int(r[0])) if r[0].isdigit() else (1, 0)) return rows def write(tab, header, rows): if not os.path.isdir(OUTDIR): os.makedirs(OUTDIR) path = os.path.join(OUTDIR, tab + ".csv") with io.open(path, "w", encoding="utf-8", newline="") as f: w = csv.writer(f, lineterminator="\n") w.writerow(header) w.writerows(rows) return path def main(): url, tok = creds() if "--diff" in sys.argv: for tab, header in TABS.items(): write(tab, header, fetch(url, tok, tab, len(header))) print(subprocess.run(["git", "diff", "--stat", "--", "docs/tracker"], cwd=REPO, capture_output=True, text=True).stdout or "(no changes)") print(subprocess.run(["git", "diff", "--", "docs/tracker"], cwd=REPO, capture_output=True, text=True).stdout[:8000]) return for tab, header in TABS.items(): rows = fetch(url, tok, tab, len(header)) print("%-7s %3d rows -> %s" % (tab, len(rows), write(tab, header, rows))) if __name__ == "__main__": main()