casebench
test cases straight onto the board.
git clone https://owenrusk.dev/casebench.git
commit 71e65c89933d25d238a483807b7a3de0e6f55f13 author Owen Rusk <owen@papermothgames.com> date 2025-03-10 14:12:37 -0500
first cut: put a case folder on a board writes the case straight into the board tables. the runtime only sees what passes through it, so it never sees these.
| .gitignore | +3 | -0 |
| README.md | +7 | -0 |
| casebench/__init__.py | +0 | -0 |
| casebench/__main__.py | +5 | -0 |
| casebench/casefile.py | +34 | -0 |
| casebench/cli.py | +29 | -0 |
| casebench/db.py | +26 | -0 |
| pyproject.toml | +12 | -0 |
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e4567d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.egg-info/ +.venv/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..29eb4dc --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# casebench + +Test cases go straight to the board. The runtime never sees them. + + casebench put path/to/case --player owen + +needs CASEBENCH_DSN, pointing at the hub's postgres. diff --git a/casebench/__init__.py b/casebench/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/casebench/__main__.py b/casebench/__main__.py new file mode 100644 index 0000000..dd8a8c9 --- /dev/null +++ b/casebench/__main__.py @@ -0,0 +1,5 @@ +import sys + +from .cli import main + +sys.exit(main()) diff --git a/casebench/casefile.py b/casebench/casefile.py new file mode 100644 index 0000000..2beeafc --- /dev/null +++ b/casebench/casefile.py @@ -0,0 +1,34 @@ +from dataclasses import dataclass +from pathlib import Path + +import yaml + + +class CaseError(ValueError): + pass + + +@dataclass(frozen=True) +class Item: + id: str + title: str + text: str + + +@dataclass(frozen=True) +class Case: + id: str + title: str + items: tuple[Item, ...] + + +def load(folder: Path) -> Case: + path = Path(folder) / "case.yaml" + raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + items = tuple( + Item(entry["id"], entry["title"], (path.parent / entry["text"]).read_text(encoding="utf-8")) + for entry in raw.get("items") or [] + ) + if not items: + raise CaseError(f"{path}: no items") + return Case(raw["id"], raw["title"], items) diff --git a/casebench/cli.py b/casebench/cli.py new file mode 100644 index 0000000..8098615 --- /dev/null +++ b/casebench/cli.py @@ -0,0 +1,29 @@ +import argparse +import os +import sys +from pathlib import Path + +from . import casefile, db + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(prog="casebench") + sub = parser.add_subparsers(dest="command", required=True) + put = sub.add_parser("put", help="put a case on a player's board") + put.add_argument("folder", type=Path) + put.add_argument("--player", required=True) + args = parser.parse_args(argv) + + dsn = os.environ.get("CASEBENCH_DSN") + if not dsn: + print("casebench: set CASEBENCH_DSN", file=sys.stderr) + return 2 + case = casefile.load(args.folder) + with db.connect(dsn) as conn: + player = db.player_id(conn, args.player) + if player is None: + print(f"casebench: no player {args.player}", file=sys.stderr) + return 1 + db.put(conn, player, case) + print(f"{args.player}: put {case.id}") + return 0 diff --git a/casebench/db.py b/casebench/db.py new file mode 100644 index 0000000..b75e378 --- /dev/null +++ b/casebench/db.py @@ -0,0 +1,26 @@ +import psycopg + +from .casefile import Case + + +def connect(dsn: str) -> psycopg.Connection: + return psycopg.connect(dsn) + + +def player_id(conn: psycopg.Connection, handle: str) -> str | None: + row = conn.execute("select id from players where handle = %s", (handle,)).fetchone() + return row[0] if row else None + + +def put(conn: psycopg.Connection, player: str, case: Case) -> None: + with conn.transaction(): + conn.execute( + "insert into board_cases (player_id, case_id, title, source) values (%s, %s, %s, 'bench')", + (player, case.id, case.title), + ) + for position, item in enumerate(case.items): + conn.execute( + "insert into board_items (player_id, case_id, item_id, position, title, body)" + " values (%s, %s, %s, %s, %s, %s)", + (player, case.id, item.id, position, item.title, item.text), + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9d717a4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "casebench" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = ["pyyaml>=6", "psycopg>=3.1"] + +[project.scripts] +casebench = "casebench.cli:main" + +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta"