diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/markdown/extensions/admonition.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/markdown/extensions/admonition.py | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/markdown/extensions/admonition.py b/.venv/lib/python3.12/site-packages/markdown/extensions/admonition.py new file mode 100644 index 00000000..01c2316d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/markdown/extensions/admonition.py @@ -0,0 +1,183 @@ +# Admonition extension for Python-Markdown +# ======================================== + +# Adds rST-style admonitions. Inspired by [rST][] feature with the same name. + +# [rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions + +# See https://Python-Markdown.github.io/extensions/admonition +# for documentation. + +# Original code Copyright [Tiago Serafim](https://www.tiagoserafim.com/). + +# All changes Copyright The Python Markdown Project + +# License: [BSD](https://opensource.org/licenses/bsd-license.php) + + +""" +Adds rST-style admonitions. Inspired by [rST][] feature with the same name. + +[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions + +See the [documentation](https://Python-Markdown.github.io/extensions/admonition) +for details. +""" + +from __future__ import annotations + +from . import Extension +from ..blockprocessors import BlockProcessor +import xml.etree.ElementTree as etree +import re +from typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from markdown import blockparser + + +class AdmonitionExtension(Extension): + """ Admonition extension for Python-Markdown. """ + + def extendMarkdown(self, md): + """ Add Admonition to Markdown instance. """ + md.registerExtension(self) + + md.parser.blockprocessors.register(AdmonitionProcessor(md.parser), 'admonition', 105) + + +class AdmonitionProcessor(BlockProcessor): + + CLASSNAME = 'admonition' + CLASSNAME_TITLE = 'admonition-title' + RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)') + RE_SPACES = re.compile(' +') + + def __init__(self, parser: blockparser.BlockParser): + """Initialization.""" + + super().__init__(parser) + + self.current_sibling: etree.Element | None = None + self.content_indent = 0 + + def parse_content(self, parent: etree.Element, block: str) -> tuple[etree.Element | None, str, str]: + """Get sibling admonition. + + Retrieve the appropriate sibling element. This can get tricky when + dealing with lists. + + """ + + old_block = block + the_rest = '' + + # We already acquired the block via test + if self.current_sibling is not None: + sibling = self.current_sibling + block, the_rest = self.detab(block, self.content_indent) + self.current_sibling = None + self.content_indent = 0 + return sibling, block, the_rest + + sibling = self.lastChild(parent) + + if sibling is None or sibling.tag != 'div' or sibling.get('class', '').find(self.CLASSNAME) == -1: + sibling = None + else: + # If the last child is a list and the content is sufficiently indented + # to be under it, then the content's sibling is in the list. + last_child = self.lastChild(sibling) + indent = 0 + while last_child is not None: + if ( + sibling is not None and block.startswith(' ' * self.tab_length * 2) and + last_child is not None and last_child.tag in ('ul', 'ol', 'dl') + ): + + # The expectation is that we'll find an `<li>` or `<dt>`. + # We should get its last child as well. + sibling = self.lastChild(last_child) + last_child = self.lastChild(sibling) if sibling is not None else None + + # Context has been lost at this point, so we must adjust the + # text's indentation level so it will be evaluated correctly + # under the list. + block = block[self.tab_length:] + indent += self.tab_length + else: + last_child = None + + if not block.startswith(' ' * self.tab_length): + sibling = None + + if sibling is not None: + indent += self.tab_length + block, the_rest = self.detab(old_block, indent) + self.current_sibling = sibling + self.content_indent = indent + + return sibling, block, the_rest + + def test(self, parent: etree.Element, block: str) -> bool: + + if self.RE.search(block): + return True + else: + return self.parse_content(parent, block)[0] is not None + + def run(self, parent: etree.Element, blocks: list[str]) -> None: + block = blocks.pop(0) + m = self.RE.search(block) + + if m: + if m.start() > 0: + self.parser.parseBlocks(parent, [block[:m.start()]]) + block = block[m.end():] # removes the first line + block, theRest = self.detab(block) + else: + sibling, block, theRest = self.parse_content(parent, block) + + if m: + klass, title = self.get_class_and_title(m) + div = etree.SubElement(parent, 'div') + div.set('class', '{} {}'.format(self.CLASSNAME, klass)) + if title: + p = etree.SubElement(div, 'p') + p.text = title + p.set('class', self.CLASSNAME_TITLE) + else: + # Sibling is a list item, but we need to wrap it's content should be wrapped in <p> + if sibling.tag in ('li', 'dd') and sibling.text: + text = sibling.text + sibling.text = '' + p = etree.SubElement(sibling, 'p') + p.text = text + + div = sibling + + self.parser.parseChunk(div, block) + + if theRest: + # This block contained unindented line(s) after the first indented + # line. Insert these lines as the first block of the master blocks + # list for future processing. + blocks.insert(0, theRest) + + def get_class_and_title(self, match: re.Match[str]) -> tuple[str, str | None]: + klass, title = match.group(1).lower(), match.group(2) + klass = self.RE_SPACES.sub(' ', klass) + if title is None: + # no title was provided, use the capitalized class name as title + # e.g.: `!!! note` will render + # `<p class="admonition-title">Note</p>` + title = klass.split(' ', 1)[0].capitalize() + elif title == '': + # an explicit blank title should not be rendered + # e.g.: `!!! warning ""` will *not* render `p` with a title + title = None + return klass, title + + +def makeExtension(**kwargs): # pragma: no cover + return AdmonitionExtension(**kwargs) |