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.
This commit is contained in:
russell@unturf.com 2026-05-04 08:26:27 -04:00
parent 306b7f3701
commit 546fa6d690
No known key found for this signature in database
4 changed files with 81 additions and 1 deletions

3
.gitignore vendored
View file

@ -21,3 +21,6 @@ bench/qa_results/
# Sphinx build artifacts (generated; RTD builds on its servers)
docs/_source/_build/
# Auto-generated by docs/_source/_ext/makefile_targets.py at Sphinx build time
docs/_source/api/makefile.rst

View file

@ -0,0 +1,74 @@
"""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}

View file

@ -3,9 +3,10 @@
import sys
from pathlib import Path
# Add aborist package to path
# Add aborist package + local _ext (Sphinx extensions) to path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(Path(__file__).parent / "_ext"))
# Project info
project = "Aborist"
@ -20,6 +21,7 @@ extensions = [
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"makefile_targets", # local: generates api/makefile.rst from Makefile ## annotations
]
# Autodoc settings

View file

@ -9,6 +9,7 @@ Contents:
:maxdepth: 3
:caption: API Modules
api/makefile
api/substrate
api/storage
api/retrieval