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""" 34CodeHilite Extension for Python-Markdown 35======================================== 36 37Adds code/syntax highlighting to standard Python-Markdown code blocks. 38 39Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). 40 41Project website: <http://packages.python.org/Markdown/extensions/code_hilite.html> 42Contact: markdown@freewisdom.org 43 44License: BSD (see ../LICENSE.md for details) 45 46Dependencies: 47* [Python 2.3+](http://python.org/) 48* [Markdown 2.0+](http://packages.python.org/Markdown/) 49* [Pygments](http://pygments.org/) 50 51""" 52 53from __future__ import absolute_import 54from __future__ import unicode_literals 55from . import Extension 56from ..treeprocessors import Treeprocessor 57import warnings 58try: 59 from pygments import highlight 60 from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer 61 from pygments.formatters import HtmlFormatter 62 pygments = True 63except ImportError: 64 pygments = False 65 66# ------------------ The Main CodeHilite Class ---------------------- 67class CodeHilite(object): 68 """ 69 Determine language of source code, and pass it into the pygments hilighter. 70 71 Basic Usage: 72 >>> code = CodeHilite(src = 'some text') 73 >>> html = code.hilite() 74 75 * src: Source string or any object with a .readline attribute. 76 77 * linenums: (Boolean) Set line numbering to 'on' (True), 'off' (False) or 'auto'(None). 78 Set to 'auto' by default. 79 80 * guess_lang: (Boolean) Turn language auto-detection 'on' or 'off' (on by default). 81 82 * css_class: Set class name of wrapper div ('codehilite' by default). 83 84 Low Level Usage: 85 >>> code = CodeHilite() 86 >>> code.src = 'some text' # String or anything with a .readline attr. 87 >>> code.linenos = True # True or False; Turns line numbering on or of. 88 >>> html = code.hilite() 89 90 """ 91 92 def __init__(self, src=None, linenums=None, guess_lang=True, 93 css_class="codehilite", lang=None, style='default', 94 noclasses=False, tab_length=4): 95 self.src = src 96 self.lang = lang 97 self.linenums = linenums 98 self.guess_lang = guess_lang 99 self.css_class = css_class 100 self.style = style 101 self.noclasses = noclasses 102 self.tab_length = tab_length 103 104 def hilite(self): 105 """ 106 Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with 107 optional line numbers. The output should then be styled with css to 108 your liking. No styles are applied by default - only styling hooks 109 (i.e.: <span class="k">). 110 111 returns : A string of html. 112 113 """ 114 115 self.src = self.src.strip('\n') 116 117 if self.lang is None: 118 self._getLang() 119 120 if pygments: 121 try: 122 lexer = get_lexer_by_name(self.lang) 123 except ValueError: 124 try: 125 if self.guess_lang: 126 lexer = guess_lexer(self.src) 127 else: 128 lexer = TextLexer() 129 except ValueError: 130 lexer = TextLexer() 131 formatter = HtmlFormatter(linenos=self.linenums, 132 cssclass=self.css_class, 133 style=self.style, 134 noclasses=self.noclasses) 135 return highlight(self.src, lexer, formatter) 136 else: 137 # just escape and build markup usable by JS highlighting libs 138 txt = self.src.replace('&', '&') 139 txt = txt.replace('<', '<') 140 txt = txt.replace('>', '>') 141 txt = txt.replace('"', '"') 142 classes = [] 143 if self.lang: 144 classes.append('language-%s' % self.lang) 145 if self.linenums: 146 classes.append('linenums') 147 class_str = '' 148 if classes: 149 class_str = ' class="%s"' % ' '.join(classes) 150 return '<pre class="%s"><code%s>%s</code></pre>\n'% \ 151 (self.css_class, class_str, txt) 152 153 def _getLang(self): 154 """ 155 Determines language of a code block from shebang line and whether said 156 line should be removed or left in place. If the sheband line contains a 157 path (even a single /) then it is assumed to be a real shebang line and 158 left alone. However, if no path is given (e.i.: #!python or :::python) 159 then it is assumed to be a mock shebang for language identifitation of a 160 code fragment and removed from the code block prior to processing for 161 code highlighting. When a mock shebang (e.i: #!python) is found, line 162 numbering is turned on. When colons are found in place of a shebang 163 (e.i.: :::python), line numbering is left in the current state - off 164 by default. 165 166 """ 167 168 import re 169 170 #split text into lines 171 lines = self.src.split("\n") 172 #pull first line to examine 173 fl = lines.pop(0) 174 175 c = re.compile(r''' 176 (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons. 177 (?P<path>(?:/\w+)*[/ ])? # Zero or 1 path 178 (?P<lang>[\w+-]*) # The language 179 ''', re.VERBOSE) 180 # search first line for shebang 181 m = c.search(fl) 182 if m: 183 # we have a match 184 try: 185 self.lang = m.group('lang').lower() 186 except IndexError: 187 self.lang = None 188 if m.group('path'): 189 # path exists - restore first line 190 lines.insert(0, fl) 191 if self.linenums is None and m.group('shebang'): 192 # Overridable and Shebang exists - use line numbers 193 self.linenums = True 194 else: 195 # No match 196 lines.insert(0, fl) 197 198 self.src = "\n".join(lines).strip("\n") 199 200 201 202# ------------------ The Markdown Extension ------------------------------- 203class HiliteTreeprocessor(Treeprocessor): 204 """ Hilight source code in code blocks. """ 205 206 def run(self, root): 207 """ Find code blocks and store in htmlStash. """ 208 blocks = root.getiterator('pre') 209 for block in blocks: 210 children = block.getchildren() 211 if len(children) == 1 and children[0].tag == 'code': 212 code = CodeHilite(children[0].text, 213 linenums=self.config['linenums'], 214 guess_lang=self.config['guess_lang'], 215 css_class=self.config['css_class'], 216 style=self.config['pygments_style'], 217 noclasses=self.config['noclasses'], 218 tab_length=self.markdown.tab_length) 219 placeholder = self.markdown.htmlStash.store(code.hilite(), 220 safe=True) 221 # Clear codeblock in etree instance 222 block.clear() 223 # Change to p element which will later 224 # be removed when inserting raw html 225 block.tag = 'p' 226 block.text = placeholder 227 228 229class CodeHiliteExtension(Extension): 230 """ Add source code hilighting to markdown codeblocks. """ 231 232 def __init__(self, configs): 233 # define default configs 234 self.config = { 235 'linenums': [None, "Use lines numbers. True=yes, False=no, None=auto"], 236 'force_linenos' : [False, "Depreciated! Use 'linenums' instead. Force line numbers - Default: False"], 237 'guess_lang' : [True, "Automatic language detection - Default: True"], 238 'css_class' : ["codehilite", 239 "Set class name for wrapper <div> - Default: codehilite"], 240 'pygments_style' : ['default', 'Pygments HTML Formatter Style (Colorscheme) - Default: default'], 241 'noclasses': [False, 'Use inline styles instead of CSS classes - Default false'] 242 } 243 244 # Override defaults with user settings 245 for key, value in configs: 246 # convert strings to booleans 247 if value == 'True': value = True 248 if value == 'False': value = False 249 if value == 'None': value = None 250 251 if key == 'force_linenos': 252 warnings.warn('The "force_linenos" config setting' 253 ' to the CodeHilite extension is deprecrecated.' 254 ' Use "linenums" instead.', PendingDeprecationWarning) 255 if value: 256 # Carry 'force_linenos' over to new 'linenos'. 257 self.setConfig('linenums', True) 258 259 self.setConfig(key, value) 260 261 def extendMarkdown(self, md, md_globals): 262 """ Add HilitePostprocessor to Markdown instance. """ 263 hiliter = HiliteTreeprocessor(md) 264 hiliter.config = self.getConfigs() 265 md.treeprocessors.add("hilite", hiliter, "<inline") 266 267 md.registerExtension(self) 268 269 270def makeExtension(configs={}): 271 return CodeHiliteExtension(configs=configs) 272 273