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