ctf_status.py is now just the command line; the work lives in tracker/. The rules that decide whether a challenge is believed are in tracker/checks.py, one function per rule, listed in README_CHECKS and REPO_CHECKS. Output is unchanged. Read the config from .env only (ctf_status.env.example -> .env.example), let single_run_lock() hold its own lock files, and rewrite the README as a short overview of what the repo does and how to run it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.9 KiB
Python
64 lines
2.9 KiB
Python
"""The summary printed when ctf_status.py runs in a terminal."""
|
||
from __future__ import annotations
|
||
|
||
from collections import Counter
|
||
|
||
from .challenge import BLOCKED_ICON, STATUS_ICON, STATUS_ORDER
|
||
from .overview import Overview, icons
|
||
|
||
BOLD, DIM, RED, YEL, GRN, CYA, RST = (
|
||
"\033[1m", "\033[2m", "\033[31m", "\033[33m", "\033[32m", "\033[36m", "\033[0m")
|
||
|
||
|
||
def print_terminal(ov: Overview, c: bool) -> None:
|
||
def col(x): return x if c else ""
|
||
stages = ov.stages
|
||
groups = ov.by_category()
|
||
|
||
total = Counter(ch.stage for ch in ov.challenges)
|
||
print(f"\n{col(BOLD)}=== {len(ov.challenges)} challenges ==={col(RST)} "
|
||
+ " ".join(f"{STATUS_ICON[s]} {s or '?'}:{total[s]}" for s in stages if total[s]))
|
||
|
||
ready = ov.testable
|
||
print(f"\n{col(BOLD)}{col(GRN)}=== ready for infra testing ({len(ready)}) ==={col(RST)}")
|
||
for ch in ready:
|
||
print(f" {STATUS_ICON[ch.stage]} {ch.title[:34]:<35}{ch.category or '?':<10}"
|
||
f"{ch.difficulty or '?':<13}{col(DIM)}{ch.repo.web_url}{col(RST)}")
|
||
|
||
if ov.blocked:
|
||
print(f"\n{col(BOLD)}{col(RED)}=== {BLOCKED_ICON} claims {STATUS_ORDER[2]}+, "
|
||
f"but the repo disagrees ({len(ov.blocked)}) ==={col(RST)}")
|
||
for ch in ov.blocked:
|
||
print(f" {STATUS_ICON[ch.stage]} {ch.title[:34]:<35}{col(DIM)}"
|
||
+ "; ".join(ch.blockers) + col(RST))
|
||
|
||
print(f"\n{col(BOLD)}=== status by category ==={col(RST)}")
|
||
print(f" {'':<10}" + "".join(f"{s or '?':>14}" for s in stages) + f"{'total':>7}")
|
||
for cat in ov.categories:
|
||
chs = groups.get(cat, [])
|
||
cnt = Counter(ch.stage for ch in chs)
|
||
tint = "" if chs else DIM
|
||
print(f" {col(tint)}{cat:<10}" + "".join(f"{cnt[s] or '·':>14}" for s in stages)
|
||
+ f"{len(chs):>7}{col(RST)}")
|
||
|
||
print(f"\n{col(BOLD)}=== category × difficulty ==={col(RST)} "
|
||
+ " ".join(f"{STATUS_ICON[s]} {s}" for s in STATUS_ORDER) + f" {BLOCKED_ICON} blocked")
|
||
diffs = ov.difficulties
|
||
print(f" {'':<10}" + "".join(f"{d:<13}" for d in diffs))
|
||
for cat in ov.categories:
|
||
chs = groups.get(cat, [])
|
||
cells = [icons([ch for ch in chs if (ch.difficulty or "?") == d]) for d in diffs]
|
||
# emoji are two columns wide in a terminal
|
||
print(f" {cat:<10}" + "".join(f"{x or '·'}{' ' * (13 - (2 * len(x) if x else 1))}"
|
||
for x in cells))
|
||
|
||
print(f"\n{col(BOLD)}=== needs attention ==={col(RST)}")
|
||
for ch in sorted(ov.challenges, key=lambda c: c.repo.path.lower()):
|
||
if ch.problems:
|
||
print(f" {col(YEL)}{ch.repo.path:<32}{col(RST)} {col(DIM)}"
|
||
+ "; ".join(ch.problems) + col(RST))
|
||
for ch in ov.blank:
|
||
why = "no README" if ch.repo.readme is None else "README is still the blank template"
|
||
print(f" {col(RED)}{ch.repo.path:<32}{col(RST)} {col(DIM)}{why}{col(RST)}")
|
||
print()
|