diff options
Diffstat (limited to 'src/mistune/core.py')
-rw-r--r-- | src/mistune/core.py | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/src/mistune/core.py b/src/mistune/core.py new file mode 100644 index 0000000..71db4dd --- /dev/null +++ b/src/mistune/core.py @@ -0,0 +1,208 @@ +import re + +_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): + child = self.__class__(self) + child.process(src) + return child + + def process(self, src): + 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): + return self.src[self.cursor:end_pos] + + def last_token(self): + if self.tokens: + return self.tokens[-1] + + def prepend_token(self, token): + """Insert token before the last token.""" + self.tokens.insert(len(self.tokens) - 1, token) + + def append_token(self, token): + """Add token to the end of token list.""" + self.tokens.append(token) + + def add_paragraph(self, text): + 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): + 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): + """Insert token before the last token.""" + self.tokens.insert(len(self.tokens) - 1, token) + + def append_token(self, token): + """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, 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, 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) |