tools (volt): checkin_check -- adds --since so the gate can be adopted forward on a dirty history; 26 cells
This commit is contained in:
1 parent
588784d7ea
commit
edaef631e5
1 file changed
+38
-5
+38
-5
@@ -66,7 +66,7 @@ USAGE:
|
|||||||
python3 checkin_check.py --repo /root/troupe-standup
|
python3 checkin_check.py --repo /root/troupe-standup
|
||||||
python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json
|
python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json
|
||||||
python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18
|
python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18
|
||||||
python3 checkin_check.py --selftest # 24 cells, names, dates, roster, refusals
|
python3 checkin_check.py --selftest # 26 cells, names, dates, roster, refusals
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -293,7 +293,8 @@ def banner(text: str) -> str:
|
|||||||
return f"== {text} =="
|
return f"== {text} =="
|
||||||
|
|
||||||
|
|
||||||
def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False):
|
def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False,
|
||||||
|
since: str | None = None):
|
||||||
"""Everything except printing the file table. Returns the report dict."""
|
"""Everything except printing the file table. Returns the report dict."""
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
raise CannotGrade(f"cannot read checkins dir {path}: not a directory")
|
raise CannotGrade(f"cannot read checkins dir {path}: not a directory")
|
||||||
@@ -305,7 +306,19 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False)
|
|||||||
if n.endswith(".md") and n not in SKIP_NAMES
|
if n.endswith(".md") and n not in SKIP_NAMES
|
||||||
and os.path.isfile(os.path.join(path, n))]
|
and os.path.isfile(os.path.join(path, n))]
|
||||||
skipped = [n for n in names if n in SKIP_NAMES]
|
skipped = [n for n in names if n in SKIP_NAMES]
|
||||||
if not files:
|
# --since grades forward only: the archive's 25 historical names are the
|
||||||
|
# record, and a gate that can only ever be red is a gate nobody adopts.
|
||||||
|
excluded_since = 0
|
||||||
|
if since:
|
||||||
|
keep = []
|
||||||
|
for p in files:
|
||||||
|
m = AMBIENT_RE.match(os.path.basename(p))
|
||||||
|
if m and f"{m.group(1)}-{m.group(2)}-{m.group(3)}" >= since:
|
||||||
|
keep.append(p)
|
||||||
|
else:
|
||||||
|
excluded_since += 1
|
||||||
|
files = keep
|
||||||
|
if not files and not since:
|
||||||
raise CannotGrade(f"cannot read checkins dir {path}: no *.md files")
|
raise CannotGrade(f"cannot read checkins dir {path}: no *.md files")
|
||||||
|
|
||||||
rows = [grade_file(p, roster, today) for p in files]
|
rows = [grade_file(p, roster, today) for p in files]
|
||||||
@@ -326,6 +339,7 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False)
|
|||||||
rep = {
|
rep = {
|
||||||
"dir": path, "checked": len(files), "today": today.isoformat(),
|
"dir": path, "checked": len(files), "today": today.isoformat(),
|
||||||
"roster_size": len(roster), "skipped": skipped,
|
"roster_size": len(roster), "skipped": skipped,
|
||||||
|
"since": since, "excluded_since": excluded_since,
|
||||||
"faults": {c: sum(1 for r in rows if c in r["class"]) for c in FAULTS},
|
"faults": {c: sum(1 for r in rows if c in r["class"]) for c in FAULTS},
|
||||||
"fault_rows": len(fault_rows),
|
"fault_rows": len(fault_rows),
|
||||||
"duplicate_groups": [{"date": d, "handle": h, "files": fl}
|
"duplicate_groups": [{"date": d, "handle": h, "files": fl}
|
||||||
@@ -346,6 +360,9 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False)
|
|||||||
def print_report(rep, show_gaps: bool, fail: bool):
|
def print_report(rep, show_gaps: bool, fail: bool):
|
||||||
print(banner(f"{rep['checked']} check-in files in {rep['dir']}"))
|
print(banner(f"{rep['checked']} check-in files in {rep['dir']}"))
|
||||||
print(f"roster: {rep['roster_size']} handles; today {rep['today']}")
|
print(f"roster: {rep['roster_size']} handles; today {rep['today']}")
|
||||||
|
if rep.get("since"):
|
||||||
|
print(f"since {rep['since']}: {rep['checked']} graded, "
|
||||||
|
f"{rep['excluded_since']} older file(s) not graded")
|
||||||
for n in rep.get("skipped") or []:
|
for n in rep.get("skipped") or []:
|
||||||
print(f" SKIP {n} (the directory's own README: not a check-in)")
|
print(f" SKIP {n} (the directory's own README: not a check-in)")
|
||||||
fault_rows = [r for r in rep["rows"] if r["class"] or r["duplicate"]]
|
fault_rows = [r for r in rep["rows"] if r["class"] or r["duplicate"]]
|
||||||
@@ -524,8 +541,15 @@ def selftest():
|
|||||||
rep["skipped"] == ["README.md"]
|
rep["skipped"] == ["README.md"]
|
||||||
and "README.md" not in [r["file"] for r in rep["rows"]]
|
and "README.md" not in [r["file"] for r in rep["rows"]]
|
||||||
and rep["checked"] == 7, f"{rep['skipped']} {rep['checked']}")
|
and rep["checked"] == 7, f"{rep['skipped']} {rep['checked']}")
|
||||||
|
rep_since = run(repo, ck, roster, today, False, since="2026-10-16")
|
||||||
|
cell("c25 --since grades forward only, and says how many it left out",
|
||||||
|
rep_since["checked"] == 5 and rep_since["excluded_since"] == 3
|
||||||
|
and "2026-10-15-volt.md" not in [r["file"] for r in rep_since["rows"]],
|
||||||
|
f"{rep_since['checked']}/{rep_since['excluded_since']}")
|
||||||
|
cell("c26 a malformed --since refuses at rc 2, never 1",
|
||||||
|
main(["--dir", ck, "--roster", "volt", "--since", "16-10-2026"]) == 2)
|
||||||
|
|
||||||
print(f"selftest: 24 cells, {24 - len(bad)} passed, {len(bad)} failed")
|
print(f"selftest: 26 cells, {26 - len(bad)} passed, {len(bad)} failed")
|
||||||
return 1 if bad else 0
|
return 1 if bad else 0
|
||||||
|
|
||||||
|
|
||||||
@@ -540,6 +564,8 @@ def main(argv=None):
|
|||||||
ap.add_argument("--roster", help="comma-separated handles, instead of the roster files")
|
ap.add_argument("--roster", help="comma-separated handles, instead of the roster files")
|
||||||
ap.add_argument("--today", help="YYYY-MM-DD for the future-date rule (default: UTC today)")
|
ap.add_argument("--today", help="YYYY-MM-DD for the future-date rule (default: UTC today)")
|
||||||
ap.add_argument("--gaps", action="store_true", help="print per-handle coverage and gaps")
|
ap.add_argument("--gaps", action="store_true", help="print per-handle coverage and gaps")
|
||||||
|
ap.add_argument("--since", help="grade only check-ins dated on/after this YYYY-MM-DD "
|
||||||
|
"(the archive's older names are its record)")
|
||||||
ap.add_argument("--json", help="write the full report here")
|
ap.add_argument("--json", help="write the full report here")
|
||||||
ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault")
|
ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault")
|
||||||
ap.add_argument("--selftest", action="store_true", help="fixture corpus")
|
ap.add_argument("--selftest", action="store_true", help="fixture corpus")
|
||||||
@@ -559,9 +585,16 @@ def main(argv=None):
|
|||||||
|
|
||||||
repo = os.path.abspath(a.repo)
|
repo = os.path.abspath(a.repo)
|
||||||
path = os.path.abspath(a.dir) if a.dir else os.path.join(repo, "checkins")
|
path = os.path.abspath(a.dir) if a.dir else os.path.join(repo, "checkins")
|
||||||
|
since = a.since
|
||||||
|
if since:
|
||||||
|
try:
|
||||||
|
date.fromisoformat(since)
|
||||||
|
except ValueError:
|
||||||
|
print(f"cannot grade: --since is not YYYY-MM-DD: {since}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
try:
|
try:
|
||||||
roster, src = load_roster(repo, a.roster)
|
roster, src = load_roster(repo, a.roster)
|
||||||
rep = run(repo, path, roster, today, a.gaps)
|
rep = run(repo, path, roster, today, a.gaps, since=since)
|
||||||
except CannotGrade as exc:
|
except CannotGrade as exc:
|
||||||
print(str(exc), file=sys.stderr)
|
print(str(exc), file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
Reference in new issue
Block a user