""" Full-page s-expression rendering. Provides ``render_page()`` for rendering a complete HTML page from an s-expression, bypassing Jinja entirely. Used by error handlers and (eventually) by route handlers for fully-migrated pages. ``render_sx_response()`` is the main entry point for GET route handlers: it calls the app's context processor, merges in route-specific kwargs, renders the s-expression to HTML, and returns a Quart ``Response``. Usage:: from shared.sx.page import render_page, render_sx_response # Error pages (no context needed) html = render_page( '(~error-page :title "Not Found" :message "NOT FOUND" :image img :asset-url aurl)', image="/static/errors/404.gif", asset_url="/static", ) # GET route handlers (auto-injects app context) resp = await render_sx_response('(~orders-page :orders orders)', orders=orders) """ from __future__ import annotations from typing import Any from .jinja_bridge import sx SEARCH_HEADERS_MOBILE = '{"X-Origin":"search-mobile","X-Search":"true"}' SEARCH_HEADERS_DESKTOP = '{"X-Origin":"search-desktop","X-Search":"true"}' def render_page(source: str, **kwargs: Any) -> str: """Render a full HTML page from an s-expression string. This is a thin wrapper around ``sx()`` — it exists to make the intent explicit in call sites (rendering a whole page, not a fragment). """ return sx(source, **kwargs) async def get_template_context(**kwargs: Any) -> dict[str, Any]: """Gather the full template context from all registered context processors. Returns a dict with all context variables that would normally be available in a Jinja template, merged with any extra kwargs. """ import asyncio from quart import current_app, request ctx: dict[str, Any] = {} # App-level context processors for proc in current_app.template_context_processors.get(None, []): rv = proc() if asyncio.iscoroutine(rv): rv = await rv ctx.update(rv) # Blueprint-scoped context processors for bp_name in (request.blueprints or []): for proc in current_app.template_context_processors.get(bp_name, []): rv = proc() if asyncio.iscoroutine(rv): rv = await rv ctx.update(rv) # Inject Jinja globals that s-expression components need (URL helpers, # asset_url, styles, etc.) — these aren't provided by context processors. for key, val in current_app.jinja_env.globals.items(): if key not in ctx: ctx[key] = val # Expose request-scoped values that sx components need from quart import g if "rights" not in ctx: ctx["rights"] = getattr(g, "rights", {}) ctx.update(kwargs) return ctx async def render_sx_response(source: str, **kwargs: Any) -> str: """Render an s-expression with the full app template context. Calls the app's registered context processors (which provide cart_mini, auth_menu, nav_tree, asset_url, etc.) and merges them with the caller's kwargs before rendering. Returns the rendered HTML string (caller wraps in Response as needed). """ ctx = await get_template_context(**kwargs) return sx(source, **ctx)