Shared endpoint enricher builds curl, request, and response samples for every registry endpoint so download.md/html stay complete without a hand-maintained duplicate. Group pages reuse the same curl builder.
223 lines
8.2 KiB
Python
223 lines
8.2 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
import json
|
|
from html import escape
|
|
|
|
from molodetz.docs_api import ordered_groups, substitute
|
|
from molodetz.docs_prose import page_source, render_page, visible_pages
|
|
|
|
BASE_PLACEHOLDER = "https://example.com"
|
|
USERNAME_PLACEHOLDER = "retoor"
|
|
API_KEY_PLACEHOLDER = "YOUR_API_KEY"
|
|
|
|
|
|
def _filled(value):
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, str):
|
|
return substitute(value, BASE_PLACEHOLDER, USERNAME_PLACEHOLDER, API_KEY_PLACEHOLDER)
|
|
if isinstance(value, dict):
|
|
return {key: _filled(item) for key, item in value.items()}
|
|
if isinstance(value, list):
|
|
return [_filled(item) for item in value]
|
|
return value
|
|
|
|
|
|
def request_sample(item):
|
|
if item.get("probe_body") is not None:
|
|
return _filled(item["probe_body"])
|
|
body = {}
|
|
query = {}
|
|
path = {}
|
|
for field in item.get("fields") or []:
|
|
example = field.get("example")
|
|
if example is None and field.get("enum"):
|
|
example = field["enum"][0]
|
|
if example is None:
|
|
continue
|
|
example = _filled(example)
|
|
location = field.get("location") or "body"
|
|
if location == "query":
|
|
query[field["name"]] = example
|
|
elif location == "path":
|
|
path[field["name"]] = example
|
|
else:
|
|
body[field["name"]] = example
|
|
sample = {}
|
|
if path:
|
|
sample["path"] = path
|
|
if query:
|
|
sample["query"] = query
|
|
if body:
|
|
sample["body"] = body
|
|
return sample or None
|
|
|
|
|
|
def response_sample(item):
|
|
return _filled(item.get("sample"))
|
|
|
|
|
|
def curl_example(item, base=BASE_PLACEHOLDER, username=USERNAME_PLACEHOLDER, api_key=API_KEY_PLACEHOLDER):
|
|
path = item["path"]
|
|
for field in item.get("fields") or []:
|
|
if field.get("location") == "path" and field.get("example") is not None:
|
|
path = path.replace("{" + field["name"] + "}", str(_filled(field["example"])))
|
|
headers = ["-H 'Accept: application/json'"]
|
|
if item.get("auth") != "public":
|
|
headers.append("-H 'X-API-KEY: {{api_key}}'")
|
|
parts = ["curl", "-X", item["method"], *headers]
|
|
body = request_sample(item)
|
|
payload = None
|
|
if isinstance(body, dict) and "body" in body:
|
|
payload = body["body"]
|
|
elif isinstance(body, dict) and body and "path" not in body and "query" not in body:
|
|
payload = body
|
|
elif item.get("probe_body") is not None:
|
|
payload = _filled(item["probe_body"])
|
|
if payload is not None and item["method"] not in ("GET", "HEAD"):
|
|
parts.append("-H 'Content-Type: application/json'")
|
|
parts.append(f"-d '{json.dumps(payload, ensure_ascii=False)}'")
|
|
query = body.get("query") if isinstance(body, dict) else None
|
|
url = f"{{{{base}}}}{path}"
|
|
if query:
|
|
from urllib.parse import urlencode
|
|
|
|
url = f"{url}?{urlencode(query, doseq=True)}"
|
|
parts.append(f"'{url}'")
|
|
return substitute(" ".join(parts), base, username, api_key)
|
|
|
|
|
|
def enrich_endpoint(item, base=BASE_PLACEHOLDER, username=USERNAME_PLACEHOLDER, api_key=API_KEY_PLACEHOLDER):
|
|
return {
|
|
**item,
|
|
"curl": curl_example(item, base, username, api_key),
|
|
"request_sample": request_sample(item),
|
|
"response_sample": response_sample(item),
|
|
}
|
|
|
|
|
|
def visible_api_groups(viewer_is_admin):
|
|
return [group for group in ordered_groups() if viewer_is_admin or group["slug"] != "admin"]
|
|
|
|
|
|
def _json_block(value):
|
|
if value is None:
|
|
return "null"
|
|
return json.dumps(value, indent=2, ensure_ascii=False, sort_keys=True)
|
|
|
|
|
|
def format_endpoint_markdown(item):
|
|
enriched = enrich_endpoint(item)
|
|
lines = [
|
|
f"### `{enriched['method']} {enriched['path']}`",
|
|
"",
|
|
f"- **Title:** {enriched['title']}",
|
|
f"- **Auth:** {enriched['auth']} ({enriched['min_role']})",
|
|
f"- **Negotiation:** {enriched['negotiation']}",
|
|
]
|
|
if enriched.get("description"):
|
|
lines += ["", enriched["description"]]
|
|
if enriched.get("fields"):
|
|
lines += ["", "#### Fields", ""]
|
|
for field in enriched["fields"]:
|
|
required = "required" if field.get("required") else "optional"
|
|
enum = f" allowed={field['enum']}" if field.get("enum") else ""
|
|
example = f" example={field['example']!r}" if field.get("example") is not None else ""
|
|
lines.append(
|
|
f"- `{field['name']}` ({field.get('type', 'string')}, {field.get('location', 'body')}, {required}){enum}{example}"
|
|
+ (f" - {field['description']}" if field.get("description") else "")
|
|
)
|
|
lines += ["", "#### Request sample", "", "```bash", enriched["curl"], "```"]
|
|
if enriched["request_sample"] is not None:
|
|
lines += ["", "```json", _json_block(enriched["request_sample"]), "```"]
|
|
lines += ["", "#### Response sample", "", "```json", _json_block(enriched["response_sample"]), "```", ""]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def format_endpoint_html(item):
|
|
enriched = enrich_endpoint(item)
|
|
fields_rows = ""
|
|
if enriched.get("fields"):
|
|
rows = []
|
|
for field in enriched["fields"]:
|
|
rows.append(
|
|
"<tr>"
|
|
f"<td><code>{escape(field['name'])}</code></td>"
|
|
f"<td>{escape(str(field.get('type', 'string')))}</td>"
|
|
f"<td>{escape(str(field.get('location', 'body')))}</td>"
|
|
f"<td>{'yes' if field.get('required') else 'no'}</td>"
|
|
f"<td>{escape(str(field.get('example', '')))}</td>"
|
|
"</tr>"
|
|
)
|
|
fields_rows = (
|
|
"<h4>Fields</h4><table><thead><tr><th>Name</th><th>Type</th><th>Location</th>"
|
|
"<th>Required</th><th>Example</th></tr></thead><tbody>"
|
|
+ "".join(rows)
|
|
+ "</tbody></table>"
|
|
)
|
|
request_json = (
|
|
f"<h4>Request JSON</h4><pre>{escape(_json_block(enriched['request_sample']))}</pre>"
|
|
if enriched["request_sample"] is not None
|
|
else ""
|
|
)
|
|
return (
|
|
f"<section>"
|
|
f"<h3><code>{escape(enriched['method'])} {escape(enriched['path'])}</code></h3>"
|
|
f"<p><strong>{escape(enriched['title'])}</strong> · auth {escape(enriched['auth'])} "
|
|
f"({escape(enriched['min_role'])}) · {escape(enriched['negotiation'])}</p>"
|
|
+ (f"<p>{escape(enriched['description'])}</p>" if enriched.get("description") else "")
|
|
+ fields_rows
|
|
+ f"<h4>Request sample</h4><pre>{escape(enriched['curl'])}</pre>"
|
|
+ request_json
|
|
+ f"<h4>Response sample</h4><pre>{escape(_json_block(enriched['response_sample']))}</pre>"
|
|
+ "</section>"
|
|
)
|
|
|
|
|
|
def format_group_markdown(group):
|
|
parts = [f"## API: {group['title']}", "", group.get("description") or "", ""]
|
|
for item in group["endpoints"]:
|
|
parts.append(format_endpoint_markdown(item))
|
|
return "\n".join(parts).strip() + "\n"
|
|
|
|
|
|
def format_group_html(group):
|
|
body = "".join(format_endpoint_html(item) for item in group["endpoints"])
|
|
return (
|
|
f"<section><h2>API: {escape(group['title'])}</h2>"
|
|
f"<p>{escape(group.get('description') or '')}</p>{body}</section>"
|
|
)
|
|
|
|
|
|
def export_markdown(viewer_is_admin):
|
|
parts = [
|
|
"# Molodetz documentation",
|
|
"",
|
|
"Auto-generated from the live docs registry. Give this file to an agent to learn the API.",
|
|
"",
|
|
]
|
|
for page in visible_pages(viewer_is_admin):
|
|
parts += [page_source(page["slug"]).strip(), ""]
|
|
parts.append("# API reference")
|
|
parts.append("")
|
|
for group in visible_api_groups(viewer_is_admin):
|
|
parts.append(format_group_markdown(group))
|
|
parts.append("")
|
|
return "\n".join(parts).rstrip() + "\n"
|
|
|
|
|
|
def export_html(viewer_is_admin):
|
|
body = [
|
|
"<h1>Molodetz documentation</h1>",
|
|
"<p>Auto-generated from the live docs registry. Give this file to an agent to learn the API.</p>",
|
|
]
|
|
for page in visible_pages(viewer_is_admin):
|
|
body.append(f"<section>{render_page(page['slug'])}</section>")
|
|
body.append("<h1>API reference</h1>")
|
|
for group in visible_api_groups(viewer_is_admin):
|
|
body.append(format_group_html(group))
|
|
return (
|
|
"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">"
|
|
"<title>Molodetz documentation</title></head>"
|
|
f"<body>{''.join(body)}</body></html>"
|
|
)
|