From 83382bcefb246f1169b0d01375fcd86cd05b031e Mon Sep 17 00:00:00 2001 From: Tiago Serafim Date: Sun, 2 Sep 2012 20:12:53 -0300 Subject: Initial version with insufficient tests and no docs. --- markdown/extensions/admonition.py | 136 ++++++++++++++++++++++++++++++++++++++ tests/extensions/admonition.html | 19 ++++++ tests/extensions/admonition.txt | 17 +++++ tests/extensions/test.cfg | 3 + tests/test_extensions.py | 56 +++++++++++++--- 5 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 markdown/extensions/admonition.py create mode 100644 tests/extensions/admonition.html create mode 100644 tests/extensions/admonition.txt 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): + !!! [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: +
+

Note

+

This is the first line inside the box

+
+ +You can also specify the title and CSS class of the admonition: + !!! custom "Did you know?" + Another line here. + +Outputs: +
+

Did you know?

+

Another line here.

+
+ +[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 @@ +

Some text

+
+

Note

+

A normal paragraph here

+
    +
  1. first
  2. +
  3. second
  4. +
+
+

More text and stuff.

+
+

Did you know?

+

You can customize the title of the admonition

+
+
+

And now...

+

For something completely different.

+

You can also use a custom CSS class name.

+
\ 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), '

Some Header

') @@ -189,8 +189,8 @@ The body. This is paragraph one.''' self.assertEqual(self.md.convert(text), '

The body. This is paragraph one.

') 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)']), '

Some text with a ' 'WikiLink.

') - + 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]]'), '

foo

') +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), + '
\n' + '

Note

\n' + '

First line

\n' + '
\n' + '
\n' + '

Did you know?

\n' + '

Another text

\n
') -- cgit v1.2.3