diff options
author | Yuri Takhteyev <yuri@freewisdom.org> | 2008-05-02 10:33:47 -0700 |
---|---|---|
committer | Yuri Takhteyev <yuri@freewisdom.org> | 2008-05-02 10:33:47 -0700 |
commit | dc9e65bcdc7416c66a9cb2026616dc4cc1c9da3a (patch) | |
tree | 61dbe9a4143b99c0c9c197a51156b30e7a4e6324 | |
parent | a190c3941a7bed4274fe08e92ba4ce368c1c837f (diff) | |
download | markdown-dc9e65bcdc7416c66a9cb2026616dc4cc1c9da3a.tar.gz markdown-dc9e65bcdc7416c66a9cb2026616dc4cc1c9da3a.tar.bz2 markdown-dc9e65bcdc7416c66a9cb2026616dc4cc1c9da3a.zip |
Adding four extensions that weren't in source control before.
-rw-r--r-- | mdx_codehilite.py | 270 | ||||
-rw-r--r-- | mdx_imagelinks.py | 135 | ||||
-rw-r--r-- | mdx_tables.py | 65 | ||||
-rw-r--r-- | mdx_wikilink.py | 105 |
4 files changed, 575 insertions, 0 deletions
diff --git a/mdx_codehilite.py b/mdx_codehilite.py new file mode 100644 index 0000000..ea83e9a --- /dev/null +++ b/mdx_codehilite.py @@ -0,0 +1,270 @@ +#!/usr/bin/python +import markdown + +# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY ----------------- + +DEFAULT_HILITER = 'pygments' # one of 'enscript', 'dp', or 'pygments' +try: + TAB_LENGTH = markdown.TAB_LENGTH +except AttributeError: + TAB_LENGTH = 4 + +# --------------- THE CODE ------------------------------------------- +# --------------- hiliter utility functions -------------------------- +def escape(txt) : + '''basic html escaping''' + txt = txt.replace('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + +def number(txt): + '''use <ol> for line numbering''' + # Fix Whitespace + txt = txt.replace('\t', ' '*TAB_LENGTH) + txt = txt.replace(" "*4, " ") + txt = txt.replace(" "*3, " ") + txt = txt.replace(" "*2, " ") + + # Add line numbers + lines = txt.splitlines() + txt = '<div class="codehilite"><pre><ol>\n' + for line in lines: + txt += '\t<li>%s</li>\n'% line + txt += '</ol></pre></div>\n' + return txt + +# ---------------- The hiliters --------------------------------------- +def enscript(src, lang=None, num=True): + ''' +Pass source code on to [enscript] (http://www.codento.com/people/mtr/genscript/) +command line utility for hiliting. + +Usage: + >>> enscript(src [, lang [, num ]] ) + + @param src: Can be a string or any object with a .readline attribute. + + @param lang: The language of code. Basic escaping only, if None. + + @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default). + + @returns : A string of html. + ''' + if lang: + cmd = 'enscript --highlight=%s --color --language=html --tabsize=%d --output=-'% (lang, TAB_LENGTH) + from os import popen3 + (i, out, err) = popen3(cmd) + i.write(src) + i.close() + # check for errors + e = err.read() + if e != 'output left in -\n' : + # error - just escape + txt = escape(src) + else : + import re + pattern = re.compile(r'<PRE>(?P<code>.*?)</PRE>', re.DOTALL) + txt = pattern.search(out.read()).group('code') + # fix enscripts output + txt = txt.replace('\n</FONT></I>', '</FONT></I>\n').strip() + html_map = {'<I>' : '<em>', + '</I>' : '</em>', + '<B>' : '<strong>', + '</B>' : '</strong>', + '<FONT COLOR="#' : '<span style="color:#', + '</FONT>' : '</span>' + } + for k, v in html_map.items() : + txt = txt.replace(k, v) + else: + txt = escape(src) + if num : + txt = number(txt) + else : + txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt + return txt + + +def dp(src, lang=None, num=True): + ''' +Pass source code to a textarea for the [dp.SyntaxHighlighter] (http://www.dreamprojections.com/syntaxhighlighter/Default.aspx) + +Usage: + >>> dp(src [, lang [, num ]] ) + + @param src: A string. + + @param lang: The language of code. Undefined if None. + + @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default). + + @returns : A string of html. + ''' + gutter = '' + if not num: + gutter = ':nogutter' + if not lang: + lang = '' + + return '<div class="codehilite"><textarea name="code" class="%s%s" cols="60" rows="10">\n%s\n</textarea></div>\n'% (lang, gutter, src) + +def pygment(src, lang = None, num = True): + ''' +Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with +optional line numbers. The output should then be styled with css to your liking. +No styles are applied by default - only styling hooks (i.e.: <span class="k">). + +Usage: + >>> pygment(src [, lang [, num ]] ) + + @param src: Can be a string or any object with a .readline attribute. + + @param lang: The language of code. Pygments will try to guess language if None. + + @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default). + + @returns : A string of html. + ''' + try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer + from pygments.formatters import HtmlFormatter + except ImportError: + # just escape and pass through + txt = escape(src) + if num: + txt = number(txt) + else : + txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt + return txt + else: + try: + lexer = get_lexer_by_name(lang) + except ValueError: + try: + lexer = guess_lexer(src) + except ValueError: + lexer = TextLexer() + formatter = HtmlFormatter(linenos=num, cssclass="codehilite") + return highlight(src, lexer, formatter) + + +# ------------------ The Main CodeHilite Class ---------------------- +class CodeHilite: + ''' +A wrapper class providing a single API for various hilighting engines. Takes source code, determines which language it containes (if not provided), and passes it into the hiliter specified. + +Basic Usage: + >>> code = CodeHilite(src = text) + >>> html = code.hilite() + + @param src: Can be a string or any object with a .readline attribute. + + @param lang: A string. Accepted values determined by hiliter used. Overrides _getLang() + + @param linenos: (Boolen) Turns line numbering 'on' or 'off' (off by default). + + @param hiliter: A string. One of 'enscript', 'dp', or 'pygments'. + +Low Level Usage: + >>> code = CodeHilite() + >>> code.src = text # Can be a string or any object with a .readline attribute. + >>> code.lang = 'python' # Setting this will override _getLang() + >>> code.linenos = True # True or False; Turns line numbering on or off. + >>> code.hiliter = MyCustomHiliter # Where MyCustomHiliter is callable, takes three arguments (src, lang, linenos) and returns a string. + >>> html = code.hilite() + ''' + def __init__(self, src=None, lang=None, linenos = False, hiliter=DEFAULT_HILITER): + self.src = src + self.lang = lang + self.linenos = linenos + # map of highlighters + hl_map = { 'enscript' : enscript, 'dp' : dp, 'pygments' : pygment } + try : + self.hiliter = hl_map[hiliter] + except KeyError: + raise "Please provide a valid hiliter as a string. One of 'enscript', 'dp', or 'pygments'" + + + def _getLang(self): + ''' +Determines language of a code block from shebang lines and whether said line should be removed or left in place. If the sheband line contains a path (even a single /) then it is assumed to be a real shebang lines and left alone. However, if no path is given (e.i.: #!python or :::python) then it is assumed to be a mock shebang for language identifitation of a code fragment and removed from the code block prior to processing for code highlighting. When a mock shebang (e.i: #!python) is found, line numbering is turned on. When colons are found in place of a shebang (e.i.: :::python), line numbering is left in the current state - off by default. + ''' + import re + + #split text into lines + lines = self.src.split("\n") + #pull first line to examine + fl = lines.pop(0) + + c = re.compile(r''' + (?:(?:::+)|(?P<shebang>[#]!)) #shebang or 2 or more colons + (?P<path>(?:/\w+)*[/ ])? # zero or 1 path ending in either a / or a single space + (?P<lang>\w*) # the language (a single / or space before lang is a path) + ''', re.VERBOSE) + # search first line for shebang + m = c.search(fl) + if m: + # we have a match + try: + self.lang = m.group('lang').lower() + except IndexError: + self.lang = None + if m.group('path'): + # path exists - restore first line + lines.insert(0, fl) + if m.group('shebang'): + # shebang exists - use line numbers + self.linenos = True + else: + # No match + lines.insert(0, fl) + + self.src = "\n".join(lines).strip("\n") + + def hilite(self): + '''The wrapper function which brings it all togeather''' + self.src = self.src.strip('\n') + + if not self.lang : self._getLang() + + return self.hiliter(self.src, self.lang, self.linenos) + + +# ------------------ The Markdown Extention ------------------------------- +class CodeHiliteExtention (markdown.Extension) : + def __init__(self, configs): + # define default configs + self.config = {'hiliter' : [DEFAULT_HILITER, "one of 'enscript', 'dp', or 'pygments'"], + 'force_linenos' : [False, "Force line numbers - Default: False"] } + + # Override defaults with user settings + for key, value in configs : + # self.config[key][0] = value + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals) : + + def _hiliteCodeBlock(parent_elem, lines, inList): + """Overrides function of same name in standard Markdown class and + sends code blocks to a code highlighting proccessor. The result + is then stored in the HtmlStash, a placeholder is inserted into + the dom and the remainder of the text file is processed recursively. + + @param parent_elem: DOM element to which the content will be added + @param lines: a list of lines + @param inList: a level + @returns: None""" + detabbed, theRest = md.blockGuru.detectTabbed(lines) + text = "\n".join(detabbed).rstrip()+"\n" + code = CodeHilite(text, hiliter=self.config['hiliter'][0], linenos=self.config['force_linenos'][0]) + placeholder = md.htmlStash.store(code.hilite()) + parent_elem.appendChild(md.doc.createTextNode(placeholder)) + md._processSection(parent_elem, theRest, inList) + + md._processCodeBlock = _hiliteCodeBlock + +def makeExtension(configs=None) : + return CodeHiliteExtention(configs=configs) diff --git a/mdx_imagelinks.py b/mdx_imagelinks.py new file mode 100644 index 0000000..e545b24 --- /dev/null +++ b/mdx_imagelinks.py @@ -0,0 +1,135 @@ +""" +========================= IMAGE LINKS ================================= + + +Turns paragraphs like + +<~~~~~~~~~~~~~~~~~~~~~~~~ +dir/subdir +dir/subdir +dir/subdir +~~~~~~~~~~~~~~ +dir/subdir +dir/subdir +dir/subdir +~~~~~~~~~~~~~~~~~~~> + +Into mini-photo galleries. + +""" + +import re, markdown +import url_manager + + +IMAGE_LINK = """<a href="%s"><img src="%s" title="%s"/></a>""" +SLIDESHOW_LINK = """<a href="%s" target="_blank">[slideshow]</a>""" +ALBUM_LINK = """ <a href="%s">[%s]</a>""" + + +class ImageLinksExtension (markdown.Extension): + + def __init__ (self) : + self.reset() + + def extendMarkdown(self, md, md_globals) : + + self.md = md + + # Stateless extensions do not need to be registered + md.registerExtension(self) + + # Insert a preprocessor before all preprocessors + + preprocessor = ImageLinkPreprocessor() + preprocessor.md = md + md.preprocessors.insert(0, preprocessor) + + def reset(self) : + # May be called by Markdown is state reset is desired + pass + + +class ImageLinkPreprocessor (markdown.Preprocessor): + + def run(self, lines) : + + url = url_manager.BlogEntryUrl(url_manager.BlogUrl("all"), + "2006/08/29/the_rest_of_our") + + + all_images = [] + blocks = [] + in_image_block = False + + new_lines = [] + + for line in lines : + + if line.startswith("<~~~~~~~") : + albums = [] + rows = [] + in_image_block = True + + if not in_image_block : + + new_lines.append(line) + + else : + + line = line.strip() + + if line.endswith("~~~~~~>") or not line : + in_image_block = False + new_block = "<div><br/><center><span class='image-links'>\n" + + album_url_hash = {} + + for row in rows : + for photo_url, title in row : + new_block += " " + new_block += IMAGE_LINK % (photo_url, + photo_url.get_thumbnail(), + title) + + album_url_hash[str(photo_url.get_album())] = 1 + + new_block += "<br/>" + + new_block += "</span>" + new_block += SLIDESHOW_LINK % url.get_slideshow() + + album_urls = album_url_hash.keys() + album_urls.sort() + + if len(album_urls) == 1 : + new_block += ALBUM_LINK % (album_urls[0], "complete album") + else : + for i in range(len(album_urls)) : + new_block += ALBUM_LINK % (album_urls[i], + "album %d" % (i + 1) ) + + new_lines.append(new_block + "</center><br/></div>") + + elif line[1:6] == "~~~~~" : + rows.append([]) # start a new row + else : + parts = line.split() + line = parts[0] + title = " ".join(parts[1:]) + + album, photo = line.split("/") + photo_url = url.get_photo(album, photo, + len(all_images)+1) + all_images.append(photo_url) + rows[-1].append((photo_url, title)) + + if not album in albums : + albums.append(album) + + return new_lines + + +def makeExtension(configs) : + return ImageLinksExtension(configs) + diff --git a/mdx_tables.py b/mdx_tables.py new file mode 100644 index 0000000..c5c84a4 --- /dev/null +++ b/mdx_tables.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +""" +Table extension for Python-Markdown +""" + +import markdown + + +class TablePattern(markdown.Pattern) : + def __init__ (self, md): + markdown.Pattern.__init__(self, r'^\|([^\n]*)\|(\n|$)') + self.md = md + + def handleMatch(self, m, doc) : + # a single line represents a row + tr = doc.createElement('tr') + tr.appendChild(doc.createTextNode('\n')) + # chunks between pipes represent cells + for t in m.group(2).split('|'): + if len(t) >= 2 and t.startswith('*') and t.endswith('*'): + # if a cell is bounded by asterisks, it is a <th> + td = doc.createElement('th') + t = t[1:-1] + else: + # otherwise it is a <td> + td = doc.createElement('td') + # apply inline patterns on chunks + for n in self.md._handleInline(t): + if(type(n) == unicode): + td.appendChild(doc.createTextNode(n)) + else: + td.appendChild(n) + tr.appendChild(td) + # very long lines are evil + tr.appendChild(doc.createTextNode('\n')) + return tr + + +class TablePostprocessor: + def run(self, doc): + # markdown wrapped our <tr>s in a <p>, we fix that here + def test_for_p(element): + return element.type == 'element' and element.nodeName == 'p' + # replace "p > tr" with "table > tr" + for element in doc.find(test_for_p): + for node in element.childNodes: + if(node.type == 'text' and node.value.strip() == ''): + # skip leading whitespace + continue + if (node.type == 'element' and node.nodeName == 'tr'): + element.nodeName = 'table' + break + + +class TableExtension(markdown.Extension): + def extendMarkdown(self, md, md_globals): + md.inlinePatterns.insert(0, TablePattern(md)) + md.postprocessors.append(TablePostprocessor()) + + +def makeExtension(configs): + return TableExtension(configs) + + diff --git a/mdx_wikilink.py b/mdx_wikilink.py new file mode 100644 index 0000000..4279c1a --- /dev/null +++ b/mdx_wikilink.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +''' +WikiLink Extention for Python-Markdown +====================================== + +Converts CamelCase words to relative links. Requires Python-Markdown 1.6+ + +Basic usage: + + >>> import markdown + >>> text = "Some text with a WikiLink." + >>> md = markdown.markdown(text, ['wikilink']) + >>> md + '\\n<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.\\n</p>\\n\\n\\n' + +To define custom settings the simple way: + + >>> md = markdown.markdown(text, + ... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)'] + ... ) + >>> md + '\\n<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.\\n</p>\\n\\n\\n' + +Custom settings the complex way: + + >>> md = markdown.Markdown(text, + ... extensions = ['wikilink'], + ... extension_configs = {'wikilink': [ + ... ('base_url', 'http://example.com/'), + ... ('end_url', '.html'), + ... ('html_class', '') ]}, + ... encoding='utf8', + ... safe_mode = True) + >>> str(md) + '\\n<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>\\n\\n\\n' + +From the command line: + + python markdown.py -x wikilink(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt + +By [Waylan Limberg](http://achinghead.com/). + +Project website: http://achinghead.com/markdown-wikilinks/ +Contact: waylan [at] gmail [dot] com + +License: [BSD](http://www.opensource.org/licenses/bsd-license.php) + +Version: 0.4 (Oct 14, 2006) + +Dependencies: +* [Python 2.3+](http://python.org) +* [Markdown 1.6+](http://www.freewisdom.org/projects/python-markdown/) +* For older dependencies use [WikiLink Version 0.3] +(http://code.limberg.name/svn/projects/py-markdown-ext/wikilinks/tags/release-0.3/) +''' + +import markdown + +class WikiLinkExtension (markdown.Extension) : + def __init__(self, configs): + # set extension defaults + self.config = { + 'base_url' : ['/', 'String to append to beginning or URL.'], + 'end_url' : ['/', 'String to append to end of URL.'], + 'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'] + } + + # Override defaults with user settings + for key, value in configs : + # self.config[key][0] = value + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + self.md = md + #md.registerExtension(self) #??? + + # append to end of inline patterns + WIKILINK_RE = r'''(?P<escape>\\|\b)(?P<camelcase>([A-Z]+[a-z-_]+){2,})\b''' + md.inlinePatterns.append(WikiLinks(WIKILINK_RE, self.config)) + +class WikiLinks (markdown.BasePattern) : + def __init__(self, pattern, config): + markdown.BasePattern.__init__(self, pattern) + self.config = config + + def handleMatch(self, m, doc) : + if m.group('escape') == '\\': + a = doc.createTextNode(m.group('camelcase')) + else : + url = '%s%s%s'% (self.config['base_url'][0], m.group('camelcase'), self.config['end_url'][0]) + label = m.group('camelcase').replace('_', ' ') + a = doc.createElement('a') + a.appendChild(doc.createTextNode(label)) + a.setAttribute('href', url) + if self.config['html_class'][0] : + a.setAttribute('class', self.config['html_class'][0]) + return a + +def makeExtension(configs=None) : + return WikiLinkExtension(configs=configs) + +if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
\ No newline at end of file |