Commit 8fa2086580b678958dd292ddda57b799b9842cbd
- Diff rendering mode:
- inline
- side by side
pm/mdx_myhilite.py
(225 / 0)
|   | |||
| 1 | #!/usr/bin/python | ||
| 2 | |||
| 3 | """ | ||
| 4 | CodeHilite Extension for Python-Markdown | ||
| 5 | ======================================== | ||
| 6 | |||
| 7 | Adds code/syntax highlighting to standard Python-Markdown code blocks. | ||
| 8 | |||
| 9 | Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). | ||
| 10 | |||
| 11 | Project website: <http://www.freewisdom.org/project/python-markdown/CodeHilite> | ||
| 12 | Contact: markdown@freewisdom.org | ||
| 13 | |||
| 14 | License: BSD (see ../docs/LICENSE for details) | ||
| 15 | |||
| 16 | Dependencies: | ||
| 17 | * [Python 2.3+](http://python.org/) | ||
| 18 | * [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) | ||
| 19 | * [Pygments](http://pygments.org/) | ||
| 20 | |||
| 21 | Modified to disable automatic language detection | ||
| 22 | |||
| 23 | """ | ||
| 24 | |||
| 25 | import markdown | ||
| 26 | |||
| 27 | # --------------- CONSTANTS YOU MIGHT WANT TO MODIFY ----------------- | ||
| 28 | |||
| 29 | try: | ||
| 30 | TAB_LENGTH = markdown.TAB_LENGTH | ||
| 31 | except AttributeError: | ||
| 32 | TAB_LENGTH = 4 | ||
| 33 | |||
| 34 | |||
| 35 | # ------------------ The Main CodeHilite Class ---------------------- | ||
| 36 | class CodeHilite: | ||
| 37 | """ | ||
| 38 | Determine language of source code, and pass it into the pygments hilighter. | ||
| 39 | |||
| 40 | Basic Usage: | ||
| 41 | >>> code = CodeHilite(src = 'some text') | ||
| 42 | >>> html = code.hilite() | ||
| 43 | |||
| 44 | * src: Source string or any object with a .readline attribute. | ||
| 45 | |||
| 46 | * linenos: (Boolen) Turn line numbering 'on' or 'off' (off by default). | ||
| 47 | |||
| 48 | * css_class: Set class name of wrapper div ('codehilite' by default). | ||
| 49 | |||
| 50 | Low Level Usage: | ||
| 51 | >>> code = CodeHilite() | ||
| 52 | >>> code.src = 'some text' # String or anything with a .readline attr. | ||
| 53 | >>> code.linenos = True # True or False; Turns line numbering on or of. | ||
| 54 | >>> html = code.hilite() | ||
| 55 | |||
| 56 | """ | ||
| 57 | |||
| 58 | def __init__(self, src=None, linenos=False, css_class="codehilite"): | ||
| 59 | self.src = src | ||
| 60 | self.lang = None | ||
| 61 | self.linenos = linenos | ||
| 62 | self.css_class = css_class | ||
| 63 | |||
| 64 | def hilite(self): | ||
| 65 | """ | ||
| 66 | Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with | ||
| 67 | optional line numbers. The output should then be styled with css to | ||
| 68 | your liking. No styles are applied by default - only styling hooks | ||
| 69 | (i.e.: <span class="k">). | ||
| 70 | |||
| 71 | returns : A string of html. | ||
| 72 | |||
| 73 | """ | ||
| 74 | |||
| 75 | self.src = self.src.strip('\n') | ||
| 76 | |||
| 77 | self._getLang() | ||
| 78 | |||
| 79 | try: | ||
| 80 | from pygments import highlight | ||
| 81 | from pygments.lexers import get_lexer_by_name, guess_lexer, \ | ||
| 82 | TextLexer | ||
| 83 | from pygments.formatters import HtmlFormatter | ||
| 84 | except ImportError: | ||
| 85 | # just escape and pass through | ||
| 86 | txt = self._escape(self.src) | ||
| 87 | if self.linenos: | ||
| 88 | txt = self._number(txt) | ||
| 89 | else : | ||
| 90 | txt = '<div class="%s"><pre>%s</pre></div>\n'% \ | ||
| 91 | (self.css_class, txt) | ||
| 92 | return txt | ||
| 93 | else: | ||
| 94 | try: | ||
| 95 | lexer = get_lexer_by_name(self.lang) | ||
| 96 | except ValueError: | ||
| 97 | # try: | ||
| 98 | # lexer = guess_lexer(self.src) | ||
| 99 | # except ValueError: | ||
| 100 | lexer = TextLexer() | ||
| 101 | formatter = HtmlFormatter(linenos=self.linenos, | ||
| 102 | cssclass=self.css_class) | ||
| 103 | return highlight(self.src, lexer, formatter) | ||
| 104 | |||
| 105 | def _escape(self, txt): | ||
| 106 | """ basic html escaping """ | ||
| 107 | txt = txt.replace('&', '&') | ||
| 108 | txt = txt.replace('<', '<') | ||
| 109 | txt = txt.replace('>', '>') | ||
| 110 | txt = txt.replace('"', '"') | ||
| 111 | return txt | ||
| 112 | |||
| 113 | def _number(self, txt): | ||
| 114 | """ Use <ol> for line numbering """ | ||
| 115 | # Fix Whitespace | ||
| 116 | txt = txt.replace('\t', ' '*TAB_LENGTH) | ||
| 117 | txt = txt.replace(" "*4, " ") | ||
| 118 | txt = txt.replace(" "*3, " ") | ||
| 119 | txt = txt.replace(" "*2, " ") | ||
| 120 | |||
| 121 | # Add line numbers | ||
| 122 | lines = txt.splitlines() | ||
| 123 | txt = '<div class="codehilite"><pre><ol>\n' | ||
| 124 | for line in lines: | ||
| 125 | txt += '\t<li>%s</li>\n'% line | ||
| 126 | txt += '</ol></pre></div>\n' | ||
| 127 | return txt | ||
| 128 | |||
| 129 | |||
| 130 | def _getLang(self): | ||
| 131 | """ | ||
| 132 | Determines language of a code block from shebang lines and whether said | ||
| 133 | line should be removed or left in place. If the sheband line contains a | ||
| 134 | path (even a single /) then it is assumed to be a real shebang lines and | ||
| 135 | left alone. However, if no path is given (e.i.: #!python or :::python) | ||
| 136 | then it is assumed to be a mock shebang for language identifitation of a | ||
| 137 | code fragment and removed from the code block prior to processing for | ||
| 138 | code highlighting. When a mock shebang (e.i: #!python) is found, line | ||
| 139 | numbering is turned on. When colons are found in place of a shebang | ||
| 140 | (e.i.: :::python), line numbering is left in the current state - off | ||
| 141 | by default. | ||
| 142 | |||
| 143 | """ | ||
| 144 | |||
| 145 | import re | ||
| 146 | |||
| 147 | #split text into lines | ||
| 148 | lines = self.src.split("\n") | ||
| 149 | #pull first line to examine | ||
| 150 | fl = lines.pop(0) | ||
| 151 | |||
| 152 | c = re.compile(r''' | ||
| 153 | (?:(?:::+)|(?P<shebang>[#]!)) # Shebang or 2 or more colons. | ||
| 154 | (?P<path>(?:/\w+)*[/ ])? # Zero or 1 path | ||
| 155 | (?P<lang>[\w+-]*) # The language | ||
| 156 | ''', re.VERBOSE) | ||
| 157 | # search first line for shebang | ||
| 158 | m = c.search(fl) | ||
| 159 | if m: | ||
| 160 | # we have a match | ||
| 161 | try: | ||
| 162 | self.lang = m.group('lang').lower() | ||
| 163 | except IndexError: | ||
| 164 | self.lang = None | ||
| 165 | if m.group('path'): | ||
| 166 | # path exists - restore first line | ||
| 167 | lines.insert(0, fl) | ||
| 168 | if m.group('shebang'): | ||
| 169 | # shebang exists - use line numbers | ||
| 170 | self.linenos = True | ||
| 171 | else: | ||
| 172 | # No match | ||
| 173 | lines.insert(0, fl) | ||
| 174 | |||
| 175 | self.src = "\n".join(lines).strip("\n") | ||
| 176 | |||
| 177 | |||
| 178 | |||
| 179 | # ------------------ The Markdown Extension ------------------------------- | ||
| 180 | class HiliteTreeprocessor(markdown.treeprocessors.Treeprocessor): | ||
| 181 | """ Hilight source code in code blocks. """ | ||
| 182 | |||
| 183 | def run(self, root): | ||
| 184 | """ Find code blocks and store in htmlStash. """ | ||
| 185 | blocks = root.getiterator('pre') | ||
| 186 | for block in blocks: | ||
| 187 | children = block.getchildren() | ||
| 188 | if len(children) == 1 and children[0].tag == 'code': | ||
| 189 | code = CodeHilite(children[0].text, | ||
| 190 | linenos=self.config['force_linenos'][0], | ||
| 191 | css_class=self.config['css_class'][0]) | ||
| 192 | placeholder = self.markdown.htmlStash.store(code.hilite(), | ||
| 193 | safe=True) | ||
| 194 | # Clear codeblock in etree instance | ||
| 195 | block.clear() | ||
| 196 | # Change to p element which will later | ||
| 197 | # be removed when inserting raw html | ||
| 198 | block.tag = 'p' | ||
| 199 | block.text = placeholder | ||
| 200 | |||
| 201 | |||
| 202 | class CodeHiliteExtension(markdown.Extension): | ||
| 203 | """ Add source code hilighting to markdown codeblocks. """ | ||
| 204 | |||
| 205 | def __init__(self, configs): | ||
| 206 | # define default configs | ||
| 207 | self.config = { | ||
| 208 | 'force_linenos' : [False, "Force line numbers - Default: False"], | ||
| 209 | 'css_class' : ["codehilite", | ||
| 210 | "Set class name for wrapper <div> - Default: codehilite"], | ||
| 211 | } | ||
| 212 | |||
| 213 | # Override defaults with user settings | ||
| 214 | for key, value in configs: | ||
| 215 | self.setConfig(key, value) | ||
| 216 | |||
| 217 | def extendMarkdown(self, md, md_globals): | ||
| 218 | """ Add HilitePostprocessor to Markdown instance. """ | ||
| 219 | hiliter = HiliteTreeprocessor(md) | ||
| 220 | hiliter.config = self.config | ||
| 221 | md.treeprocessors.add("hilite", hiliter, "_begin") | ||
| 222 | |||
| 223 | |||
| 224 | def makeExtension(configs={}): | ||
| 225 | return CodeHiliteExtension(configs=configs) |
|   | |||
| 3 | 3 | ||
| 4 | 4 | register = template.Library() | |
| 5 | 5 | ||
| 6 | markdown_opts = "safe,codehilite,extra" | ||
| 6 | markdown_opts = "safe,myhilite,extra" | ||
| 7 | 7 | ||
| 8 | 8 | def markext(value, arg=''): | |
| 9 | 9 | return markdown(value, markdown_opts) |

