"""Page helper registration for sx docs. All helpers return data values (dicts, lists) — no sx_call(), no SxExpr. Markup composition lives entirely in .sx files. """ from __future__ import annotations def _register_sx_helpers() -> None: """Register Python data helpers as page helpers.""" from shared.sx.pages import register_page_helpers from content.highlight import highlight as _highlight register_page_helpers("sx", { "highlight": _highlight, "primitives-data": _primitives_data, "reference-data": _reference_data, "attr-detail-data": _attr_detail_data, "spec-data": _spec_data, }) def _primitives_data() -> dict: """Return the PRIMITIVES dict for the primitives docs page.""" from content.pages import PRIMITIVES return PRIMITIVES def _reference_data(slug: str) -> dict: """Return reference table data for a given slug. Returns a dict whose keys become SX env bindings: - attributes: req-attrs, beh-attrs, uniq-attrs - headers: req-headers, resp-headers - events: events-list - js-api: js-api-list """ from content.pages import ( REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS, REQUEST_HEADERS, RESPONSE_HEADERS, EVENTS, JS_API, ATTR_DETAILS, ) if slug == "attributes": return { "req-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in REQUEST_ATTRS ], "beh-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in BEHAVIOR_ATTRS ], "uniq-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in SX_UNIQUE_ATTRS ], } elif slug == "headers": return { "req-headers": [ {"name": n, "value": v, "desc": d} for n, v, d in REQUEST_HEADERS ], "resp-headers": [ {"name": n, "value": v, "desc": d} for n, v, d in RESPONSE_HEADERS ], } elif slug == "events": return { "events-list": [ {"name": n, "desc": d} for n, d in EVENTS ], } elif slug == "js-api": return { "js-api-list": [ {"name": n, "desc": d} for n, d in JS_API ], } # Default — return attrs data for fallback return { "req-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in REQUEST_ATTRS ], "beh-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in BEHAVIOR_ATTRS ], "uniq-attrs": [ {"name": a, "desc": d, "exists": e, "href": f"/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} for a, d, e in SX_UNIQUE_ATTRS ], } _CORE_SPECS = { "parser": ("parser.sx", "Parser", "Tokenization and parsing of SX source text into AST."), "evaluator": ("eval.sx", "Evaluator", "Tree-walking evaluation of SX expressions."), "primitives": ("primitives.sx", "Primitives", "All built-in pure functions and their signatures."), "renderer": ("render.sx", "Renderer", "Shared rendering registries and utilities used by all adapters."), } _ADAPTER_SPECS = { "adapter-dom": ("adapter-dom.sx", "DOM Adapter", "Renders SX expressions to live DOM nodes. Browser-only."), "adapter-html": ("adapter-html.sx", "HTML Adapter", "Renders SX expressions to HTML strings. Server-side."), "adapter-sx": ("adapter-sx.sx", "SX Wire Adapter", "Serializes SX for client-side rendering. Component calls stay unexpanded."), "engine": ("engine.sx", "SxEngine", "Fetch/swap/history engine for browser-side SX. Like HTMX but native to SX."), } _ALL_SPECS = {**_CORE_SPECS, **_ADAPTER_SPECS} def _spec_data(slug: str) -> dict: """Return spec file source and metadata for display.""" import os ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") if not os.path.isdir(ref_dir): ref_dir = "/app/shared/sx/ref" base = {"spec-not-found": None, "spec-title": None, "spec-desc": None, "spec-filename": None, "spec-source": None, "spec-files": None} if slug == "core": specs = [] for key in ("parser", "evaluator", "primitives", "renderer"): filename, title, desc = _CORE_SPECS[key] filepath = os.path.join(ref_dir, filename) source = _read_spec(filepath) specs.append({ "title": title, "desc": desc, "filename": filename, "source": source, "href": f"/specs/{key}", }) return {**base, "spec-title": "Core Language", "spec-files": specs} if slug == "adapters": specs = [] for key in ("adapter-dom", "adapter-html", "adapter-sx", "engine"): filename, title, desc = _ADAPTER_SPECS[key] filepath = os.path.join(ref_dir, filename) source = _read_spec(filepath) specs.append({ "title": title, "desc": desc, "filename": filename, "source": source, "href": f"/specs/{key}", }) return {**base, "spec-title": "Adapters & Engine", "spec-files": specs} info = _ALL_SPECS.get(slug) if not info: return {**base, "spec-not-found": True} filename, title, desc = info filepath = os.path.join(ref_dir, filename) source = _read_spec(filepath) return {**base, "spec-title": title, "spec-desc": desc, "spec-filename": filename, "spec-source": source} def _read_spec(filepath: str) -> str: """Read a spec file, returning empty string if missing.""" try: with open(filepath, encoding="utf-8") as f: return f.read() except FileNotFoundError: return ";; spec file not found" def _attr_detail_data(slug: str) -> dict: """Return attribute detail data for a specific attribute slug. Returns a dict whose keys become SX env bindings: - attr-title, attr-description, attr-example, attr-handler - attr-demo (component call or None) - attr-wire-id (wire placeholder id or None) - attr-not-found (truthy if not found) """ from content.pages import ATTR_DETAILS from shared.sx.helpers import SxExpr detail = ATTR_DETAILS.get(slug) if not detail: return {"attr-not-found": True} demo_name = detail.get("demo") wire_id = None if "handler" in detail: wire_id = f"ref-wire-{slug.replace(':', '-').replace('*', 'star')}" return { "attr-not-found": None, "attr-title": slug, "attr-description": detail["description"], "attr-example": detail["example"], "attr-handler": detail.get("handler"), "attr-demo": SxExpr(f"(~{demo_name})") if demo_name else None, "attr-wire-id": wire_id, }