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