aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuri Takhteyev <yuri@freewisdom.org>2008-05-02 10:33:47 -0700
committerYuri Takhteyev <yuri@freewisdom.org>2008-05-02 10:33:47 -0700
commitdc9e65bcdc7416c66a9cb2026616dc4cc1c9da3a (patch)
tree61dbe9a4143b99c0c9c197a51156b30e7a4e6324
parenta190c3941a7bed4274fe08e92ba4ce368c1c837f (diff)
downloadmarkdown-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.py270
-rw-r--r--mdx_imagelinks.py135
-rw-r--r--mdx_tables.py65
-rw-r--r--mdx_wikilink.py105
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('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+def number(txt):
+ '''use <ol> for line numbering'''
+ # Fix Whitespace
+ txt = txt.replace('\t', ' '*TAB_LENGTH)
+ txt = txt.replace(" "*4, "&nbsp; &nbsp; ")
+ txt = txt.replace(" "*3, "&nbsp; &nbsp;")
+ txt = txt.replace(" "*2, "&nbsp; ")
+
+ # 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 = """&nbsp;<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 += "&nbsp;"
+ 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