Spaces:
Paused
Paused
File size: 4,424 Bytes
8c1b9fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | """Docs-consistency checker used by CI (.github/workflows/docs.yml) and a
pytest wrapper (tests/test_docs.py).
Checks, over a curated set of public docs:
1. Every relative markdown link points to a file that exists.
2. Every `make <target>` referenced in those docs exists in the Makefile.
3. env.example documents the environment variables the getting-started /
Hugging Face docs tell people to set.
Exits non-zero and prints every problem if anything fails. Pure stdlib; runs
offline at $0.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# Public docs we hold to the link/target contract. Deliberately curated — not
# every file under docs/ (some are private research notes).
DOCS = [
"README.md",
"RUNNING.md",
"docs/getting-started/no-podman.md",
"docs/getting-started/podman.md",
"docs/getting-started/server.md",
"docs/getting-started/huggingface-space.md",
"docs/getting-started/troubleshooting.md",
"docs/evaluation.md",
"docs/benchmarks.md",
"examples/demo_corpus/README.md",
]
# Env vars the HF/getting-started docs promise exist; env.example must list them.
REQUIRED_ENV_VARS = [
"AURALYNQ_HF_SPACE",
"AURALYNQ_DEMO_MODE",
"AURALYNQ_ALLOW_UPLOADS",
"AURALYNQ_LLM__PROVIDER",
"AURALYNQ_VECTOR__BACKEND",
"AURALYNQ_EMBEDDING__PROVIDER",
"AURALYNQ_SERVE__API_KEY",
]
_LINK = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
_MAKE = re.compile(r"\bmake\s+([a-zA-Z][a-zA-Z0-9_-]*)")
_FENCE = re.compile(r"```.*?```", re.S)
_INLINE_CODE = re.compile(r"`([^`\n]+)`")
def _code_spans(text: str) -> str:
"""Concatenate only the code portions of a markdown doc (fenced blocks +
inline code), so `make sure` in prose isn't mistaken for `make <target>`."""
parts = _FENCE.findall(text)
parts += _INLINE_CODE.findall(text)
return "\n".join(parts)
def _make_targets() -> set[str]:
text = (ROOT / "Makefile").read_text(encoding="utf-8")
# Match ".PHONY: name" and "name:" target definitions.
targets: set[str] = set()
for m in re.finditer(r"^\.PHONY:\s*(.+)$", text, re.M):
targets.update(m.group(1).split())
for m in re.finditer(r"^([a-zA-Z][a-zA-Z0-9_-]*)\s*:", text, re.M):
targets.add(m.group(1))
return targets
def check_links(errors: list[str]) -> None:
for rel in DOCS:
doc = ROOT / rel
if not doc.exists():
errors.append(f"{rel}: listed in check_docs but the file is missing")
continue
base = doc.parent
for link in _LINK.findall(doc.read_text(encoding="utf-8")):
target = link.split("#", 1)[0].strip()
if not target or target.startswith(("http://", "https://", "mailto:")):
continue
resolved = (base / target).resolve()
if not resolved.exists():
errors.append(f"{rel}: broken relative link -> {target}")
def check_make_targets(errors: list[str]) -> None:
targets = _make_targets()
# Env-style make invocations we intentionally skip (they're commands users
# run, not always literal targets, e.g. "make stack-up" IS a target though).
for rel in DOCS:
doc = ROOT / rel
if not doc.exists():
continue
code = _code_spans(doc.read_text(encoding="utf-8"))
for tgt in _MAKE.findall(code):
if tgt not in targets:
errors.append(f"{rel}: references `make {tgt}` but no such Makefile target")
def check_env_example(errors: list[str]) -> None:
env = ROOT / ".env.example"
hf_env = ROOT / "deploy/huggingface/env.example"
corpus = ""
for p in (env, hf_env):
if p.exists():
corpus += p.read_text(encoding="utf-8")
if not corpus:
errors.append(".env.example and deploy/huggingface/env.example both missing")
return
for var in REQUIRED_ENV_VARS:
if var not in corpus:
errors.append(f"env example files do not document {var}")
def main() -> int:
errors: list[str] = []
check_links(errors)
check_make_targets(errors)
check_env_example(errors)
if errors:
print("✗ docs check FAILED:")
for e in sorted(set(errors)):
print(f" - {e}")
return 1
print("✓ docs check passed")
return 0
if __name__ == "__main__":
sys.exit(main())
|