From 123b6a9f34f5363823177505015fce700ff8b286 Mon Sep 17 00:00:00 2001 From: Crozzers Date: Sat, 27 Jan 2024 18:33:28 +0000 Subject: [PATCH] Refactor extras ordering system to use lists rather than number based ordering Lists let us have a theoreticaly infinite number of extras registered, with no overflowing into the next stage --- lib/markdown2.py | 170 +++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 86 deletions(-) diff --git a/lib/markdown2.py b/lib/markdown2.py index a20d2788..3f2a9f60 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -120,7 +120,8 @@ import functools from hashlib import sha256 from random import randint, random -from typing import Optional, Union +from typing import Iterable, Optional, Tuple, Union +from enum import Enum, auto # ---- globals @@ -180,87 +181,83 @@ def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH, use_file_vars=use_file_vars, cli=cli).convert(text) -class Stage(): - PREPROCESS = 50 - HASH_HTML = 150 - LINK_DEFS = 250 +class Stage: + PREPROCESS = 1 + HASH_HTML = 2 + LINK_DEFS = 3 - BLOCK_GAMUT = 350 - HEADERS = 450 - LISTS = 550 - CODE_BLOCKS = 650 - BLOCK_QUOTES = 750 - PARAGRAPHS = 850 + BLOCK_GAMUT = 4 + HEADERS = 5 + LISTS = 6 + CODE_BLOCKS = 7 + BLOCK_QUOTES = 8 + PARAGRAPHS = 9 - SPAN_GAMUT = 950 - CODE_SPANS = 1050 - ESCAPE_SPECIAL = 1150 - LINKS = 1250 # and auto links - ITALIC_AND_BOLD = 1350 + SPAN_GAMUT = 10 + CODE_SPANS = 11 + ESCAPE_SPECIAL = 12 + LINKS = 13 # and auto links + ITALIC_AND_BOLD = 14 - POSTPROCESS = 1450 - UNHASH_HTML = 1550 + POSTPROCESS = 15 + UNHASH_HTML = 16 - __counts = {} + _exec_order = {} @classmethod - def _order(cls, items, step=5): - index = 0 if step > 0 else 1 - ret = [] - counts = cls._Stage__counts - for item in items: + def _order(cls, extra: 'Extra', order: Optional[Tuple[list, list]] = None): + order = order or extra.order + + for index, item in enumerate((*order[0], *order[1])): + before = index < len(order[0]) if not isinstance(item, int) and issubclass(item, Extra): - ret.extend(o + step for o in item.order) + # eg: FencedCodeBlocks + for extras in cls._exec_order.values(): + # insert this extra everywhere the other one is mentioned + for section in extras: + if item in section: + to_index = section.index(item) + if not before: + to_index += 1 + section.insert(to_index, extra) else: - if item not in counts: - counts[item] = [0, 0] - counts[item][index] += step - ret.append(item + counts[item][index]) - return ret - - @classmethod - def after(cls, *items: 'Stage', step=5) -> list: - return cls._order(items, step) + # eg: Stage.PREPROCESS + cls._exec_order.setdefault(item, ([], [])) + if extra in cls._exec_order[item][0 if before else 1]: + # extra is already runnig after this stage. Don't duplicate that effort + continue + if before: + cls._exec_order[item][0].insert(0, extra) + else: + cls._exec_order[item][1].append(extra) @classmethod - def before(cls, *items: 'Stage', step=-5) -> list: - return cls._order(items, step) - - @staticmethod - def mark(stage): + def mark(cls, stage: 'Stage'): def wrapper(func): @functools.wraps(func) - def inner(self: 'Markdown', text, *args, **kwargs): - self.stage = stage - before = [] - after = [] + def inner(md: 'Markdown', text, *args, **kwargs): + md.stage = stage + md.order = stage - 0.5 - for extra in self.extras: - if extra not in self.extra_classes: - continue - klass = self.extra_classes[extra] - for order in klass.order: - if order // 100 == stage // 100: - if order < stage: - before.append((order, klass)) - else: - after.append((order, klass)) - - before.sort(key=lambda k: k[0]) - after.sort(key=lambda k: k[0]) - - for order, klass in before: - self.order = order - if klass.test(text): - text = klass.run(text) + if stage in cls._exec_order: + for klass in cls._exec_order[stage][0]: + if klass.name not in md.extra_classes: + continue + extra = md.extra_classes[klass.name] + if extra.test(text): + text = extra.run(text) - self.order = stage - text = func(self, text, *args, **kwargs) + md.order = stage + text = func(md, text, *args, **kwargs) + md.order = stage + 0.5 - for order, klass in after: - self.order = order - if klass.test(text): - text = klass.run(text) + if stage in cls._exec_order: + for klass in cls._exec_order[stage][1]: + if klass.name not in md.extra_classes: + continue + extra = md.extra_classes[klass.name] + if extra.test(text): + text = extra.run(text) return text @@ -2435,9 +2432,9 @@ class Extra(ABC): An identifiable name that users can use to invoke the extra in the Markdown class ''' - order: list + order: Tuple[list, list] ''' - A list of stages at which this extra should be invoked. + A tuple containing all the stages this extra runs before and after, respectively. See `Stage`, `Stage.before` and `Stage.after` ''' @@ -2464,6 +2461,7 @@ def register(cls): Registers the class for use with `Markdown` ''' cls._registry[cls.name] = cls + Stage._order(cls) @abstractmethod def run(self, text: str) -> str: @@ -2494,7 +2492,7 @@ class ItalicAndBoldProcessor(Extra): After the I&B stage any hashes in the `hash_tables` instance variable are replaced. ''' name = 'italic-and-bold-processor' - order = Stage.before(Stage.ITALIC_AND_BOLD) + Stage.after(Stage.ITALIC_AND_BOLD) + order = (Stage.ITALIC_AND_BOLD,), (Stage.ITALIC_AND_BOLD,) strong_re = Markdown._strong_re em_re = Markdown._em_re @@ -2539,7 +2537,7 @@ class Admonitions(Extra): ''' name = 'admonitions' - order = Stage.before(Stage.BLOCK_GAMUT, Stage.LINK_DEFS) + order = (Stage.BLOCK_GAMUT, Stage.LINK_DEFS), () admonitions = r'admonition|attention|caution|danger|error|hint|important|note|tip|warning' @@ -2589,7 +2587,7 @@ def run(self, text): class Breaks(Extra): name = 'breaks' - order = Stage.after(Stage.ITALIC_AND_BOLD) + order = (), (Stage.ITALIC_AND_BOLD,) def run(self, text): on_backslash = self.options.get('on_backslash', False) @@ -2635,7 +2633,7 @@ class FencedCodeBlocks(Extra): ''' name = 'fenced-code-blocks' - order = Stage.before(Stage.LINK_DEFS, Stage.BLOCK_GAMUT) + Stage.after(Stage.PREPROCESS) + order = (Stage.LINK_DEFS, Stage.BLOCK_GAMUT), (Stage.PREPROCESS,) fenced_code_block_re = re.compile(r''' (?:\n+|\A\n?|(?<=\n)) @@ -2729,7 +2727,7 @@ class LinkPatterns(Extra): references, revision number references). ''' name = 'link-patterns' - order = Stage.before(Stage.LINKS) + order = (Stage.LINKS,), () _basic_link_re = re.compile(r'!?\[.*?\]\(.*?\)') @@ -2802,7 +2800,7 @@ class MarkdownInHTML(Extra): some limitations. ''' name = 'markdown-in-html' - order = Stage.after(Stage.HASH_HTML) + order = (), (Stage.HASH_HTML,) def run(self, text): def callback(block): @@ -2819,7 +2817,7 @@ def test(self, text): class Mermaid(FencedCodeBlocks): name = 'mermaid' - order = Stage.before(FencedCodeBlocks) + order = (FencedCodeBlocks,), () def tags(self, lexer_name): if lexer_name == 'mermaid': @@ -2834,7 +2832,7 @@ class MiddleWordEm(ItalicAndBoldProcessor): converted to `thistexthere`. ''' name = 'middle-word-em' - order = Stage.before(CodeFriendly) + Stage.after(Stage.ITALIC_AND_BOLD) + order = (CodeFriendly,), (Stage.ITALIC_AND_BOLD,) def __init__(self, md: Markdown, options: Union[dict, bool]): ''' @@ -2884,7 +2882,7 @@ class Numbering(Extra): ''' name = 'numbering' - order = Stage.before(Stage.LINK_DEFS) + order = (Stage.LINK_DEFS,), () def test(self, text): return True @@ -2952,7 +2950,7 @@ class PyShell(Extra): ''' name = 'pyshell' - order = Stage.after(Stage.LISTS) + order = (), (Stage.LISTS,) def test(self, text): return ">>>" in text @@ -2988,7 +2986,7 @@ class SmartyPants(Extra): and ellipses. ''' name = 'smarty-pants' - order = Stage.after(Stage.SPAN_GAMUT) + order = (), (Stage.SPAN_GAMUT,) _opening_single_quote_re = re.compile(r"(?. ''' name = 'tables' - order = Stage.after(Stage.LISTS) + order = (), (Stage.LISTS,) def run(self, text): """Copying PHP-Markdown and GFM table syntax. Some regex borrowed from @@ -3154,7 +3152,7 @@ def test(self, text): class TelegramSpoiler(Extra): name = 'tg-spoiler' - order = Stage.after(Stage.ITALIC_AND_BOLD) + order = (), (Stage.ITALIC_AND_BOLD,) _tg_spoiler_re = re.compile(r"\|\|\s?(.+?)\s?\|\|", re.S) @@ -3170,7 +3168,7 @@ class Underline(Extra): Text inside of double dash is --underlined--. ''' name = 'underline' - order = Stage.before(Stage.ITALIC_AND_BOLD) + order = (Stage.ITALIC_AND_BOLD,), () _underline_re = re.compile(r"(?)(?=\S)(.+?)(?<=\S)(?)", re.S) @@ -3186,7 +3184,7 @@ class Wavedrom(Extra): Support for generating Wavedrom digital timing diagrams ''' name = 'wavedrom' - order = Stage.before(Stage.CODE_BLOCKS, FencedCodeBlocks) + Stage.after(Stage.PREPROCESS) + order = (Stage.CODE_BLOCKS, FencedCodeBlocks), () def test(self, text): match = FencedCodeBlocks.fenced_code_block_re.search(text) @@ -3227,7 +3225,7 @@ class WikiTables(Extra): . ''' name = 'wiki-tables' - order = Stage.before(Tables) + order = (Tables,), () def run(self, text): less_than_tab = self.md.tab_width - 1