Files
challenge_status_tracker/tracker/terminal.py
T
Casually9790andClaude Opus 5.5 e00f40eaf4 Split ctf_status.py into a tracker package, config from .env only
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>
2026-10-20 19:40:24 +02:00

64 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()