sanic/guide/webapp/display/markdown.py

136 lines
4.4 KiB
Python

import re
from textwrap import dedent
from html5tagger import HTML, Builder, E # type: ignore
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 .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)