"""Docs-as-tests: README.md stays in sync with the real commands and file tree.""" from __future__ import annotations import re import subprocess from pathlib import Path from discord.ext import commands REPO_ROOT = Path(__file__).resolve().parents[2] README = REPO_ROOT / "README.md" BINARY_SUFFIXES = { ".bin", ".db", ".gif", ".ico", ".jpeg", ".jpg", ".mp3", ".onnx", ".png", ".wav", } def _git(args: list[str]) -> list[str]: result = subprocess.run( ["git", *args], cwd=REPO_ROOT, capture_output=True, text=True, check=True, ) return result.stdout.splitlines() def _committed_files() -> set[str]: """Files that make up the current tree: index minus deletions, plus untracked.""" deleted: set[str] = set() for line in _git(["status", "--porcelain"]): if "D" in line[:2]: deleted.add(line[3:]) tracked = set(_git(["ls-files"])) - deleted untracked = set(_git(["ls-files", "--others", "--exclude-standard"])) files: set[str] = set() for path in tracked | untracked: parts = Path(path).parts if any(part.startswith(".") for part in parts): continue if Path(path).suffix.lower() in BINARY_SUFFIXES: continue files.add(path) return files def _readme_tree_paths() -> set[str]: """Reconstruct the relative paths (and directory entries) from the README tree.""" text = README.read_text(encoding="utf-8") match = re.search(r"## File Structure\s*```text\n(.*?)```", text, re.DOTALL) assert match is not None, "File Structure tree block not found in README" lines = match.group(1).splitlines() assert lines, "File Structure tree block is empty" root = lines[0].split("#")[0].strip().rstrip("/") assert root == REPO_ROOT.name, f"Tree root {root!r} != repo dir {REPO_ROOT.name!r}" stack: list[str] = [] paths: set[str] = set() entry_re = re.compile( r"^(?P(?:[│ ] )*)(?:├── |└── )(?P\S.*?)(?:\s+#.*)?$" ) for line in lines[1:]: m = entry_re.match(line) assert m is not None, f"Unparseable tree line: {line!r}" depth = len(m.group("prefix")) // 4 name = m.group("name").strip() if name.endswith("/"): stack = stack[:depth] + [name.rstrip("/")] paths.add("/".join(stack) + "/") else: paths.add("/".join(stack[:depth] + [name])) return paths def test_readme_documents_every_registered_command(bot: commands.Bot) -> None: """Every command registered on the bot is documented in README.md.""" readme = README.read_text(encoding="utf-8") assert len(bot.commands) >= 11 missing = [cmd.name for cmd in bot.commands if cmd.name not in readme] assert missing == [], f"Commands missing from README: {missing}" def test_readme_file_tree_matches_actual_tree() -> None: """The README tree covers every committed non-dotfile, non-binary file.""" actual = _committed_files() readme_paths = _readme_tree_paths() missing_in_readme = actual - readme_paths assert ( missing_in_readme == set() ), f"Files missing from the README tree: {sorted(missing_in_readme)}" for path in readme_paths: target = REPO_ROOT / path if path.endswith("/"): assert target.is_dir(), f"README tree lists missing directory: {path}" else: assert target.is_file(), f"README tree lists missing file: {path}"