|
import re |
|
from typing import Dict, Any |
|
from textwrap import indent |
|
from ._list import render_list |
|
from ..core import BaseRenderer, BlockState |
|
from ..util import strip_end |
|
|
|
fenced_re = re.compile(r'^[`~]+', re.M) |
|
|
|
|
|
class MarkdownRenderer(BaseRenderer): |
|
"""A renderer to re-format Markdown text.""" |
|
NAME = 'markdown' |
|
|
|
def __call__(self, tokens, state: BlockState): |
|
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): |
|
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, state: BlockState): |
|
children = token['children'] |
|
return self.render_tokens(children, state) |
|
|
|
def text(self, token: Dict[str, Any], state: BlockState) -> str: |
|
return 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 = 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 '`' + 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 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 = 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 = attrs.get('info', '') |
|
code = token['raw'] |
|
if code and code[-1] != '\n': |
|
code += '\n' |
|
|
|
marker = token.get('marker') |
|
if not marker: |
|
marker = _get_fenced_marker(code) |
|
return marker + info + '\n' + code + marker + '\n\n' |
|
|
|
def block_quote(self, token: Dict[str, Any], state: BlockState) -> str: |
|
text = indent(self.render_children(token, state), '> ') |
|
return text + '\n\n' |
|
|
|
def block_html(self, token: Dict[str, Any], state: BlockState) -> str: |
|
return 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): |
|
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) |
|
|