Viewing file: admonition.py (5.71 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
""" 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 # noqa
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)
"""
from . import Extension from ..blockprocessors import BlockProcessor import xml.etree.ElementTree as etree import re
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): """Initialization."""
super().__init__(parser)
self.current_sibling = None self.content_indention = 0
def parse_content(self, parent, block): """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.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: if ( sibling and block.startswith(' ' * self.tab_length * 2) and last_child 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 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, block):
if self.RE.search(block): return True else: return self.parse_content(parent, block)[0] is not None
def run(self, parent, blocks): 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): 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 classname 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)
|