arborist/docs/_source/_ext/makefile_targets.py
russell@unturf.com 546fa6d690
docs: auto-generate Makefile reference page for RTD
Adds a Sphinx extension at docs/_source/_ext/makefile_targets.py that
parses the project Makefile's '## description' annotations and writes
docs/_source/api/makefile.rst at build time. Same convention 'make help'
uses, so the reference stays in sync with the source.

Generated page is grouped by target prefix (fetch-, ingest-, distill-,
docs-, etc.) and rendered as a list-table. Shows on RTD alongside the
autodoc API modules.

Generated file is gitignored — RTD regenerates on every build.
2026-05-04 08:26:27 -04:00

74 lines
2.7 KiB
Python

"""Generate docs/_source/api/makefile.rst from Makefile ## annotations.
Convention: every documented target has the form
target-name: deps ## one-line description
This extension parses the project's Makefile and writes an RST page
listing every documented target, grouped by category prefix.
"""
from pathlib import Path
import re
TARGET_RE = re.compile(r"^([a-zA-Z0-9_-]+):.*?##\s*(.*)$")
def parse_makefile(makefile_path: Path) -> list[tuple[str, str]]:
"""Return list of (target, description) tuples from a Makefile."""
targets = []
for line in makefile_path.read_text(encoding="utf-8").splitlines():
m = TARGET_RE.match(line)
if m:
targets.append((m.group(1), m.group(2).strip()))
return targets
def categorize(targets: list[tuple[str, str]]) -> dict[str, list[tuple[str, str]]]:
"""Group targets by leading prefix (e.g. 'fetch-', 'ingest-', 'docs-')."""
groups: dict[str, list[tuple[str, str]]] = {}
for name, desc in targets:
prefix = name.split("-", 1)[0] if "-" in name else name
groups.setdefault(prefix, []).append((name, desc))
return groups
def generate_rst(targets: list[tuple[str, str]], output_path: Path) -> None:
"""Write a single RST page listing every documented target."""
groups = categorize(targets)
lines = [
"Makefile reference",
"==================",
"",
"Every aborist workflow lives behind a ``make`` target. This page is",
"auto-generated from the project ``Makefile``'s ``## description``",
"annotations at Sphinx build time, so it stays in sync with the source.",
"",
"Run ``make help`` locally for the same listing.",
"",
]
for prefix in sorted(groups):
lines.append(prefix)
lines.append("-" * len(prefix))
lines.append("")
lines.append(".. list-table::")
lines.append(" :widths: 25 75")
lines.append(" :header-rows: 1")
lines.append("")
lines.append(" * - Target")
lines.append(" - Description")
for name, desc in sorted(groups[prefix]):
lines.append(f" * - ``{name}``")
lines.append(f" - {desc}")
lines.append("")
output_path.write_text("\n".join(lines), encoding="utf-8")
def setup(app):
"""Sphinx hook: regenerate makefile.rst at the start of every build."""
project_root = Path(app.srcdir).parent.parent
makefile = project_root / "Makefile"
output = Path(app.srcdir) / "api" / "makefile.rst"
if makefile.exists():
targets = parse_makefile(makefile)
generate_rst(targets, output)
return {"version": "1.0", "parallel_read_safe": True}