"""Build source-only Python reference pages with Griffe, without importing FastVideo."""
from __future__ import annotations
import html
import json
import re
import runpy
import shutil
from pathlib import Path
from urllib.parse import urljoin, urlsplit, urlunsplit
import griffe
import markdown
import yaml
ROOT = Path(__file__).resolve().parents[2]
SITE = ROOT / "docs-site"
GENERATED = SITE / ".generated"
def signature(obj) -> str:
"""Preserve parameter kinds, annotations, defaults, and return annotations."""
if obj.is_attribute:
annotation = f": {obj.annotation}" if obj.annotation else ""
value = f" = {obj.value}" if obj.value is not None else ""
return f"{obj.name}{annotation}{value}"
parameters = list(getattr(obj, "parameters", []))
parts = []
keyword_separator = False
for i, param in enumerate(parameters):
if param.name in {"self", "cls"}:
continue
kind = param.kind.value
if kind == "keyword-only" and not keyword_separator:
parts.append("*")
keyword_separator = True
prefix = "**" if kind == "variadic keyword" else "*" if kind == "variadic positional" else ""
if prefix == "*":
keyword_separator = True
text = prefix + param.name
if param.annotation is not None:
text += f": {param.annotation}"
if param.default is not None and not prefix:
text += f" = {param.default}"
parts.append(text)
if kind == "positional-only" and (i + 1 == len(parameters) or parameters[i + 1].kind.value != kind):
parts.append("/")
result = f"{obj.name}({', '.join(parts)})"
if obj.is_function and obj.returns is not None:
result += f" -> {obj.returns}"
return result
def code_html(text: str) -> str:
return html.escape(text).replace("[", "[").replace("]", "]")
def prose(text: str) -> str:
# Docstrings are trusted repository source, rendered as Markdown rather than executed.
return markdown.markdown(text, extensions=["fenced_code", "tables", "sane_lists"])
def api_overview(text: str, package) -> str:
def reference(match):
label, target = match.groups()
path = (target or label).strip("`")
obj = package if path == package.path else package[path.removeprefix(package.path + ".")]
module = obj
while not module.is_module:
module = module.parent
route = "/api/" + module.path.replace(".", "/")
return f"[{label}]({route}#{path})"
text = re.sub(r"\[([^\]\n]+)\]\[([^\]\n]*)\]", reference, text)
def guide(match):
label, target = match.groups()
url = urlsplit(target)
if url.scheme or url.netloc or not re.search(r"\.mdx?$", url.path):
return match[0]
url = urlsplit(urljoin("/api/index.md", target))
route = re.sub(r"\.mdx?$", "", url.path)
route = re.sub(r"/index$", "", route) or "/"
return f"[{label}]({urlunsplit(('', '', route, url.query, url.fragment))})"
return prose(re.sub(r"\[([^\]\n]+)\]\(([^\s)]+)\)", guide, text))
def docstring(obj, level: int) -> str:
if not obj.docstring:
return ""
output = []
for section in obj.docstring.parsed:
kind = section.kind.value
if kind == "text":
output.append(prose(section.value))
continue
title = section.title or kind.replace("_", " ").capitalize()
output.append(f"
{html.escape(value)}" if example_kind.value == "examples" else prose(value))
elif isinstance(section.value, list):
output.append("{html.escape(label)}{html.escape(path)}{code_html(signature(obj))}')
output.append(docstring(obj, min(level + 1, 6)))
if obj.is_class:
constructor = obj.members.get("__init__")
if constructor is not None:
output.append(docstring(constructor, min(level + 1, 6)))
if source and not obj.is_alias:
try:
code = obj.source
except (ValueError, KeyError):
code = ""
if code:
output.append(f'{code_html(code)}