150 أسطر
5.4 KiB
Python
150 أسطر
5.4 KiB
Python
from textwrap import indent
|
|
from typing import Any, Dict, Iterable, List, cast
|
|
|
|
from ..core import BaseRenderer, BlockState
|
|
from ..util import strip_end
|
|
from ._list import render_list
|
|
|
|
|
|
class RSTRenderer(BaseRenderer):
|
|
"""A renderer for converting Markdown to ReST."""
|
|
|
|
NAME = "rst"
|
|
|
|
#: marker symbols for heading
|
|
HEADING_MARKERS = {
|
|
1: "=",
|
|
2: "-",
|
|
3: "~",
|
|
4: "^",
|
|
5: '"',
|
|
6: "'",
|
|
}
|
|
INLINE_IMAGE_PREFIX = "img-"
|
|
|
|
def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
|
|
prev = None
|
|
for tok in tokens:
|
|
# ignore blank line
|
|
if tok["type"] == "blank_line":
|
|
continue
|
|
tok["prev"] = prev
|
|
prev = tok
|
|
yield self.render_token(tok, state)
|
|
|
|
def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
|
|
state.env["inline_images"] = []
|
|
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]:
|
|
images = state.env["inline_images"]
|
|
for index, token in enumerate(images):
|
|
attrs = token["attrs"]
|
|
alt = self.render_children(token, state)
|
|
ident = self.INLINE_IMAGE_PREFIX + str(index)
|
|
yield ".. |" + ident + "| image:: " + attrs["url"] + "\n :alt: " + alt
|
|
|
|
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:
|
|
text = cast(str, token["raw"])
|
|
return text.replace("|", r"\|")
|
|
|
|
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:
|
|
attrs = token["attrs"]
|
|
text = self.render_children(token, state)
|
|
return "`" + text + " <" + cast(str, attrs["url"]) + ">`__"
|
|
|
|
def image(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
refs: List[Dict[str, Any]] = state.env["inline_images"]
|
|
index = len(refs)
|
|
refs.append(token)
|
|
return "|" + self.INLINE_IMAGE_PREFIX + str(index) + "|"
|
|
|
|
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 "<linebreak>"
|
|
|
|
def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
return " "
|
|
|
|
def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
# rst does not support inline html
|
|
return ""
|
|
|
|
def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
children = token["children"]
|
|
if len(children) == 1 and children[0]["type"] == "image":
|
|
image = children[0]
|
|
attrs = image["attrs"]
|
|
title = cast(str, attrs.get("title"))
|
|
alt = self.render_children(image, state)
|
|
text = ".. figure:: " + cast(str, attrs["url"])
|
|
if title:
|
|
text += "\n :alt: " + title
|
|
text += "\n\n" + indent(alt, " ")
|
|
else:
|
|
text = self.render_tokens(children, state)
|
|
lines = text.split("<linebreak>")
|
|
if len(lines) > 1:
|
|
text = "\n".join("| " + line for line in lines)
|
|
return text + "\n\n"
|
|
|
|
def heading(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
attrs = token["attrs"]
|
|
text = self.render_children(token, state)
|
|
marker = self.HEADING_MARKERS[attrs["level"]]
|
|
return text + "\n" + marker * len(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 = indent(cast(str, token["raw"]), " ")
|
|
if info:
|
|
lang = info.split()[0]
|
|
return ".. code:: " + lang + "\n\n" + code + "\n"
|
|
else:
|
|
return "::\n\n" + code + "\n\n"
|
|
|
|
def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
text = indent(self.render_children(token, state), " ")
|
|
prev = token["prev"]
|
|
ignore_blocks = (
|
|
"paragraph",
|
|
"thematic_break",
|
|
"linebreak",
|
|
"heading",
|
|
)
|
|
if prev and prev["type"] not in ignore_blocks:
|
|
text = "..\n\n" + text
|
|
return text
|
|
|
|
def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
|
|
raw = token["raw"]
|
|
return ".. raw:: html\n\n" + indent(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)
|