File size: 6,034 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
import re
from typing import Dict, Any
_LINE_END = re.compile(r'\n|$')
class BlockState:
"""The state to save block parser's cursor and tokens."""
def __init__(self, parent=None):
self.src = ''
self.tokens = []
# current cursor position
self.cursor = 0
self.cursor_max = 0
# for list and block quote chain
self.list_tight = True
self.parent = parent
# for saving def references
if parent:
self.env = parent.env
else:
self.env = {'ref_links': {}}
def child_state(self, src: str):
child = self.__class__(self)
child.process(src)
return child
def process(self, src: str):
self.src = src
self.cursor_max = len(src)
def find_line_end(self):
m = _LINE_END.search(self.src, self.cursor)
return m.end()
def get_text(self, end_pos: int):
return self.src[self.cursor:end_pos]
def last_token(self):
if self.tokens:
return self.tokens[-1]
def prepend_token(self, token: Dict[str, Any]):
"""Insert token before the last token."""
self.tokens.insert(len(self.tokens) - 1, token)
def append_token(self, token: Dict[str, Any]):
"""Add token to the end of token list."""
self.tokens.append(token)
def add_paragraph(self, text: str):
last_token = self.last_token()
if last_token and last_token['type'] == 'paragraph':
last_token['text'] += text
else:
self.tokens.append({'type': 'paragraph', 'text': text})
def append_paragraph(self):
last_token = self.last_token()
if last_token and last_token['type'] == 'paragraph':
pos = self.find_line_end()
last_token['text'] += self.get_text(pos)
return pos
def depth(self):
d = 0
parent = self.parent
while parent:
d += 1
parent = parent.parent
return d
class InlineState:
"""The state to save inline parser's tokens."""
def __init__(self, env: Dict[str, Any]):
self.env = env
self.src = ''
self.tokens = []
self.in_image = False
self.in_link = False
self.in_emphasis = False
self.in_strong = False
def prepend_token(self, token: Dict[str, Any]):
"""Insert token before the last token."""
self.tokens.insert(len(self.tokens) - 1, token)
def append_token(self, token: Dict[str, Any]):
"""Add token to the end of token list."""
self.tokens.append(token)
def copy(self):
"""Create a copy of current state."""
state = self.__class__(self.env)
state.in_image = self.in_image
state.in_link = self.in_link
state.in_emphasis = self.in_emphasis
state.in_strong = self.in_strong
return state
class Parser:
sc_flag = re.M
state_cls = BlockState
SPECIFICATION = {}
DEFAULT_RULES = []
def __init__(self):
self.specification = self.SPECIFICATION.copy()
self.rules = list(self.DEFAULT_RULES)
self._methods = {}
self.__sc = {}
def compile_sc(self, rules=None):
if rules is None:
key = '$'
rules = self.rules
else:
key = '|'.join(rules)
sc = self.__sc.get(key)
if sc:
return sc
regex = '|'.join(r'(?P<%s>%s)' % (k, self.specification[k]) for k in rules)
sc = re.compile(regex, self.sc_flag)
self.__sc[key] = sc
return sc
def register(self, name: str, pattern, func, before=None):
"""Register a new rule to parse the token. This method is usually used to
create a new plugin.
:param name: name of the new grammar
:param pattern: regex pattern in string
:param func: the parsing function
:param before: insert this rule before a built-in rule
"""
self._methods[name] = lambda m, state: func(self, m, state)
if pattern:
self.specification[name] = pattern
if name not in self.rules:
self.insert_rule(self.rules, name, before=before)
def register_rule(self, name, pattern, func):
raise DeprecationWarning('This plugin is not compatible with mistune v3.')
@staticmethod
def insert_rule(rules, name, before=None):
if before:
try:
index = rules.index(before)
rules.insert(index, name)
except ValueError:
rules.append(name)
else:
rules.append(name)
def parse_method(self, m, state):
func = self._methods[m.lastgroup]
return func(m, state)
class BaseRenderer(object):
NAME = 'base'
def __init__(self):
self.__methods = {}
def register(self, name: str, method):
"""Register a render method for the named token. For example::
def render_wiki(renderer, key, title):
return f'<a href="/wiki/{key}">{title}</a>'
renderer.register('wiki', render_wiki)
"""
# bind self into renderer method
self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)
def _get_method(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError:
method = self.__methods.get(name)
if not method:
raise AttributeError('No renderer "{!r}"'.format(name))
return method
def render_token(self, token, state):
func = self._get_method(token['type'])
return func(token, state)
def iter_tokens(self, tokens, state):
for tok in tokens:
yield self.render_token(tok, state)
def render_tokens(self, tokens, state):
return ''.join(self.iter_tokens(tokens, state))
def __call__(self, tokens, state):
return self.render_tokens(tokens, state)
|