Home | History | Annotate | Download | only in extensions
      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 """
     34 Fenced Code Extension for Python Markdown
     35 =========================================
     36 
     37 This extension adds Fenced Code Blocks to Python-Markdown.
     38 
     39     >>> import markdown
     40     >>> text = '''
     41     ... A paragraph before a fenced code block:
     42     ...
     43     ... ~~~
     44     ... Fenced code block
     45     ... ~~~
     46     ... '''
     47     >>> html = markdown.markdown(text, extensions=['fenced_code'])
     48     >>> print html
     49     <p>A paragraph before a fenced code block:</p>
     50     <pre><code>Fenced code block
     51     </code></pre>
     52 
     53 Works with safe_mode also (we check this because we are using the HtmlStash):
     54 
     55     >>> print markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
     56     <p>A paragraph before a fenced code block:</p>
     57     <pre><code>Fenced code block
     58     </code></pre>
     59 
     60 Include tilde's in a code block and wrap with blank lines:
     61 
     62     >>> text = '''
     63     ... ~~~~~~~~
     64     ...
     65     ... ~~~~
     66     ... ~~~~~~~~'''
     67     >>> print markdown.markdown(text, extensions=['fenced_code'])
     68     <pre><code>
     69     ~~~~
     70     </code></pre>
     71 
     72 Language tags:
     73 
     74     >>> text = '''
     75     ... ~~~~{.python}
     76     ... # Some python code
     77     ... ~~~~'''
     78     >>> print markdown.markdown(text, extensions=['fenced_code'])
     79     <pre><code class="python"># Some python code
     80     </code></pre>
     81 
     82 Optionally backticks instead of tildes as per how github's code block markdown is identified:
     83 
     84     >>> text = '''
     85     ... `````
     86     ... # Arbitrary code
     87     ... ~~~~~ # these tildes will not close the block
     88     ... `````'''
     89     >>> print markdown.markdown(text, extensions=['fenced_code'])
     90     <pre><code># Arbitrary code
     91     ~~~~~ # these tildes will not close the block
     92     </code></pre>
     93 
     94 Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
     95 
     96 Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html>
     97 Contact: markdown (at] freewisdom.org
     98 
     99 License: BSD (see ../docs/LICENSE for details)
    100 
    101 Dependencies:
    102 * [Python 2.4+](http://python.org)
    103 * [Markdown 2.0+](http://packages.python.org/Markdown/)
    104 * [Pygments (optional)](http://pygments.org)
    105 
    106 """
    107 
    108 from __future__ import absolute_import
    109 from __future__ import unicode_literals
    110 from . import Extension
    111 from ..preprocessors import Preprocessor
    112 from .codehilite import CodeHilite, CodeHiliteExtension
    113 import re
    114 
    115 # Global vars
    116 FENCED_BLOCK_RE = re.compile( \
    117     r'(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*)\}?)?[ ]*\n(?P<code>.*?)(?<=\n)(?P=fence)[ ]*$',
    118     re.MULTILINE|re.DOTALL
    119     )
    120 CODE_WRAP = '<pre><code%s>%s</code></pre>'
    121 LANG_TAG = ' class="%s"'
    122 
    123 class FencedCodeExtension(Extension):
    124 
    125     def extendMarkdown(self, md, md_globals):
    126         """ Add FencedBlockPreprocessor to the Markdown instance. """
    127         md.registerExtension(self)
    128 
    129         md.preprocessors.add('fenced_code_block',
    130                                  FencedBlockPreprocessor(md),
    131                                  ">normalize_whitespace")
    132 
    133 
    134 class FencedBlockPreprocessor(Preprocessor):
    135 
    136     def __init__(self, md):
    137         super(FencedBlockPreprocessor, self).__init__(md)
    138 
    139         self.checked_for_codehilite = False
    140         self.codehilite_conf = {}
    141 
    142     def run(self, lines):
    143         """ Match and store Fenced Code Blocks in the HtmlStash. """
    144 
    145         # Check for code hilite extension
    146         if not self.checked_for_codehilite:
    147             for ext in self.markdown.registeredExtensions:
    148                 if isinstance(ext, CodeHiliteExtension):
    149                     self.codehilite_conf = ext.config
    150                     break
    151 
    152             self.checked_for_codehilite = True
    153 
    154         text = "\n".join(lines)
    155         while 1:
    156             m = FENCED_BLOCK_RE.search(text)
    157             if m:
    158                 lang = ''
    159                 if m.group('lang'):
    160                     lang = LANG_TAG % m.group('lang')
    161 
    162                 # If config is not empty, then the codehighlite extension
    163                 # is enabled, so we call it to highlite the code
    164                 if self.codehilite_conf:
    165                     highliter = CodeHilite(m.group('code'),
    166                             linenums=self.codehilite_conf['linenums'][0],
    167                             guess_lang=self.codehilite_conf['guess_lang'][0],
    168                             css_class=self.codehilite_conf['css_class'][0],
    169                             style=self.codehilite_conf['pygments_style'][0],
    170                             lang=(m.group('lang') or None),
    171                             noclasses=self.codehilite_conf['noclasses'][0])
    172 
    173                     code = highliter.hilite()
    174                 else:
    175                     code = CODE_WRAP % (lang, self._escape(m.group('code')))
    176 
    177                 placeholder = self.markdown.htmlStash.store(code, safe=True)
    178                 text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
    179             else:
    180                 break
    181         return text.split("\n")
    182 
    183     def _escape(self, txt):
    184         """ basic html escaping """
    185         txt = txt.replace('&', '&amp;')
    186         txt = txt.replace('<', '&lt;')
    187         txt = txt.replace('>', '&gt;')
    188         txt = txt.replace('"', '&quot;')
    189         return txt
    190 
    191 
    192 def makeExtension(configs=None):
    193     return FencedCodeExtension(configs=configs)
    194