admonition.py revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1# markdown is released under the BSD license 2# Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) 3# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) 4# Copyright 2004 Manfred Stienstra (the original version) 5# 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions are met: 10# 11# * Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# * Neither the name of the <organization> nor the 17# names of its contributors may be used to endorse or promote products 18# derived from this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY 21# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT 24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30# POSSIBILITY OF SUCH DAMAGE. 31 32 33""" 34Admonition extension for Python-Markdown 35======================================== 36 37Adds rST-style admonitions. Inspired by [rST][] feature with the same name. 38 39The syntax is (followed by an indented block with the contents): 40 !!! [type] [optional explicit title] 41 42Where `type` is used as a CSS class name of the div. If not present, `title` 43defaults to the capitalized `type`, so "note" -> "Note". 44 45rST suggests the following `types`, but you're free to use whatever you want: 46 attention, caution, danger, error, hint, important, note, tip, warning 47 48 49A simple example: 50 !!! note 51 This is the first line inside the box. 52 53Outputs: 54 <div class="admonition note"> 55 <p class="admonition-title">Note</p> 56 <p>This is the first line inside the box</p> 57 </div> 58 59You can also specify the title and CSS class of the admonition: 60 !!! custom "Did you know?" 61 Another line here. 62 63Outputs: 64 <div class="admonition custom"> 65 <p class="admonition-title">Did you know?</p> 66 <p>Another line here.</p> 67 </div> 68 69[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions 70 71By [Tiago Serafim](http://www.tiagoserafim.com/). 72 73""" 74 75from __future__ import absolute_import 76from __future__ import unicode_literals 77from . import Extension 78from ..blockprocessors import BlockProcessor 79from ..util import etree 80import re 81 82 83class AdmonitionExtension(Extension): 84 """ Admonition extension for Python-Markdown. """ 85 86 def extendMarkdown(self, md, md_globals): 87 """ Add Admonition to Markdown instance. """ 88 md.registerExtension(self) 89 90 md.parser.blockprocessors.add('admonition', 91 AdmonitionProcessor(md.parser), 92 '_begin') 93 94 95class AdmonitionProcessor(BlockProcessor): 96 97 CLASSNAME = 'admonition' 98 CLASSNAME_TITLE = 'admonition-title' 99 RE = re.compile(r'(?:^|\n)!!!\ ?([\w\-]+)(?:\ "(.*?)")?') 100 101 def test(self, parent, block): 102 sibling = self.lastChild(parent) 103 return self.RE.search(block) or \ 104 (block.startswith(' ' * self.tab_length) and sibling and \ 105 sibling.get('class', '').find(self.CLASSNAME) != -1) 106 107 def run(self, parent, blocks): 108 sibling = self.lastChild(parent) 109 block = blocks.pop(0) 110 m = self.RE.search(block) 111 112 if m: 113 block = block[m.end() + 1:] # removes the first line 114 115 block, theRest = self.detab(block) 116 117 if m: 118 klass, title = self.get_class_and_title(m) 119 div = etree.SubElement(parent, 'div') 120 div.set('class', '%s %s' % (self.CLASSNAME, klass)) 121 if title: 122 p = etree.SubElement(div, 'p') 123 p.text = title 124 p.set('class', self.CLASSNAME_TITLE) 125 else: 126 div = sibling 127 128 self.parser.parseChunk(div, block) 129 130 if theRest: 131 # This block contained unindented line(s) after the first indented 132 # line. Insert these lines as the first block of the master blocks 133 # list for future processing. 134 blocks.insert(0, theRest) 135 136 def get_class_and_title(self, match): 137 klass, title = match.group(1).lower(), match.group(2) 138 if title is None: 139 # no title was provided, use the capitalized classname as title 140 # e.g.: `!!! note` will render `<p class="admonition-title">Note</p>` 141 title = klass.capitalize() 142 elif title == '': 143 # an explicit blank title should not be rendered 144 # e.g.: `!!! warning ""` will *not* render `p` with a title 145 title = None 146 return klass, title 147 148 149def makeExtension(configs={}): 150 return AdmonitionExtension(configs=configs) 151