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"><p>block two</p>\\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('&', '&') 105 txt = txt.replace('<', '<') 106 txt = txt.replace('>', '>') 107 txt = txt.replace('"', '"') 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