Source code for httk.serve.web.renderers.rst

from pathlib import Path
from typing import Any

from docutils import nodes
from docutils.core import publish_doctree, publish_from_doctree
from docutils.parsers.rst import Parser
from docutils.readers.standalone import Reader
from docutils.writers.html5_polyglot import Writer

from httk.serve.web.widgets.extraction import placement_for

from .base import RenderResult, WidgetPlacement

_RST_SETTINGS_OVERRIDES = {
    "raw_enabled": False,
    "file_insertion_enabled": False,
}


[docs] class RstRenderer:
[docs] def render(self, source_path: Path) -> RenderResult: source = source_path.read_text(encoding="utf-8") # Docutils is used as the block parser here, not as a rendered-HTML # postprocessor: only paragraph nodes can become widget placements. publish_kwargs: dict[str, Any] = { "source": source, "source_path": str(source_path), "reader": Reader(), "parser": Parser(), "settings": None, "settings_spec": None, "settings_overrides": _RST_SETTINGS_OVERRIDES, "config_section": None, "enable_exit_status": False, } document = publish_doctree(**publish_kwargs) widgets = self._extract_widgets(document, source_path) writer = Writer() publish_from_doctree( document, writer=writer, settings_overrides=_RST_SETTINGS_OVERRIDES, enable_exit_status=False, ) html = writer.parts.get("html_body", "") metadata = self._extract_metadata(document) return RenderResult(html=html, metadata=metadata, widgets=widgets)
def _extract_widgets( self, doctree: nodes.document, source_path: Path, *, line_offset: int = 0 ) -> tuple[WidgetPlacement, ...]: placements: list[WidgetPlacement] = [] for paragraph in doctree.findall(nodes.paragraph): raw_text = paragraph.rawsource.strip() escaped = raw_text.startswith("\\{{ widget(") and raw_text.endswith(" }}") active = raw_text.startswith("{{ widget(") and raw_text.endswith(" }}") if not escaped and not active: continue line = (paragraph.line or 1) + line_offset if escaped: paragraph.clear() paragraph += nodes.Text(raw_text[1:]) continue placement = placement_for(raw_text, source_path=source_path, line=line, column=1, index=len(placements)) paragraph.clear() paragraph += nodes.Text(placement.placeholder) placements.append(placement) return tuple(placements) def _extract_metadata(self, doctree: nodes.document) -> dict[str, object]: metadata: dict[str, object] = {} for field in doctree.findall(nodes.field): if len(field.children) < 2: continue name_node = field.children[0] body_node = field.children[1] name = name_node.astext().strip().lower() if name.endswith("-list"): key = name[: -len("-list")] values: list[str] = [] for item in body_node.findall(nodes.list_item): text = item.astext().strip() if text: values.append(text) if not values: text = body_node.astext().strip() values = [x.strip() for x in text.splitlines() if x.strip()] metadata[key] = values else: metadata[name] = body_node.astext().strip() return metadata