151 أسطر
4.8 KiB
Python
151 أسطر
4.8 KiB
Python
import re
|
|
from textwrap import indent
|
|
from typing import Any, Dict, Iterable, cast
|
|
|
|
from ..core import BaseRenderer, BlockState
|
|
from ..util import strip_end
|
|
from ._list import render_list
|
|
|
|
fenced_re = re.compile(r"^[`~]+", re.M)
|
|
|
|
|
|
class MarkdownRenderer(BaseRenderer):
|
|
"""A renderer to re-format Markdown text."""
|
|
|
|
NAME = "markdown"
|
|
|
|
def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
|
|
out = self.render_tokens(tokens, state)
|
|
# special handle for line breaks
|
|
out += "\n\n".join(self.render_referrences(state)) + "\n"
|
|
return strip_end(out)
|
|
|
|
def render_referrences(self, state: BlockState) -> Iterable[str]:
|
|
ref_links = state.env["ref_links"]
|
|
for key in ref_links:
|
|
attrs = ref_links[key]
|
|
text = "[" + attrs["label"] + "]: " + attrs["url"]
|
|
title = attrs.get("title")
|
|
if title:
|
|
text += ' "' + title + '"'
|
|
yield text
|
|
|
|
def render_children(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
children = token["children"]
|
|
return self.render_tokens(children, state)
|
|
|
|
def text(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return cast(str, token["raw"])
|
|
|
|
def emphasis(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "*" + self.render_children(token, state) + "*"
|
|
|
|
def strong(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "**" + self.render_children(token, state) + "**"
|
|
|
|
def link(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
label = cast(str, token.get("label"))
|
|
text = self.render_children(token, state)
|
|
out = "[" + text + "]"
|
|
if label:
|
|
return out + "[" + label + "]"
|
|
|
|
attrs = token["attrs"]
|
|
url = attrs["url"]
|
|
title = attrs.get("title")
|
|
if text == url and not title:
|
|
return "<" + text + ">"
|
|
elif "mailto:" + text == url and not title:
|
|
return "<" + text + ">"
|
|
|
|
out += "("
|
|
if "(" in url or ")" in url:
|
|
out += "<" + url + ">"
|
|
else:
|
|
out += url
|
|
if title:
|
|
out += ' "' + title + '"'
|
|
return out + ")"
|
|
|
|
def image(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "!" + self.link(token, state)
|
|
|
|
def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "`" + cast(str, token["raw"]) + "`"
|
|
|
|
def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return " \n"
|
|
|
|
def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "\n"
|
|
|
|
def blank_line(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return ""
|
|
|
|
def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return cast(str, token["raw"])
|
|
|
|
def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
text = self.render_children(token, state)
|
|
return text + "\n\n"
|
|
|
|
def heading(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
level = cast(int, token["attrs"]["level"])
|
|
marker = "#" * level
|
|
text = self.render_children(token, state)
|
|
return marker + " " + text + "\n\n"
|
|
|
|
def thematic_break(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return "***\n\n"
|
|
|
|
def block_text(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return self.render_children(token, state) + "\n"
|
|
|
|
def block_code(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
attrs = token.get("attrs", {})
|
|
info = cast(str, attrs.get("info", ""))
|
|
code = cast(str, token["raw"])
|
|
if code and code[-1] != "\n":
|
|
code += "\n"
|
|
|
|
marker = token.get("marker")
|
|
if not marker:
|
|
marker = _get_fenced_marker(code)
|
|
marker2 = cast(str, marker)
|
|
return marker2 + info + "\n" + code + marker2 + "\n\n"
|
|
|
|
def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
text = indent(self.render_children(token, state), "> ", lambda _: True)
|
|
text = text.rstrip("> \n")
|
|
return text + "\n\n"
|
|
|
|
def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return cast(str, token["raw"]) + "\n\n"
|
|
|
|
def block_error(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return ""
|
|
|
|
def list(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return render_list(self, token, state)
|
|
|
|
|
|
def _get_fenced_marker(code: str) -> str:
|
|
found = fenced_re.findall(code)
|
|
if not found:
|
|
return "```"
|
|
|
|
ticks = [] # `
|
|
waves = [] # ~
|
|
for s in found:
|
|
if s[0] == "`":
|
|
ticks.append(len(s))
|
|
else:
|
|
waves.append(len(s))
|
|
|
|
if not ticks:
|
|
return "```"
|
|
|
|
if not waves:
|
|
return "~~~"
|
|
return "`" * (max(ticks) + 1)
|