from __future__ import annotations
import re
import shlex
import subprocess
import sys
from pathlib import Path
from typing import Optional
ROOT = Path(__file__).resolve().parent.parent
SOURCES = [
ROOT / "README.md",
ROOT / "docs/bash.md",
ROOT / "docs/protocol-v1.md",
ROOT / "docs/transport-mappings.md",
ROOT / "skills/agent-first-data/SKILL.md",
ROOT / "skills/agent-first-data/references/bash.md",
ROOT / "skills/agent-first-data/references/cli-protocol.md",
ROOT / "skills/agent-first-data/references/documents.md",
ROOT / "skills/agent-first-data/references/naming-output.md",
]
MUST_HAVE_EXAMPLES = {
ROOT / "README.md",
ROOT / "skills/agent-first-data/references/documents.md",
}
READ_VERBS = {"get", "value", "values", "keys", "paths", "lint", "render", "validate"}
FENCE_OPEN = re.compile(r"^ {0,3}(?P<fence>`{3,}|~{3,})(?P<info>.*)$")
def fenced_blocks(text: str) -> list[str]:
blocks = []
block: Optional[list[str]] = None
fence_char = ""
fence_width = 0
for line in text.splitlines():
if block is None:
match = FENCE_OPEN.fullmatch(line)
if not match:
continue
fence = match.group("fence")
info = match.group("info")
if fence[0] == "`" and "`" in info:
continue
block = []
fence_char = fence[0]
fence_width = len(fence)
continue
candidate = line.lstrip(" ")
indent = len(line) - len(candidate)
candidate = candidate.rstrip(" \t")
if (
indent <= 3
and len(candidate) >= fence_width
and all(char == fence_char for char in candidate)
):
blocks.append("\n".join(block))
block = None
fence_char = ""
fence_width = 0
else:
block.append(line)
if block is not None:
blocks.append("\n".join(block))
return blocks
def examples(text: str) -> list[str]:
found = []
for block in fenced_blocks(text):
joined: list[str] = []
for line in block.splitlines():
if joined and joined[-1].endswith("\\"):
joined[-1] = joined[-1][:-1].rstrip() + " " + line.strip()
else:
joined.append(line.strip())
for line in joined:
if not line.startswith("afdata ") or line.endswith("\\"):
continue
if any(ch in line for ch in "|$><&"):
continue
found.append(line)
return found
def main() -> int:
failures = []
counts: list[str] = []
checked = 0
for source in SOURCES:
if not source.is_file():
failures.append(f"{source.relative_to(ROOT)}: source file is missing")
continue
source_checked = 0
for line in examples(source.read_text(encoding="utf-8")):
try:
argv = shlex.split(line, comments=True)
except ValueError:
continue
if len(argv) < 2 or argv[1] not in READ_VERBS:
continue
targets = [a for a in argv[2:] if not a.startswith("-") and Path(ROOT / a).is_file()]
if not targets:
continue
checked += 1
source_checked += 1
run = subprocess.run(
[str(ROOT / "target/debug/afdata")] + argv[1:],
cwd=ROOT,
capture_output=True,
encoding="utf-8",
)
if run.returncode != 0:
failures.append(
f"{source.relative_to(ROOT)}: `{line}`\n -> {run.stderr.strip()[:200]}"
)
if source_checked == 0 and source in MUST_HAVE_EXAMPLES:
failures.append(
f"{source.relative_to(ROOT)}: no runnable read examples found"
)
counts.append(f"{source.relative_to(ROOT)}: {source_checked}")
if failures:
print("documented examples that do not run:", file=sys.stderr)
for failure in failures:
print(f" {failure}", file=sys.stderr)
return 1
print(f"doc examples ok: {checked} runnable commands execute cleanly")
print(f" ({'; '.join(counts)})")
return 0
if __name__ == "__main__":
sys.exit(main())