Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
3.7 KiB
Python
100 lines
3.7 KiB
Python
"""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()
|