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('&', '&') 186 txt = txt.replace('<', '<') 187 txt = txt.replace('>', '>') 188 txt = txt.replace('"', '"') 189 return txt 190 191 192 def makeExtension(configs=None): 193 return FencedCodeExtension(configs=configs) 194