Home | History | Annotate | Download | only in extensions
      1 #!/usr/bin/env python
      2 
      3 """
      4 Fenced Code Extension for Python Markdown
      5 =========================================
      6 
      7 This extension adds Fenced Code Blocks to Python-Markdown.
      8 
      9     >>> import markdown
     10     >>> text = '''
     11     ... A paragraph before a fenced code block:
     12     ... 
     13     ... ~~~
     14     ... Fenced code block
     15     ... ~~~
     16     ... '''
     17     >>> html = markdown.markdown(text, extensions=['fenced_code'])
     18     >>> html
     19     u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>'
     20 
     21 Works with safe_mode also (we check this because we are using the HtmlStash):
     22 
     23     >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
     24     u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>'
     25     
     26 Include tilde's in a code block and wrap with blank lines:
     27 
     28     >>> text = '''
     29     ... ~~~~~~~~
     30     ... 
     31     ... ~~~~
     32     ... 
     33     ... ~~~~~~~~'''
     34     >>> markdown.markdown(text, extensions=['fenced_code'])
     35     u'<pre><code>\\n~~~~\\n\\n</code></pre>'
     36 
     37 Multiple blocks and language tags:
     38 
     39     >>> text = '''
     40     ... ~~~~{.python}
     41     ... block one
     42     ... ~~~~
     43     ... 
     44     ... ~~~~.html
     45     ... <p>block two</p>
     46     ... ~~~~'''
     47     >>> markdown.markdown(text, extensions=['fenced_code'])
     48     u'<pre><code class="python">block one\\n</code></pre>\\n\\n<pre><code class="html">&lt;p&gt;block two&lt;/p&gt;\\n</code></pre>'
     49 
     50 Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
     51 
     52 Project website: <http://www.freewisdom.org/project/python-markdown/Fenced__Code__Blocks>
     53 Contact: markdown (at] freewisdom.org
     54 
     55 License: BSD (see ../docs/LICENSE for details) 
     56 
     57 Dependencies:
     58 * [Python 2.3+](http://python.org)
     59 * [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
     60 
     61 """
     62 
     63 import markdown, re
     64 
     65 # Global vars
     66 FENCED_BLOCK_RE = re.compile( \
     67     r'(?P<fence>^~{3,})[ ]*(\{?\.(?P<lang>[a-zA-Z0-9_-]*)\}?)?[ ]*\n(?P<code>.*?)(?P=fence)[ ]*$', 
     68     re.MULTILINE|re.DOTALL
     69     )
     70 CODE_WRAP = '<pre><code%s>%s</code></pre>'
     71 LANG_TAG = ' class="%s"'
     72 
     73 
     74 class FencedCodeExtension(markdown.Extension):
     75 
     76     def extendMarkdown(self, md, md_globals):
     77         """ Add FencedBlockPreprocessor to the Markdown instance. """
     78 
     79         md.preprocessors.add('fenced_code_block', 
     80                                  FencedBlockPreprocessor(md), 
     81                                  "_begin")
     82 
     83 
     84 class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
     85     
     86     def run(self, lines):
     87         """ Match and store Fenced Code Blocks in the HtmlStash. """
     88         text = "\n".join(lines)
     89         while 1:
     90             m = FENCED_BLOCK_RE.search(text)
     91             if m:
     92                 lang = ''
     93                 if m.group('lang'):
     94                     lang = LANG_TAG % m.group('lang')
     95                 code = CODE_WRAP % (lang, self._escape(m.group('code')))
     96                 placeholder = self.markdown.htmlStash.store(code, safe=True)
     97                 text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
     98             else:
     99                 break
    100         return text.split("\n")
    101 
    102     def _escape(self, txt):
    103         """ basic html escaping """
    104         txt = txt.replace('&', '&amp;')
    105         txt = txt.replace('<', '&lt;')
    106         txt = txt.replace('>', '&gt;')
    107         txt = txt.replace('"', '&quot;')
    108         return txt
    109 
    110 
    111 def makeExtension(configs=None):
    112     return FencedCodeExtension()
    113 
    114 
    115 if __name__ == "__main__":
    116     import doctest
    117     doctest.testmod()
    118