aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--markdown/extensions/admonition.py136
-rw-r--r--tests/extensions/admonition.html19
-rw-r--r--tests/extensions/admonition.txt17
-rw-r--r--tests/extensions/test.cfg3
-rw-r--r--tests/test_extensions.py56
5 files changed, 221 insertions, 10 deletions
diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py
new file mode 100644
index 0000000..d8a95d3
--- /dev/null
+++ b/markdown/extensions/admonition.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+"""
+Admonition extension for Python-Markdown
+========================================
+
+Adds rST-style admonitions. Inspired by [rST][] feature with the same name.
+
+The syntax is (followed by an indented block with the contents):
+ !!! <type> [optional title]
+
+Where `type` is one of the followings:
+ attention, caution, danger, error, hint, important, note, tip, warning
+
+The above types have the appropriated title by default (i.e: note => Note)
+
+It's also possible to create custom types (with default CSS classes and Titles)
+see the docs for more info.
+
+A simple example:
+ !!! note
+ This is the first line inside the box.
+
+Outputs:
+ <div class="admonition note">
+ <p class="admonition-title">Note</p>
+ <p>This is the first line inside the box</p>
+ </div>
+
+You can also specify the title and CSS class of the admonition:
+ !!! custom "Did you know?"
+ Another line here.
+
+Outputs:
+ <div class="admonition custom">
+ <p class="admonition-title">Did you know?</p>
+ <p>Another line here.</p>
+ </div>
+
+[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
+
+By [Tiago Serafim](http://www.tiagoserafim.com/).
+
+"""
+
+import re
+import markdown
+from markdown.util import etree
+
+DEFAULT_STYLES = {
+ # 'id': ('CSS_class', 'Display Name')
+ 'attention': ('attention', 'Attention'),
+ 'caution': ('caution', 'Caution'),
+ 'danger': ('danger', 'Danger'),
+ 'error': ('error', 'Error'),
+ 'hint': ('hint', 'Hint'),
+ 'important': ('important', 'Important'),
+ 'note': ('note', 'Note'),
+ 'tip': ('tip', 'Tip'),
+ 'warning': ('warning', 'Warning'),
+}
+
+
+class AdmonitionExtension(markdown.Extension):
+ """ Admonition extension for Python-Markdown. """
+
+ def __init__(self, configs):
+ self.config = {
+ 'styles': DEFAULT_STYLES.copy(),
+ }
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add Admonition to Markdown instance. """
+ md.registerExtension(self)
+
+ md.parser.blockprocessors.add('admonition',
+ AdmonitionProcessor(md.parser, self.config),
+ '_begin')
+
+
+class AdmonitionProcessor(markdown.blockprocessors.BlockProcessor):
+
+ CLASSNAME = 'admonition'
+ CLASSNAME_TITLE = 'admonition-title'
+ RE = re.compile(r'(?:^|\n)!!!\ ?([\w\-]+)(?:\ "?([^"\n]+)"?)?')
+
+ def __init__(self, parser, config):
+ markdown.blockprocessors.BlockProcessor.__init__(self, parser)
+ self.config = config
+
+ def test(self, parent, block):
+ sibling = self.lastChild(parent)
+ return self.RE.search(block) or \
+ (block.startswith(' ' * self.tab_length) and sibling and \
+ sibling.get('class', '').find(self.CLASSNAME) != -1)
+
+ def run(self, parent, blocks):
+ sibling = self.lastChild(parent)
+ block = blocks.pop(0)
+ m = self.RE.search(block)
+
+ if m:
+ block = block[m.end() + 1:] # removes the first line
+
+ block, theRest = self.detab(block)
+
+ if m:
+ klass, title = self.get_class_and_title(m.group(1), m.group(2))
+ div = etree.SubElement(parent, 'div')
+ div.set('class', u'%s %s' % (self.CLASSNAME, klass))
+ if title:
+ p = etree.SubElement(div, 'p')
+ p.text = title
+ p.set('class', self.CLASSNAME_TITLE)
+ else:
+ 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, klass, title):
+ styles = self.config['styles']
+ style = styles.get(klass, None)
+ if style:
+ return style[0], title or style[1]
+ else:
+ return klass, title
+
+
+def makeExtension(configs={}):
+ return AdmonitionExtension(configs=configs)
diff --git a/tests/extensions/admonition.html b/tests/extensions/admonition.html
new file mode 100644
index 0000000..f28bcfd
--- /dev/null
+++ b/tests/extensions/admonition.html
@@ -0,0 +1,19 @@
+<p>Some text</p>
+<div class="admonition note">
+<p class="admonition-title">Note</p>
+<p>A normal paragraph here</p>
+<ol>
+<li>first</li>
+<li>second</li>
+</ol>
+</div>
+<p>More text and stuff.</p>
+<div class="admonition note">
+<p class="admonition-title">Did you know?</p>
+<p>You can customize the title of the admonition</p>
+</div>
+<div class="admonition mycustomcssclass">
+<p class="admonition-title">And now...</p>
+<p>For something completely different.</p>
+<p>You can also use a custom CSS class name.</p>
+</div> \ No newline at end of file
diff --git a/tests/extensions/admonition.txt b/tests/extensions/admonition.txt
new file mode 100644
index 0000000..e8db239
--- /dev/null
+++ b/tests/extensions/admonition.txt
@@ -0,0 +1,17 @@
+Some text
+
+!!! note
+ A normal paragraph here
+
+ 1. first
+ 2. second
+
+More text and stuff.
+
+!!! note "Did you know?"
+ You can customize the title of the admonition
+
+!!! mycustomcssclass "And now..."
+ For something completely different.
+
+ You can also use a custom CSS class name.
diff --git a/tests/extensions/test.cfg b/tests/extensions/test.cfg
index ac8a747..e83aa62 100644
--- a/tests/extensions/test.cfg
+++ b/tests/extensions/test.cfg
@@ -29,3 +29,6 @@ extensions=fenced_code
[sane_lists]
extensions=sane_lists
+
+[admonition]
+extensions=admonition
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index 15feb6b..ca1520e 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -2,7 +2,7 @@
Python-Markdown Extension Regression Tests
==========================================
-A collection of regression tests to confirm that the included extensions
+A collection of regression tests to confirm that the included extensions
continue to work as advertised. This used to be accomplished by doctests.
"""
@@ -59,7 +59,7 @@ class TestFencedCode(unittest.TestCase):
def testBasicFence(self):
""" Test Fenced Code Blocks. """
- text = '''
+ text = '''
A paragraph before a fenced code block:
~~~
@@ -123,7 +123,7 @@ class TestHeaderId(unittest.TestCase):
def testBasicHeaderId(self):
""" Test Basic HeaderID """
-
+
text = "# Some Header #"
self.assertEqual(self.md.convert(text),
'<h1 id="some-header">Some Header</h1>')
@@ -189,8 +189,8 @@ The body. This is paragraph one.'''
self.assertEqual(self.md.convert(text),
'<p>The body. This is paragraph one.</p>')
self.assertEqual(self.md.Meta,
- {'author': ['Waylan Limberg', 'John Doe'],
- 'blank_data': [''],
+ {'author': ['Waylan Limberg', 'John Doe'],
+ 'blank_data': [''],
'title': ['A Test Doc.']})
def testMissingMetaData(self):
@@ -226,18 +226,18 @@ class TestWikiLinks(unittest.TestCase):
def testSimpleSettings(self):
""" Test Simple Settings. """
- self.assertEqual(markdown.markdown(self.text,
+ self.assertEqual(markdown.markdown(self.text,
['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']),
'<p>Some text with a '
'<a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>')
-
+
def testComplexSettings(self):
""" Test Complex Settings. """
md = markdown.Markdown(
- extensions = ['wikilinks'],
+ extensions = ['wikilinks'],
extension_configs = {'wikilinks': [
- ('base_url', 'http://example.com/'),
+ ('base_url', 'http://example.com/'),
('end_url', '.html'),
('html_class', '') ]},
safe_mode = True)
@@ -268,8 +268,44 @@ Some text with a [[WikiLink]]."""
def my_url_builder(label, base, end):
return '/bar/'
- md = markdown.Markdown(extensions=['wikilinks'],
+ md = markdown.Markdown(extensions=['wikilinks'],
extension_configs={'wikilinks' : [('build_url', my_url_builder)]})
self.assertEqual(md.convert('[[foo]]'),
'<p><a class="wikilink" href="/bar/">foo</a></p>')
+class TestAdmonition(unittest.TestCase):
+ """ Test Admonition Extension. """
+
+ def setUp(self):
+ self.md = markdown.Markdown(extensions=['admonition'])
+ self.text = \
+'''!!! note
+ First line
+
+!!! didyouknow "Did you know?"
+ Another text'''
+
+ def testComplexSettings(self):
+ """ Test Complex Settings. """
+
+ # config = {
+ # 'styles': {
+ # 'note': ('note', 'Please Note'),
+ # 'didyouknow': ('note', 'Did you know?'),
+ # }
+ # }
+
+ md = markdown.Markdown(
+ extensions=['admonition'],
+ extension_configs={
+ 'admonition': []
+ },
+ safe_mode=True)
+ self.assertEqual(md.convert(self.text),
+ '<div class="admonition note">\n'
+ '<p class="admonition-title">Note</p>\n'
+ '<p>First line</p>\n'
+ '</div>\n'
+ '<div class="admonition didyouknow">\n'
+ '<p class="admonition-title">Did you know?</p>\n'
+ '<p>Another text</p>\n</div>')