141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
|
import re
|
||
|
from textwrap import dedent
|
||
|
|
||
|
from mistune import HTMLRenderer, create_markdown, escape
|
||
|
from mistune.directives import RSTDirective, TableOfContents
|
||
|
from mistune.util import safe_entity
|
||
|
from pygments import highlight
|
||
|
from pygments.formatters import html
|
||
|
from pygments.lexers import get_lexer_by_name
|
||
|
|
||
|
from html5tagger import HTML, Builder, E # type: ignore
|
||
|
|
||
|
from .code_style import SanicCodeStyle
|
||
|
from .plugins.attrs import Attributes
|
||
|
from .plugins.columns import Column
|
||
|
from .plugins.hook import Hook
|
||
|
from .plugins.mermaid import Mermaid
|
||
|
from .plugins.notification import Notification
|
||
|
from .plugins.span import span
|
||
|
from .plugins.tabs import Tabs
|
||
|
from .text import slugify
|
||
|
|
||
|
|
||
|
class DocsRenderer(HTMLRenderer):
|
||
|
def block_code(self, code: str, info: str | None = None):
|
||
|
builder = Builder("Block")
|
||
|
with builder.div(class_="code-block"):
|
||
|
if info:
|
||
|
lexer = get_lexer_by_name(info, stripall=False)
|
||
|
formatter = html.HtmlFormatter(
|
||
|
style=SanicCodeStyle,
|
||
|
wrapcode=True,
|
||
|
cssclass=f"highlight language-{info}",
|
||
|
)
|
||
|
builder(HTML(highlight(code, lexer, formatter)))
|
||
|
with builder.div(
|
||
|
class_="code-block__copy",
|
||
|
onclick="copyCode(this)",
|
||
|
):
|
||
|
builder.div(
|
||
|
class_="code-block__rectangle code-block__filled"
|
||
|
).div(class_="code-block__rectangle code-block__outlined")
|
||
|
else:
|
||
|
builder.pre(E.code(escape(code)))
|
||
|
return str(builder)
|
||
|
|
||
|
def heading(self, text: str, level: int, **attrs) -> str:
|
||
|
ident = slugify(text)
|
||
|
if level > 1:
|
||
|
text += self._make_tag(
|
||
|
"a", {"href": f"#{ident}", "class": "anchor"}, "#"
|
||
|
)
|
||
|
return self._make_tag(
|
||
|
f"h{level}", {"id": ident, "class": f"is-size-{level}"}, text
|
||
|
)
|
||
|
|
||
|
def link(self, text: str, url: str, title: str | None = None) -> str:
|
||
|
url = self.safe_url(url).removesuffix(".md")
|
||
|
if not url.endswith("/"):
|
||
|
url += ".html"
|
||
|
|
||
|
attributes: dict[str, str] = {"href": url}
|
||
|
if title:
|
||
|
attributes["title"] = safe_entity(title)
|
||
|
if url.startswith("http"):
|
||
|
attributes["target"] = "_blank"
|
||
|
attributes["rel"] = "nofollow noreferrer"
|
||
|
else:
|
||
|
attributes["hx-get"] = url
|
||
|
attributes["hx-target"] = "#content"
|
||
|
attributes["hx-swap"] = "innerHTML"
|
||
|
attributes["hx-push-url"] = "true"
|
||
|
return self._make_tag("a", attributes, text)
|
||
|
|
||
|
def span(self, text, classes, **attrs) -> str:
|
||
|
if classes:
|
||
|
attrs["class"] = classes
|
||
|
return self._make_tag("span", attrs, text)
|
||
|
|
||
|
def list(self, text: str, ordered: bool, **attrs) -> str:
|
||
|
tag = "ol" if ordered else "ul"
|
||
|
attrs["class"] = tag
|
||
|
return self._make_tag(tag, attrs, text)
|
||
|
|
||
|
def list_item(self, text: str, **attrs) -> str:
|
||
|
attrs["class"] = "li"
|
||
|
return self._make_tag("li", attrs, text)
|
||
|
|
||
|
def table(self, text: str, **attrs) -> str:
|
||
|
attrs["class"] = "table is-fullwidth is-bordered"
|
||
|
return self._make_tag("table", attrs, text)
|
||
|
|
||
|
def _make_tag(
|
||
|
self, tag: str, attributes: dict[str, str], text: str | None = None
|
||
|
) -> str:
|
||
|
attrs = " ".join(
|
||
|
f'{key}="{value}"' for key, value in attributes.items()
|
||
|
)
|
||
|
if text is None:
|
||
|
return f"<{tag} {attrs} />"
|
||
|
return f"<{tag} {attrs}>{text}</{tag}>"
|
||
|
|
||
|
|
||
|
RST_CODE_BLOCK_PATTERN = re.compile(
|
||
|
r"\.\.\scode-block::\s(\w+)\n\n((?:\n|(?:\s\s\s\s[^\n]*))+)"
|
||
|
)
|
||
|
|
||
|
_render_markdown = create_markdown(
|
||
|
renderer=DocsRenderer(),
|
||
|
plugins=[
|
||
|
RSTDirective(
|
||
|
[
|
||
|
# Admonition(),
|
||
|
Attributes(),
|
||
|
Notification(),
|
||
|
TableOfContents(),
|
||
|
Column(),
|
||
|
Mermaid(),
|
||
|
Tabs(),
|
||
|
Hook(),
|
||
|
]
|
||
|
),
|
||
|
"abbr",
|
||
|
"def_list",
|
||
|
"footnotes",
|
||
|
"mark",
|
||
|
"table",
|
||
|
span,
|
||
|
],
|
||
|
)
|
||
|
|
||
|
|
||
|
def render_markdown(text: str) -> str:
|
||
|
def replacer(match):
|
||
|
language = match.group(1)
|
||
|
code_block = dedent(match.group(2)).strip()
|
||
|
return f"```{language}\n{code_block}\n```\n\n"
|
||
|
|
||
|
text = RST_CODE_BLOCK_PATTERN.sub(replacer, text)
|
||
|
return _render_markdown(text)
|