"""Render braindecode rST docstrings to HTML for the model-explorer Space.
Braindecode docstrings use NumpyDoc + Sphinx extensions:
.. figure:: (architecture image)
:bdg-danger: (sphinx-design colored badges)
.. versionadded:: (Sphinx admonition)
.. important:: (admonition)
.. code-block:: (highlighted code)
[ref]_ (NumpyDoc citation reference)
Pure docutils does not know about the Sphinx directives. Rather than
spinning up a full Sphinx build inside the Space, we:
1. Pre-process the docstring with regex substitutions that map
Sphinx-only directives to plain rST equivalents docutils can parse.
2. Hand the result to docutils.publish_parts for rST -> HTML.
3. Wrap with a small CSS style block that matches braindecode.org colors.
This keeps the Space dependency-light (no sphinx at runtime) while still
rendering figures, headings, parameter tables, and references.
"""
from __future__ import annotations
import inspect
import re
from textwrap import dedent
from docutils.core import publish_parts
from docutils.utils import SystemMessage
_BADGE_COLORS = {
"bdg-danger": "#d9534f",
"bdg-success": "#5cb85c",
"bdg-primary": "#0072B2",
"bdg-info": "#56B4E9",
"bdg-warning": "#E69F00",
"bdg-secondary": "#6c757d",
"bdg-light": "#f0f0f0",
"bdg-dark": "#343a40",
}
_BADGE_RE = re.compile(r":(bdg-[a-z]+):`([^`]+)`")
_VERSIONADDED_RE = re.compile(r"^\.\. versionadded::\s*(.+)$", re.MULTILINE)
_VERSIONCHANGED_RE = re.compile(r"^\.\. versionchanged::\s*(.+)$", re.MULTILINE)
_CODE_BLOCK_RE = re.compile(r"^(\s*)\.\. code-block::\s*(\w+)?\s*$", re.MULTILINE)
def _replace_badges(text: str) -> str:
"""Convert :bdg-danger:`Foundation Model` to inline raw HTML."""
def repl(match: re.Match) -> str:
cls, label = match.group(1), match.group(2)
color = _BADGE_COLORS.get(cls, "#888")
# Use rST raw HTML inline pass-through.
return (
f"\n\n.. raw:: html\n\n"
f" {label}\n\n"
)
return _BADGE_RE.sub(repl, text)
def _replace_versionadded(text: str) -> str:
"""Convert Sphinx versionadded/versionchanged to plain admonitions."""
text = _VERSIONADDED_RE.sub(
r".. note::\n\n *New in version \1.*", text
)
text = _VERSIONCHANGED_RE.sub(
r".. note::\n\n *Changed in version \1.*", text
)
return text
def _normalize_code_block(text: str) -> str:
"""Convert `.. code-block:: python` to vanilla `.. code::` (docutils-OK)."""
def repl(match: re.Match) -> str:
indent, lang = match.group(1), match.group(2) or ""
return f"{indent}.. code:: {lang}".rstrip()
return _CODE_BLOCK_RE.sub(repl, text)
def _strip_unsupported_directives(text: str) -> str:
"""Drop directives that docutils cannot parse and we do not want rendered.
Currently: rubric (treated as a small heading) and bibliography-only items.
"""
text = re.sub(r"^\.\. rubric::\s*(.+)$", r"\n**\1**\n", text, flags=re.MULTILINE)
return text
def preprocess_docstring(doc: str) -> str:
"""Apply all rST → docutils-friendly transformations."""
if not doc:
return ""
doc = dedent(doc)
doc = _replace_badges(doc)
doc = _replace_versionadded(doc)
doc = _normalize_code_block(doc)
doc = _strip_unsupported_directives(doc)
return doc
_STYLE = """
"""
def render_docstring_html(doc: str | None) -> str:
"""Render an rST docstring to a self-contained HTML fragment.
Returns a string with embedded