complete restructure
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""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<prefix>(?:[│ ] )*)(?:├── |└── )(?P<name>\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}"
|
||||
Reference in New Issue
Block a user