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 "" 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("") 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)