aboutsummaryrefslogtreecommitdiff
path: root/markdown/extensions/admonition.py
diff options
context:
space:
mode:
Diffstat (limited to 'markdown/extensions/admonition.py')
-rw-r--r--markdown/extensions/admonition.py170
1 files changed, 170 insertions, 0 deletions
diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py
new file mode 100644
index 0000000..cb8d901
--- /dev/null
+++ b/markdown/extensions/admonition.py
@@ -0,0 +1,170 @@
+"""
+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)