summaryrefslogtreecommitdiff
path: root/src/contrib/markdown/extensions
diff options
context:
space:
mode:
authorR. Tyler Ballance <tyler@slide.com>2009-04-16 22:32:41 -0700
committerR. Tyler Ballance <tyler@slide.com>2009-04-16 22:32:41 -0700
commit45f570f03abfbe929a14a7c23e2aa2a39ce152c5 (patch)
tree992e83fd05db4ab0231ec1a2257ba45b744376c5 /src/contrib/markdown/extensions
parent2fc2c598e3ab45631739fe0f582fbfc18573b417 (diff)
parent1c42c232c419001116a099e4a49032231fff38fe (diff)
downloadpython-cheetah-45f570f03abfbe929a14a7c23e2aa2a39ce152c5.tar.gz
Merge branch 'next' of git@github.com:rtyler/cheetahv2.1.1
Diffstat (limited to 'src/contrib/markdown/extensions')
-rw-r--r--src/contrib/markdown/extensions/__init__.py0
-rw-r--r--src/contrib/markdown/extensions/abbr.py95
-rw-r--r--src/contrib/markdown/extensions/codehilite.py224
-rw-r--r--src/contrib/markdown/extensions/def_list.py104
-rw-r--r--src/contrib/markdown/extensions/extra.py49
-rw-r--r--src/contrib/markdown/extensions/fenced_code.py117
-rw-r--r--src/contrib/markdown/extensions/footnotes.py293
-rw-r--r--src/contrib/markdown/extensions/headerid.py195
-rw-r--r--src/contrib/markdown/extensions/html_tidy.py62
-rw-r--r--src/contrib/markdown/extensions/imagelinks.py119
-rw-r--r--src/contrib/markdown/extensions/legacy.py468
-rw-r--r--src/contrib/markdown/extensions/meta.py90
-rw-r--r--src/contrib/markdown/extensions/rss.py114
-rw-r--r--src/contrib/markdown/extensions/tables.py97
-rw-r--r--src/contrib/markdown/extensions/toc.py140
-rw-r--r--src/contrib/markdown/extensions/wikilinks.py155
16 files changed, 2322 insertions, 0 deletions
diff --git a/src/contrib/markdown/extensions/__init__.py b/src/contrib/markdown/extensions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/contrib/markdown/extensions/__init__.py
diff --git a/src/contrib/markdown/extensions/abbr.py b/src/contrib/markdown/extensions/abbr.py
new file mode 100644
index 0000000..783220e
--- /dev/null
+++ b/src/contrib/markdown/extensions/abbr.py
@@ -0,0 +1,95 @@
+'''
+Abbreviation Extension for Python-Markdown
+==========================================
+
+This extension adds abbreviation handling to Python-Markdown.
+
+Simple Usage:
+
+ >>> import markdown
+ >>> text = """
+ ... Some text with an ABBR and a REF. Ignore REFERENCE and ref.
+ ...
+ ... *[ABBR]: Abbreviation
+ ... *[REF]: Abbreviation Reference
+ ... """
+ >>> markdown.markdown(text, ['abbr'])
+ u'<p>Some text with an <abbr title="Abbreviation">ABBR</abbr> and a <abbr title="Abbreviation Reference">REF</abbr>. Ignore REFERENCE and ref.</p>'
+
+Copyright 2007-2008
+* [Waylan Limberg](http://achinghead.com/)
+* [Seemant Kulleen](http://www.kulleen.org/)
+
+
+'''
+
+import markdown, re
+from markdown import etree
+
+# Global Vars
+ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)')
+
+class AbbrExtension(markdown.Extension):
+ """ Abbreviation Extension for Python-Markdown. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Insert AbbrPreprocessor before ReferencePreprocessor. """
+ md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference')
+
+
+class AbbrPreprocessor(markdown.preprocessors.Preprocessor):
+ """ Abbreviation Preprocessor - parse text for abbr references. """
+
+ def run(self, lines):
+ '''
+ Find and remove all Abbreviation references from the text.
+ Each reference is set as a new AbbrPattern in the markdown instance.
+
+ '''
+ new_text = []
+ for line in lines:
+ m = ABBR_REF_RE.match(line)
+ if m:
+ abbr = m.group('abbr').strip()
+ title = m.group('title').strip()
+ self.markdown.inlinePatterns['abbr-%s'%abbr] = \
+ AbbrPattern(self._generate_pattern(abbr), title)
+ else:
+ new_text.append(line)
+ return new_text
+
+ def _generate_pattern(self, text):
+ '''
+ Given a string, returns an regex pattern to match that string.
+
+ 'HTML' -> r'(?P<abbr>[H][T][M][L])'
+
+ Note: we force each char as a literal match (in brackets) as we don't
+ know what they will be beforehand.
+
+ '''
+ chars = list(text)
+ for i in range(len(chars)):
+ chars[i] = r'[%s]' % chars[i]
+ return r'(?P<abbr>\b%s\b)' % (r''.join(chars))
+
+
+class AbbrPattern(markdown.inlinepatterns.Pattern):
+ """ Abbreviation inline pattern. """
+
+ def __init__(self, pattern, title):
+ markdown.inlinepatterns.Pattern.__init__(self, pattern)
+ self.title = title
+
+ def handleMatch(self, m):
+ abbr = etree.Element('abbr')
+ abbr.text = m.group('abbr')
+ abbr.set('title', self.title)
+ return abbr
+
+def makeExtension(configs=None):
+ return AbbrExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/src/contrib/markdown/extensions/codehilite.py b/src/contrib/markdown/extensions/codehilite.py
new file mode 100644
index 0000000..c5d496b
--- /dev/null
+++ b/src/contrib/markdown/extensions/codehilite.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+
+"""
+CodeHilite Extension for Python-Markdown
+========================================
+
+Adds code/syntax highlighting to standard Python-Markdown code blocks.
+
+Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/).
+
+Project website: <http://www.freewisdom.org/project/python-markdown/CodeHilite>
+Contact: markdown@freewisdom.org
+
+License: BSD (see ../docs/LICENSE for details)
+
+Dependencies:
+* [Python 2.3+](http://python.org/)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+* [Pygments](http://pygments.org/)
+
+"""
+
+import markdown
+
+# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY -----------------
+
+try:
+ TAB_LENGTH = markdown.TAB_LENGTH
+except AttributeError:
+ TAB_LENGTH = 4
+
+
+# ------------------ The Main CodeHilite Class ----------------------
+class CodeHilite:
+ """
+ Determine language of source code, and pass it into the pygments hilighter.
+
+ Basic Usage:
+ >>> code = CodeHilite(src = 'some text')
+ >>> html = code.hilite()
+
+ * src: Source string or any object with a .readline attribute.
+
+ * linenos: (Boolen) Turn line numbering 'on' or 'off' (off by default).
+
+ * css_class: Set class name of wrapper div ('codehilite' by default).
+
+ Low Level Usage:
+ >>> code = CodeHilite()
+ >>> code.src = 'some text' # String or anything with a .readline attr.
+ >>> code.linenos = True # True or False; Turns line numbering on or of.
+ >>> html = code.hilite()
+
+ """
+
+ def __init__(self, src=None, linenos=False, css_class="codehilite"):
+ self.src = src
+ self.lang = None
+ self.linenos = linenos
+ self.css_class = css_class
+
+ def hilite(self):
+ """
+ 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">).
+
+ returns : A string of html.
+
+ """
+
+ self.src = self.src.strip('\n')
+
+ self._getLang()
+
+ 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 = self._escape(self.src)
+ if self.linenos:
+ txt = self._number(txt)
+ else :
+ txt = '<div class="%s"><pre>%s</pre></div>\n'% \
+ (self.css_class, txt)
+ return txt
+ else:
+ try:
+ lexer = get_lexer_by_name(self.lang)
+ except ValueError:
+ try:
+ lexer = guess_lexer(self.src)
+ except ValueError:
+ lexer = TextLexer()
+ formatter = HtmlFormatter(linenos=self.linenos,
+ cssclass=self.css_class)
+ return highlight(self.src, lexer, formatter)
+
+ def _escape(self, txt):
+ """ basic html escaping """
+ txt = txt.replace('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+ def _number(self, 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
+
+
+ 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
+ (?P<lang>[\w+-]*) # The language
+ ''', 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")
+
+
+
+# ------------------ The Markdown Extension -------------------------------
+class HiliteTreeprocessor(markdown.treeprocessors.Treeprocessor):
+ """ Hilight source code in code blocks. """
+
+ def run(self, root):
+ """ Find code blocks and store in htmlStash. """
+ blocks = root.getiterator('pre')
+ for block in blocks:
+ children = block.getchildren()
+ if len(children) == 1 and children[0].tag == 'code':
+ code = CodeHilite(children[0].text,
+ linenos=self.config['force_linenos'][0],
+ css_class=self.config['css_class'][0])
+ placeholder = self.markdown.htmlStash.store(code.hilite(),
+ safe=True)
+ # Clear codeblock in etree instance
+ block.clear()
+ # Change to p element which will later
+ # be removed when inserting raw html
+ block.tag = 'p'
+ block.text = placeholder
+
+
+class CodeHiliteExtension(markdown.Extension):
+ """ Add source code hilighting to markdown codeblocks. """
+
+ def __init__(self, configs):
+ # define default configs
+ self.config = {
+ 'force_linenos' : [False, "Force line numbers - Default: False"],
+ 'css_class' : ["codehilite",
+ "Set class name for wrapper <div> - Default: codehilite"],
+ }
+
+ # Override defaults with user settings
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add HilitePostprocessor to Markdown instance. """
+ hiliter = HiliteTreeprocessor(md)
+ hiliter.config = self.config
+ md.treeprocessors.add("hilite", hiliter, "_begin")
+
+
+def makeExtension(configs={}):
+ return CodeHiliteExtension(configs=configs)
+
diff --git a/src/contrib/markdown/extensions/def_list.py b/src/contrib/markdown/extensions/def_list.py
new file mode 100644
index 0000000..73a1c85
--- /dev/null
+++ b/src/contrib/markdown/extensions/def_list.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env Python
+"""
+Definition List Extension for Python-Markdown
+=============================================
+
+Added parsing of Definition Lists to Python-Markdown.
+
+A simple example:
+
+ Apple
+ : Pomaceous fruit of plants of the genus Malus in
+ the family Rosaceae.
+ : An american computer company.
+
+ Orange
+ : The fruit of an evergreen tree of the genus Citrus.
+
+Copyright 2008 - [Waylan Limberg](http://achinghead.com)
+
+"""
+
+import markdown, re
+from markdown import etree
+
+
+class DefListProcessor(markdown.blockprocessors.BlockProcessor):
+ """ Process Definition Lists. """
+
+ RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)')
+
+ def test(self, parent, block):
+ return bool(self.RE.search(block))
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ m = self.RE.search(block)
+ terms = [l.strip() for l in block[:m.start()].split('\n') if l.strip()]
+ d, theRest = self.detab(block[m.end():])
+ if d:
+ d = '%s\n%s' % (m.group(2), d)
+ else:
+ d = m.group(2)
+ #import ipdb; ipdb.set_trace()
+ sibling = self.lastChild(parent)
+ if not terms and sibling.tag == 'p':
+ # The previous paragraph contains the terms
+ state = 'looselist'
+ terms = sibling.text.split('\n')
+ parent.remove(sibling)
+ # Aquire new sibling
+ sibling = self.lastChild(parent)
+ else:
+ state = 'list'
+
+ if sibling and sibling.tag == 'dl':
+ # This is another item on an existing list
+ dl = sibling
+ if len(dl) and dl[-1].tag == 'dd' and len(dl[-1]):
+ state = 'looselist'
+ else:
+ # This is a new list
+ dl = etree.SubElement(parent, 'dl')
+ # Add terms
+ for term in terms:
+ dt = etree.SubElement(dl, 'dt')
+ dt.text = term
+ # Add definition
+ self.parser.state.set(state)
+ dd = etree.SubElement(dl, 'dd')
+ self.parser.parseBlocks(dd, [d])
+ self.parser.state.reset()
+
+ if theRest:
+ blocks.insert(0, theRest)
+
+class DefListIndentProcessor(markdown.blockprocessors.ListIndentProcessor):
+ """ Process indented children of definition list items. """
+
+ ITEM_TYPES = ['dd']
+ LIST_TYPES = ['dl']
+
+ def create_item(parent, block):
+ """ Create a new dd and parse the block with it as the parent. """
+ dd = markdown.etree.SubElement(parent, 'dd')
+ self.parser.parseBlocks(dd, [block])
+
+
+
+class DefListExtension(markdown.Extension):
+ """ Add definition lists to Markdown. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add an instance of DefListProcessor to BlockParser. """
+ md.parser.blockprocessors.add('defindent',
+ DefListIndentProcessor(md.parser),
+ '>indent')
+ md.parser.blockprocessors.add('deflist',
+ DefListProcessor(md.parser),
+ '>ulist')
+
+
+def makeExtension(configs={}):
+ return DefListExtension(configs=configs)
+
diff --git a/src/contrib/markdown/extensions/extra.py b/src/contrib/markdown/extensions/extra.py
new file mode 100644
index 0000000..4a2ffbf
--- /dev/null
+++ b/src/contrib/markdown/extensions/extra.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+"""
+Python-Markdown Extra Extension
+===============================
+
+A compilation of various Python-Markdown extensions that imitates
+[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/).
+
+Note that each of the individual extensions still need to be available
+on your PYTHONPATH. This extension simply wraps them all up as a
+convenience so that only one extension needs to be listed when
+initiating Markdown. See the documentation for each individual
+extension for specifics about that extension.
+
+In the event that one or more of the supported extensions are not
+available for import, Markdown will issue a warning and simply continue
+without that extension.
+
+There may be additional extensions that are distributed with
+Python-Markdown that are not included here in Extra. Those extensions
+are not part of PHP Markdown Extra, and therefore, not part of
+Python-Markdown Extra. If you really would like Extra to include
+additional extensions, we suggest creating your own clone of Extra
+under a differant name. You could also edit the `extensions` global
+variable defined below, but be aware that such changes may be lost
+when you upgrade to any future version of Python-Markdown.
+
+"""
+
+import markdown
+
+extensions = ['fenced_code',
+ 'footnotes',
+ 'headerid',
+ 'def_list',
+ 'tables',
+ 'abbr',
+ ]
+
+
+class ExtraExtension(markdown.Extension):
+ """ Add various extensions to Markdown class."""
+
+ def extendMarkdown(self, md, md_globals):
+ """ Register extension instances. """
+ md.registerExtensions(extensions, self.config)
+
+def makeExtension(configs={}):
+ return ExtraExtension(configs=dict(configs))
diff --git a/src/contrib/markdown/extensions/fenced_code.py b/src/contrib/markdown/extensions/fenced_code.py
new file mode 100644
index 0000000..307b1dc
--- /dev/null
+++ b/src/contrib/markdown/extensions/fenced_code.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+"""
+Fenced Code Extension for Python Markdown
+=========================================
+
+This extension adds Fenced Code Blocks to Python-Markdown.
+
+ >>> import markdown
+ >>> text = '''
+ ... A paragraph before a fenced code block:
+ ...
+ ... ~~~
+ ... Fenced code block
+ ... ~~~
+ ... '''
+ >>> html = markdown.markdown(text, extensions=['fenced_code'])
+ >>> html
+ u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>'
+
+Works with safe_mode also (we check this because we are using the HtmlStash):
+
+ >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
+ u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>'
+
+Include tilde's in a code block and wrap with blank lines:
+
+ >>> text = '''
+ ... ~~~~~~~~
+ ...
+ ... ~~~~
+ ...
+ ... ~~~~~~~~'''
+ >>> markdown.markdown(text, extensions=['fenced_code'])
+ u'<pre><code>\\n~~~~\\n\\n</code></pre>'
+
+Multiple blocks and language tags:
+
+ >>> text = '''
+ ... ~~~~{.python}
+ ... block one
+ ... ~~~~
+ ...
+ ... ~~~~.html
+ ... <p>block two</p>
+ ... ~~~~'''
+ >>> markdown.markdown(text, extensions=['fenced_code'])
+ u'<pre><code class="python">block one\\n</code></pre>\\n\\n<pre><code class="html">&lt;p&gt;block two&lt;/p&gt;\\n</code></pre>'
+
+Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
+
+Project website: <http://www.freewisdom.org/project/python-markdown/Fenced__Code__Blocks>
+Contact: markdown@freewisdom.org
+
+License: BSD (see ../docs/LICENSE for details)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+
+"""
+
+import markdown, re
+
+# Global vars
+FENCED_BLOCK_RE = re.compile( \
+ r'(?P<fence>^~{3,})[ ]*(\{?\.(?P<lang>[a-zA-Z0-9_-]*)\}?)?[ ]*\n(?P<code>.*?)(?P=fence)[ ]*$',
+ re.MULTILINE|re.DOTALL
+ )
+CODE_WRAP = '<pre><code%s>%s</code></pre>'
+LANG_TAG = ' class="%s"'
+
+
+class FencedCodeExtension(markdown.Extension):
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add FencedBlockPreprocessor to the Markdown instance. """
+
+ md.preprocessors.add('fenced_code_block',
+ FencedBlockPreprocessor(md),
+ "_begin")
+
+
+class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
+
+ def run(self, lines):
+ """ Match and store Fenced Code Blocks in the HtmlStash. """
+ text = "\n".join(lines)
+ while 1:
+ m = FENCED_BLOCK_RE.search(text)
+ if m:
+ lang = ''
+ if m.group('lang'):
+ lang = LANG_TAG % m.group('lang')
+ code = CODE_WRAP % (lang, self._escape(m.group('code')))
+ placeholder = self.markdown.htmlStash.store(code, safe=True)
+ text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
+ else:
+ break
+ return text.split("\n")
+
+ def _escape(self, txt):
+ """ basic html escaping """
+ txt = txt.replace('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+
+def makeExtension(configs=None):
+ return FencedCodeExtension()
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/src/contrib/markdown/extensions/footnotes.py b/src/contrib/markdown/extensions/footnotes.py
new file mode 100644
index 0000000..6dacab7
--- /dev/null
+++ b/src/contrib/markdown/extensions/footnotes.py
@@ -0,0 +1,293 @@
+"""
+========================= FOOTNOTES =================================
+
+This section adds footnote handling to markdown. It can be used as
+an example for extending python-markdown with relatively complex
+functionality. While in this case the extension is included inside
+the module itself, it could just as easily be added from outside the
+module. Not that all markdown classes above are ignorant about
+footnotes. All footnote functionality is provided separately and
+then added to the markdown instance at the run time.
+
+Footnote functionality is attached by calling extendMarkdown()
+method of FootnoteExtension. The method also registers the
+extension to allow it's state to be reset by a call to reset()
+method.
+
+Example:
+ Footnotes[^1] have a label[^label] and a definition[^!DEF].
+
+ [^1]: This is a footnote
+ [^label]: A footnote on "label"
+ [^!DEF]: The footnote for definition
+
+"""
+
+import re, markdown
+from markdown import etree
+
+FN_BACKLINK_TEXT = "zz1337820767766393qq"
+NBSP_PLACEHOLDER = "qq3936677670287331zz"
+DEF_RE = re.compile(r'(\ ?\ ?\ ?)\[\^([^\]]*)\]:\s*(.*)')
+TABBED_RE = re.compile(r'((\t)|( ))(.*)')
+
+class FootnoteExtension(markdown.Extension):
+ """ Footnote Extension. """
+
+ def __init__ (self, configs):
+ """ Setup configs. """
+ self.config = {'PLACE_MARKER':
+ ["///Footnotes Go Here///",
+ "The text string that marks where the footnotes go"]}
+
+ for key, value in configs:
+ self.config[key][0] = value
+
+ self.reset()
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add pieces to Markdown. """
+ md.registerExtension(self)
+ self.parser = md.parser
+ # Insert a preprocessor before ReferencePreprocessor
+ md.preprocessors.add("footnote", FootnotePreprocessor(self),
+ "<reference")
+ # Insert an inline pattern before ImageReferencePattern
+ FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
+ md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self),
+ "<reference")
+ # Insert a tree-processor that would actually add the footnote div
+ # This must be before the inline treeprocessor so inline patterns
+ # run on the contents of the div.
+ md.treeprocessors.add("footnote", FootnoteTreeprocessor(self),
+ "<inline")
+ # Insert a postprocessor after amp_substitute oricessor
+ md.postprocessors.add("footnote", FootnotePostprocessor(self),
+ ">amp_substitute")
+
+ def reset(self):
+ """ Clear the footnotes on reset. """
+ self.footnotes = markdown.odict.OrderedDict()
+
+ def findFootnotesPlaceholder(self, root):
+ """ Return ElementTree Element that contains Footnote placeholder. """
+ def finder(element):
+ for child in element:
+ if child.text:
+ if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
+ return child, True
+ if child.tail:
+ if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
+ return (child, element), False
+ finder(child)
+ return None
+
+ res = finder(root)
+ return res
+
+ def setFootnote(self, id, text):
+ """ Store a footnote for later retrieval. """
+ self.footnotes[id] = text
+
+ def makeFootnoteId(self, id):
+ """ Return footnote link id. """
+ return 'fn:%s' % id
+
+ def makeFootnoteRefId(self, id):
+ """ Return footnote back-link id. """
+ return 'fnref:%s' % id
+
+ def makeFootnotesDiv(self, root):
+ """ Return div of footnotes as et Element. """
+
+ if not self.footnotes.keys():
+ return None
+
+ div = etree.Element("div")
+ div.set('class', 'footnote')
+ hr = etree.SubElement(div, "hr")
+ ol = etree.SubElement(div, "ol")
+
+ for id in self.footnotes.keys():
+ li = etree.SubElement(ol, "li")
+ li.set("id", self.makeFootnoteId(id))
+ self.parser.parseChunk(li, self.footnotes[id])
+ backlink = etree.Element("a")
+ backlink.set("href", "#" + self.makeFootnoteRefId(id))
+ backlink.set("rev", "footnote")
+ backlink.set("title", "Jump back to footnote %d in the text" % \
+ (self.footnotes.index(id)+1))
+ backlink.text = FN_BACKLINK_TEXT
+
+ if li.getchildren():
+ node = li[-1]
+ if node.tag == "p":
+ node.text = node.text + NBSP_PLACEHOLDER
+ node.append(backlink)
+ else:
+ p = etree.SubElement(li, "p")
+ p.append(backlink)
+ return div
+
+
+class FootnotePreprocessor(markdown.preprocessors.Preprocessor):
+ """ Find all footnote references and store for later use. """
+
+ def __init__ (self, footnotes):
+ self.footnotes = footnotes
+
+ def run(self, lines):
+ lines = self._handleFootnoteDefinitions(lines)
+ text = "\n".join(lines)
+ return text.split("\n")
+
+ def _handleFootnoteDefinitions(self, lines):
+ """
+ Recursively find all footnote definitions in lines.
+
+ Keywords:
+
+ * lines: A list of lines of text
+
+ Return: A list of lines with footnote definitions removed.
+
+ """
+ i, id, footnote = self._findFootnoteDefinition(lines)
+
+ if id :
+ plain = lines[:i]
+ detabbed, theRest = self.detectTabbed(lines[i+1:])
+ self.footnotes.setFootnote(id,
+ footnote + "\n"
+ + "\n".join(detabbed))
+ more_plain = self._handleFootnoteDefinitions(theRest)
+ return plain + [""] + more_plain
+ else :
+ return lines
+
+ def _findFootnoteDefinition(self, lines):
+ """
+ Find the parts of a footnote definition.
+
+ Keywords:
+
+ * lines: A list of lines of text.
+
+ Return: A three item tuple containing the index of the first line of a
+ footnote definition, the id of the definition and the body of the
+ definition.
+
+ """
+ counter = 0
+ for line in lines:
+ m = DEF_RE.match(line)
+ if m:
+ return counter, m.group(2), m.group(3)
+ counter += 1
+ return counter, None, None
+
+ def detectTabbed(self, lines):
+ """ Find indented text and remove indent before further proccesing.
+
+ Keyword arguments:
+
+ * lines: an array of strings
+
+ Returns: a list of post processed items and the unused
+ remainder of the original list
+
+ """
+ items = []
+ item = -1
+ i = 0 # to keep track of where we are
+
+ def detab(line):
+ match = TABBED_RE.match(line)
+ if match:
+ return match.group(4)
+
+ for line in lines:
+ if line.strip(): # Non-blank line
+ line = detab(line)
+ if line:
+ items.append(line)
+ i += 1
+ continue
+ else:
+ return items, lines[i:]
+
+ else: # Blank line: _maybe_ we are done.
+ i += 1 # advance
+
+ # Find the next non-blank line
+ for j in range(i, len(lines)):
+ if lines[j].strip():
+ next_line = lines[j]; break
+ else:
+ break # There is no more text; we are done.
+
+ # Check if the next non-blank line is tabbed
+ if detab(next_line): # Yes, more work to do.
+ items.append("")
+ continue
+ else:
+ break # No, we are done.
+ else:
+ i += 1
+
+ return items, lines[i:]
+
+
+class FootnotePattern(markdown.inlinepatterns.Pattern):
+ """ InlinePattern for footnote markers in a document's body text. """
+
+ def __init__(self, pattern, footnotes):
+ markdown.inlinepatterns.Pattern.__init__(self, pattern)
+ self.footnotes = footnotes
+
+ def handleMatch(self, m):
+ sup = etree.Element("sup")
+ a = etree.SubElement(sup, "a")
+ id = m.group(2)
+ sup.set('id', self.footnotes.makeFootnoteRefId(id))
+ a.set('href', '#' + self.footnotes.makeFootnoteId(id))
+ a.set('rel', 'footnote')
+ a.text = str(self.footnotes.footnotes.index(id) + 1)
+ return sup
+
+
+class FootnoteTreeprocessor(markdown.treeprocessors.Treeprocessor):
+ """ Build and append footnote div to end of document. """
+
+ def __init__ (self, footnotes):
+ self.footnotes = footnotes
+
+ def run(self, root):
+ footnotesDiv = self.footnotes.makeFootnotesDiv(root)
+ if footnotesDiv:
+ result = self.footnotes.findFootnotesPlaceholder(root)
+ if result:
+ node, isText = result
+ if isText:
+ node.text = None
+ node.getchildren().insert(0, footnotesDiv)
+ else:
+ child, element = node
+ ind = element.getchildren().find(child)
+ element.getchildren().insert(ind + 1, footnotesDiv)
+ child.tail = None
+ fnPlaceholder.parent.replaceChild(fnPlaceholder, footnotesDiv)
+ else:
+ root.append(footnotesDiv)
+
+class FootnotePostprocessor(markdown.postprocessors.Postprocessor):
+ """ Replace placeholders with html entities. """
+
+ def run(self, text):
+ text = text.replace(FN_BACKLINK_TEXT, "&#8617;")
+ return text.replace(NBSP_PLACEHOLDER, "&#160;")
+
+def makeExtension(configs=[]):
+ """ Return an instance of the FootnoteExtension """
+ return FootnoteExtension(configs=configs)
+
diff --git a/src/contrib/markdown/extensions/headerid.py b/src/contrib/markdown/extensions/headerid.py
new file mode 100644
index 0000000..f70a7a9
--- /dev/null
+++ b/src/contrib/markdown/extensions/headerid.py
@@ -0,0 +1,195 @@
+#!/usr/bin/python
+
+"""
+HeaderID Extension for Python-Markdown
+======================================
+
+Adds ability to set HTML IDs for headers.
+
+Basic usage:
+
+ >>> import markdown
+ >>> text = "# Some Header # {#some_id}"
+ >>> md = markdown.markdown(text, ['headerid'])
+ >>> md
+ u'<h1 id="some_id">Some Header</h1>'
+
+All header IDs are unique:
+
+ >>> text = '''
+ ... #Header
+ ... #Another Header {#header}
+ ... #Third Header {#header}'''
+ >>> md = markdown.markdown(text, ['headerid'])
+ >>> md
+ u'<h1 id="header">Header</h1>\\n<h1 id="header_1">Another Header</h1>\\n<h1 id="header_2">Third Header</h1>'
+
+To fit within a html template's hierarchy, set the header base level:
+
+ >>> text = '''
+ ... #Some Header
+ ... ## Next Level'''
+ >>> md = markdown.markdown(text, ['headerid(level=3)'])
+ >>> md
+ u'<h3 id="some_header">Some Header</h3>\\n<h4 id="next_level">Next Level</h4>'
+
+Turn off auto generated IDs:
+
+ >>> text = '''
+ ... # Some Header
+ ... # Header with ID # { #foo }'''
+ >>> md = markdown.markdown(text, ['headerid(forceid=False)'])
+ >>> md
+ u'<h1>Some Header</h1>\\n<h1 id="foo">Header with ID</h1>'
+
+Use with MetaData extension:
+
+ >>> text = '''header_level: 2
+ ... header_forceid: Off
+ ...
+ ... # A Header'''
+ >>> md = markdown.markdown(text, ['headerid', 'meta'])
+ >>> md
+ u'<h2>A Header</h2>'
+
+Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
+
+Project website: <http://www.freewisdom.org/project/python-markdown/HeaderId>
+Contact: markdown@freewisdom.org
+
+License: BSD (see ../docs/LICENSE for details)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+
+"""
+
+import markdown
+from markdown import etree
+import re
+from string import ascii_lowercase, digits, punctuation
+
+ID_CHARS = ascii_lowercase + digits + '-_'
+IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
+
+
+class HeaderIdProcessor(markdown.blockprocessors.BlockProcessor):
+ """ Replacement BlockProcessor for Header IDs. """
+
+ # Detect a header at start of any line in block
+ RE = re.compile(r"""(^|\n)
+ (?P<level>\#{1,6}) # group('level') = string of hashes
+ (?P<header>.*?) # group('header') = Header text
+ \#* # optional closing hashes
+ (?:[ \t]*\{[ \t]*\#(?P<id>[-_:a-zA-Z0-9]+)[ \t]*\})?
+ (\n|$) # ^^ group('id') = id attribute
+ """,
+ re.VERBOSE)
+
+ IDs = []
+
+ def test(self, parent, block):
+ return bool(self.RE.search(block))
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ m = self.RE.search(block)
+ if m:
+ before = block[:m.start()] # All lines before header
+ after = block[m.end():] # All lines after header
+ if before:
+ # As the header was not the first line of the block and the
+ # lines before the header must be parsed first,
+ # recursively parse this lines as a block.
+ self.parser.parseBlocks(parent, [before])
+ # Create header using named groups from RE
+ start_level, force_id = self._get_meta()
+ level = len(m.group('level')) + start_level
+ if level > 6:
+ level = 6
+ h = markdown.etree.SubElement(parent, 'h%d' % level)
+ h.text = m.group('header').strip()
+ if m.group('id'):
+ h.set('id', self._unique_id(m.group('id')))
+ elif force_id:
+ h.set('id', self._create_id(m.group('header').strip()))
+ if after:
+ # Insert remaining lines as first block for future parsing.
+ blocks.insert(0, after)
+ else:
+ # This should never happen, but just in case...
+ message(CRITICAL, "We've got a problem header!")
+
+ def _get_meta(self):
+ """ Return meta data suported by this ext as a tuple """
+ level = int(self.config['level'][0]) - 1
+ force = self._str2bool(self.config['forceid'][0])
+ if hasattr(self.md, 'Meta'):
+ if self.md.Meta.has_key('header_level'):
+ level = int(self.md.Meta['header_level'][0]) - 1
+ if self.md.Meta.has_key('header_forceid'):
+ force = self._str2bool(self.md.Meta['header_forceid'][0])
+ return level, force
+
+ def _str2bool(self, s, default=False):
+ """ Convert a string to a booleen value. """
+ s = str(s)
+ if s.lower() in ['0', 'f', 'false', 'off', 'no', 'n']:
+ return False
+ elif s.lower() in ['1', 't', 'true', 'on', 'yes', 'y']:
+ return True
+ return default
+
+ def _unique_id(self, id):
+ """ Ensure ID is unique. Append '_1', '_2'... if not """
+ while id in self.IDs:
+ m = IDCOUNT_RE.match(id)
+ if m:
+ id = '%s_%d'% (m.group(1), int(m.group(2))+1)
+ else:
+ id = '%s_%d'% (id, 1)
+ self.IDs.append(id)
+ return id
+
+ def _create_id(self, header):
+ """ Return ID from Header text. """
+ h = ''
+ for c in header.lower().replace(' ', '_'):
+ if c in ID_CHARS:
+ h += c
+ elif c not in punctuation:
+ h += '+'
+ return self._unique_id(h)
+
+
+class HeaderIdExtension (markdown.Extension):
+ def __init__(self, configs):
+ # set defaults
+ self.config = {
+ 'level' : ['1', 'Base level for headers.'],
+ 'forceid' : ['True', 'Force all headers to have an id.']
+ }
+
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ md.registerExtension(self)
+ self.processor = HeaderIdProcessor(md.parser)
+ self.processor.md = md
+ self.processor.config = self.config
+ # Replace existing hasheader in place.
+ md.parser.blockprocessors['hashheader'] = self.processor
+
+ def reset(self):
+ self.processor.IDs = []
+
+
+def makeExtension(configs=None):
+ return HeaderIdExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+
diff --git a/src/contrib/markdown/extensions/html_tidy.py b/src/contrib/markdown/extensions/html_tidy.py
new file mode 100644
index 0000000..5105e33
--- /dev/null
+++ b/src/contrib/markdown/extensions/html_tidy.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+"""
+HTML Tidy Extension for Python-Markdown
+=======================================
+
+Runs [HTML Tidy][] on the output of Python-Markdown using the [uTidylib][]
+Python wrapper. Both libtidy and uTidylib must be installed on your system.
+
+Note than any Tidy [options][] can be passed in as extension configs. So,
+for example, to output HTML rather than XHTML, set ``output_xhtml=0``. To
+indent the output, set ``indent=auto`` and to have Tidy wrap the output in
+``<html>`` and ``<body>`` tags, set ``show_body_only=0``.
+
+[HTML Tidy]: http://tidy.sourceforge.net/
+[uTidylib]: http://utidylib.berlios.de/
+[options]: http://tidy.sourceforge.net/docs/quickref.html
+
+Copyright (c)2008 [Waylan Limberg](http://achinghead.com)
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
+
+Dependencies:
+* [Python2.3+](http://python.org)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+* [HTML Tidy](http://utidylib.berlios.de/)
+* [uTidylib](http://utidylib.berlios.de/)
+
+"""
+
+import markdown
+import tidy
+
+class TidyExtension(markdown.Extension):
+
+ def __init__(self, configs):
+ # Set defaults to match typical markdown behavior.
+ self.config = dict(output_xhtml=1,
+ show_body_only=1,
+ )
+ # Merge in user defined configs overriding any present if nessecary.
+ for c in configs:
+ self.config[c[0]] = c[1]
+
+ def extendMarkdown(self, md, md_globals):
+ # Save options to markdown instance
+ md.tidy_options = self.config
+ # Add TidyProcessor to postprocessors
+ md.postprocessors['tidy'] = TidyProcessor(md)
+
+
+class TidyProcessor(markdown.postprocessors.Postprocessor):
+
+ def run(self, text):
+ # Pass text to Tidy. As Tidy does not accept unicode we need to encode
+ # it and decode its return value.
+ return unicode(tidy.parseString(text.encode('utf-8'),
+ **self.markdown.tidy_options))
+
+
+def makeExtension(configs=None):
+ return TidyExtension(configs=configs)
diff --git a/src/contrib/markdown/extensions/imagelinks.py b/src/contrib/markdown/extensions/imagelinks.py
new file mode 100644
index 0000000..ee0b708
--- /dev/null
+++ b/src/contrib/markdown/extensions/imagelinks.py
@@ -0,0 +1,119 @@
+"""
+========================= 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 extendMarkdown(self, md, md_globals):
+
+ md.preprocessors.add("imagelink", ImageLinkPreprocessor(md), "_begin")
+
+
+class ImageLinkPreprocessor(markdown.preprocessors.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/src/contrib/markdown/extensions/legacy.py b/src/contrib/markdown/extensions/legacy.py
new file mode 100644
index 0000000..5b3a9c2
--- /dev/null
+++ b/src/contrib/markdown/extensions/legacy.py
@@ -0,0 +1,468 @@
+"""
+Legacy Extension for Python-Markdown
+====================================
+
+Replaces the core parser with the old one.
+
+"""
+
+import markdown, re
+from markdown import etree
+
+"""Basic and reusable regular expressions."""
+
+def wrapRe(raw_re) : return re.compile("^%s$" % raw_re, re.DOTALL)
+CORE_RE = {
+ 'header': wrapRe(r'(#{1,6})[ \t]*(.*?)[ \t]*(#*)'), # # A title
+ 'reference-def': wrapRe(r'(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)'),
+ # [Google]: http://www.google.com/
+ 'containsline': wrapRe(r'([-]*)$|^([=]*)'), # -----, =====, etc.
+ 'ol': wrapRe(r'[ ]{0,3}[\d]*\.\s+(.*)'), # 1. text
+ 'ul': wrapRe(r'[ ]{0,3}[*+-]\s+(.*)'), # "* text"
+ 'isline1': wrapRe(r'(\**)'), # ***
+ 'isline2': wrapRe(r'(\-*)'), # ---
+ 'isline3': wrapRe(r'(\_*)'), # ___
+ 'tabbed': wrapRe(r'((\t)|( ))(.*)'), # an indented line
+ 'quoted': wrapRe(r'[ ]{0,2}> ?(.*)'), # a quoted block ("> ...")
+ 'containsline': re.compile(r'^([-]*)$|^([=]*)$', re.M),
+ 'attr': re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123}
+}
+
+class MarkdownParser:
+ """Parser Markdown into a ElementTree."""
+
+ def __init__(self):
+ pass
+
+ def parseDocument(self, lines):
+ """Parse a markdown string into an ElementTree."""
+ # Create a ElementTree from the lines
+ root = etree.Element("div")
+ buffer = []
+ for line in lines:
+ if line.startswith("#"):
+ self.parseChunk(root, buffer)
+ buffer = [line]
+ else:
+ buffer.append(line)
+
+ self.parseChunk(root, buffer)
+
+ return etree.ElementTree(root)
+
+ def parseChunk(self, parent_elem, lines, inList=0, looseList=0):
+ """Process a chunk of markdown-formatted text and attach the parse to
+ an ElementTree node.
+
+ Process a section of a source document, looking for high
+ level structural elements like lists, block quotes, code
+ segments, html blocks, etc. Some those then get stripped
+ of their high level markup (e.g. get unindented) and the
+ lower-level markup is processed recursively.
+
+ Keyword arguments:
+
+ * parent_elem: The ElementTree element to which the content will be
+ added.
+ * lines: a list of lines
+ * inList: a level
+
+ Returns: None
+
+ """
+ # Loop through lines until none left.
+ while lines:
+ # Skipping empty line
+ if not lines[0]:
+ lines = lines[1:]
+ continue
+
+ # Check if this section starts with a list, a blockquote or
+ # a code block. If so, process them.
+ processFn = { 'ul': self.__processUList,
+ 'ol': self.__processOList,
+ 'quoted': self.__processQuote,
+ 'tabbed': self.__processCodeBlock}
+ for regexp in ['ul', 'ol', 'quoted', 'tabbed']:
+ m = CORE_RE[regexp].match(lines[0])
+ if m:
+ processFn[regexp](parent_elem, lines, inList)
+ return
+
+ # We are NOT looking at one of the high-level structures like
+ # lists or blockquotes. So, it's just a regular paragraph
+ # (though perhaps nested inside a list or something else). If
+ # we are NOT inside a list, we just need to look for a blank
+ # line to find the end of the block. If we ARE inside a
+ # list, however, we need to consider that a sublist does not
+ # need to be separated by a blank line. Rather, the following
+ # markup is legal:
+ #
+ # * The top level list item
+ #
+ # Another paragraph of the list. This is where we are now.
+ # * Underneath we might have a sublist.
+ #
+
+ if inList:
+ start, lines = self.__linesUntil(lines, (lambda line:
+ CORE_RE['ul'].match(line)
+ or CORE_RE['ol'].match(line)
+ or not line.strip()))
+ self.parseChunk(parent_elem, start, inList-1,
+ looseList=looseList)
+ inList = inList-1
+
+ else: # Ok, so it's just a simple block
+ test = lambda line: not line.strip() or line[0] == '>'
+ paragraph, lines = self.__linesUntil(lines, test)
+ if len(paragraph) and paragraph[0].startswith('#'):
+ self.__processHeader(parent_elem, paragraph)
+ elif len(paragraph) and CORE_RE["isline3"].match(paragraph[0]):
+ self.__processHR(parent_elem)
+ lines = paragraph[1:] + lines
+ elif paragraph:
+ self.__processParagraph(parent_elem, paragraph,
+ inList, looseList)
+
+ if lines and not lines[0].strip():
+ lines = lines[1:] # skip the first (blank) line
+
+ def __processHR(self, parentElem):
+ hr = etree.SubElement(parentElem, "hr")
+
+ def __processHeader(self, parentElem, paragraph):
+ m = CORE_RE['header'].match(paragraph[0])
+ if m:
+ level = len(m.group(1))
+ h = etree.SubElement(parentElem, "h%d" % level)
+ h.text = m.group(2).strip()
+ else:
+ message(CRITICAL, "We've got a problem header!")
+
+ def __processParagraph(self, parentElem, paragraph, inList, looseList):
+
+ if ( parentElem.tag == 'li'
+ and not (looseList or parentElem.getchildren())):
+
+ # If this is the first paragraph inside "li", don't
+ # put <p> around it - append the paragraph bits directly
+ # onto parentElem
+ el = parentElem
+ else:
+ # Otherwise make a "p" element
+ el = etree.SubElement(parentElem, "p")
+
+ dump = []
+
+ # Searching for hr or header
+ for line in paragraph:
+ # it's hr
+ if CORE_RE["isline3"].match(line):
+ el.text = "\n".join(dump)
+ self.__processHR(el)
+ dump = []
+ # it's header
+ elif line.startswith("#"):
+ el.text = "\n".join(dump)
+ self.__processHeader(parentElem, [line])
+ dump = []
+ else:
+ dump.append(line)
+ if dump:
+ text = "\n".join(dump)
+ el.text = text
+
+ def __processUList(self, parentElem, lines, inList):
+ self.__processList(parentElem, lines, inList, listexpr='ul', tag='ul')
+
+ def __processOList(self, parentElem, lines, inList):
+ self.__processList(parentElem, lines, inList, listexpr='ol', tag='ol')
+
+ def __processList(self, parentElem, lines, inList, listexpr, tag):
+ """
+ Given a list of document lines starting with a list item,
+ finds the end of the list, breaks it up, and recursively
+ processes each list item and the remainder of the text file.
+
+ Keyword arguments:
+
+ * parentElem: A ElementTree element to which the content will be added
+ * lines: a list of lines
+ * inList: a level
+
+ Returns: None
+
+ """
+ ul = etree.SubElement(parentElem, tag) # ul might actually be '<ol>'
+
+ looseList = 0
+
+ # Make a list of list items
+ items = []
+ item = -1
+
+ i = 0 # a counter to keep track of where we are
+ for line in lines:
+ loose = 0
+ if not line.strip():
+ # If we see a blank line, this _might_ be the end of the list
+ i += 1
+ loose = 1
+
+ # Find the next non-blank line
+ for j in range(i, len(lines)):
+ if lines[j].strip():
+ next = lines[j]
+ break
+ else:
+ # There is no more text => end of the list
+ break
+
+ # Check if the next non-blank line is still a part of the list
+
+ if ( CORE_RE[listexpr].match(next) or
+ CORE_RE['tabbed'].match(next) ):
+ # get rid of any white space in the line
+ items[item].append(line.strip())
+ looseList = loose or looseList
+ continue
+ else:
+ break # found end of the list
+
+ # Now we need to detect list items (at the current level)
+ # while also detabing child elements if necessary
+
+ for expr in ['ul', 'ol', 'tabbed']:
+ m = CORE_RE[expr].match(line)
+ if m:
+ if expr in ['ul', 'ol']: # We are looking at a new item
+ #if m.group(1) :
+ # Removed the check to allow for a blank line
+ # at the beginning of the list item
+ items.append([m.group(1)])
+ item += 1
+ elif expr == 'tabbed': # This line needs to be detabbed
+ items[item].append(m.group(4)) #after the 'tab'
+ i += 1
+ break
+ else:
+ items[item].append(line) # Just regular continuation
+ i += 1 # added on 2006.02.25
+ else:
+ i += 1
+
+ # Add the ElementTree elements
+ for item in items:
+ li = etree.SubElement(ul, "li")
+ self.parseChunk(li, item, inList + 1, looseList = looseList)
+
+ # Process the remaining part of the section
+ self.parseChunk(parentElem, lines[i:], inList)
+
+ def __linesUntil(self, lines, condition):
+ """
+ A utility function to break a list of lines upon the
+ first line that satisfied a condition. The condition
+ argument should be a predicate function.
+
+ """
+ i = -1
+ for line in lines:
+ i += 1
+ if condition(line):
+ break
+ else:
+ i += 1
+ return lines[:i], lines[i:]
+
+ def __processQuote(self, parentElem, lines, inList):
+ """
+ Given a list of document lines starting with a quote finds
+ the end of the quote, unindents it and recursively
+ processes the body of the quote and the remainder of the
+ text file.
+
+ Keyword arguments:
+
+ * parentElem: ElementTree element to which the content will be added
+ * lines: a list of lines
+ * inList: a level
+
+ Returns: None
+
+ """
+ dequoted = []
+ i = 0
+ blank_line = False # allow one blank line between paragraphs
+ for line in lines:
+ m = CORE_RE['quoted'].match(line)
+ if m:
+ dequoted.append(m.group(1))
+ i += 1
+ blank_line = False
+ elif not blank_line and line.strip() != '':
+ dequoted.append(line)
+ i += 1
+ elif not blank_line and line.strip() == '':
+ dequoted.append(line)
+ i += 1
+ blank_line = True
+ else:
+ break
+
+ blockquote = etree.SubElement(parentElem, "blockquote")
+
+ self.parseChunk(blockquote, dequoted, inList)
+ self.parseChunk(parentElem, lines[i:], inList)
+
+ def __processCodeBlock(self, parentElem, lines, inList):
+ """
+ Given a list of document lines starting with a code block
+ finds the end of the block, puts it into the ElementTree verbatim
+ wrapped in ("<pre><code>") and recursively processes the
+ the remainder of the text file.
+
+ Keyword arguments:
+
+ * parentElem: ElementTree element to which the content will be added
+ * lines: a list of lines
+ * inList: a level
+
+ Returns: None
+
+ """
+ detabbed, theRest = self.detectTabbed(lines)
+ pre = etree.SubElement(parentElem, "pre")
+ code = etree.SubElement(pre, "code")
+ text = "\n".join(detabbed).rstrip()+"\n"
+ code.text = markdown.AtomicString(text)
+ self.parseChunk(parentElem, theRest, inList)
+
+ def detectTabbed(self, lines):
+ """ Find indented text and remove indent before further proccesing.
+
+ Keyword arguments:
+
+ * lines: an array of strings
+
+ Returns: a list of post processed items and the unused
+ remainder of the original list
+
+ """
+ items = []
+ item = -1
+ i = 0 # to keep track of where we are
+
+ def detab(line):
+ match = CORE_RE['tabbed'].match(line)
+ if match:
+ return match.group(4)
+
+ for line in lines:
+ if line.strip(): # Non-blank line
+ line = detab(line)
+ if line:
+ items.append(line)
+ i += 1
+ continue
+ else:
+ return items, lines[i:]
+
+ else: # Blank line: _maybe_ we are done.
+ i += 1 # advance
+
+ # Find the next non-blank line
+ for j in range(i, len(lines)):
+ if lines[j].strip():
+ next_line = lines[j]; break
+ else:
+ break # There is no more text; we are done.
+
+ # Check if the next non-blank line is tabbed
+ if detab(next_line): # Yes, more work to do.
+ items.append("")
+ continue
+ else:
+ break # No, we are done.
+ else:
+ i += 1
+
+ return items, lines[i:]
+
+class HeaderPreprocessor(markdown.Preprocessor):
+
+ """Replace underlined headers with hashed headers.
+
+ (To avoid the need for lookahead later.)
+
+ """
+
+ def run (self, lines):
+ i = -1
+ while i+1 < len(lines):
+ i = i+1
+ if not lines[i].strip():
+ continue
+
+ if lines[i].startswith("#"):
+ lines.insert(i+1, "\n")
+
+ if (i+1 <= len(lines)
+ and lines[i+1]
+ and lines[i+1][0] in ['-', '=']):
+
+ underline = lines[i+1].strip()
+
+ if underline == "="*len(underline):
+ lines[i] = "# " + lines[i].strip()
+ lines[i+1] = ""
+ elif underline == "-"*len(underline):
+ lines[i] = "## " + lines[i].strip()
+ lines[i+1] = ""
+
+ return lines
+
+
+class LinePreprocessor(markdown.Preprocessor):
+ """Convert HR lines to "___" format."""
+ blockquote_re = re.compile(r'^(> )+')
+
+ def run (self, lines):
+ for i in range(len(lines)):
+ prefix = ''
+ m = self.blockquote_re.search(lines[i])
+ if m:
+ prefix = m.group(0)
+ if self._isLine(lines[i][len(prefix):]):
+ lines[i] = prefix + "___"
+ return lines
+
+ def _isLine(self, block):
+ """Determine if a block should be replaced with an <HR>"""
+ if block.startswith(" "):
+ return False # a code block
+ text = "".join([x for x in block if not x.isspace()])
+ if len(text) <= 2:
+ return False
+ for pattern in ['isline1', 'isline2', 'isline3']:
+ m = CORE_RE[pattern].match(text)
+ if (m and m.group(1)):
+ return True
+ else:
+ return False
+
+
+class LegacyExtension(markdown.Extension):
+ """ Replace Markdown's core parser. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Set the core parser to an instance of MarkdownParser. """
+ md.parser = MarkdownParser()
+ md.preprocessors.add ("header", HeaderPreprocessor(self), "<reference")
+ md.preprocessors.add("line", LinePreprocessor(self), "<reference")
+
+
+def makeExtension(configs={}):
+ return LegacyExtension(configs=configs)
+
diff --git a/src/contrib/markdown/extensions/meta.py b/src/contrib/markdown/extensions/meta.py
new file mode 100644
index 0000000..1b555b2
--- /dev/null
+++ b/src/contrib/markdown/extensions/meta.py
@@ -0,0 +1,90 @@
+#!usr/bin/python
+
+"""
+Meta Data Extension for Python-Markdown
+=======================================
+
+This extension adds Meta Data handling to markdown.
+
+Basic Usage:
+
+ >>> import markdown
+ >>> text = '''Title: A Test Doc.
+ ... Author: Waylan Limberg
+ ... John Doe
+ ... Blank_Data:
+ ...
+ ... The body. This is paragraph one.
+ ... '''
+ >>> md = markdown.Markdown(['meta'])
+ >>> md.convert(text)
+ u'<p>The body. This is paragraph one.</p>'
+ >>> md.Meta
+ {u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']}
+
+Make sure text without Meta Data still works (markdown < 1.6b returns a <p>).
+
+ >>> text = ' Some Code - not extra lines of meta data.'
+ >>> md = markdown.Markdown(['meta'])
+ >>> md.convert(text)
+ u'<pre><code>Some Code - not extra lines of meta data.\\n</code></pre>'
+ >>> md.Meta
+ {}
+
+Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
+
+Project website: <http://www.freewisdom.org/project/python-markdown/Meta-Data>
+Contact: markdown@freewisdom.org
+
+License: BSD (see ../docs/LICENSE for details)
+
+"""
+
+import markdown, re
+
+# Global Vars
+META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)')
+META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)')
+
+class MetaExtension (markdown.Extension):
+ """ Meta-Data extension for Python-Markdown. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add MetaPreprocessor to Markdown instance. """
+
+ md.preprocessors.add("meta", MetaPreprocessor(md), "_begin")
+
+
+class MetaPreprocessor(markdown.preprocessors.Preprocessor):
+ """ Get Meta-Data. """
+
+ def run(self, lines):
+ """ Parse Meta-Data and store in Markdown.Meta. """
+ meta = {}
+ key = None
+ while 1:
+ line = lines.pop(0)
+ if line.strip() == '':
+ break # blank line - done
+ m1 = META_RE.match(line)
+ if m1:
+ key = m1.group('key').lower().strip()
+ meta[key] = [m1.group('value').strip()]
+ else:
+ m2 = META_MORE_RE.match(line)
+ if m2 and key:
+ # Add another line to existing key
+ meta[key].append(m2.group('value').strip())
+ else:
+ lines.insert(0, line)
+ break # no meta data - done
+ self.markdown.Meta = meta
+ return lines
+
+
+def makeExtension(configs={}):
+ return MetaExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/src/contrib/markdown/extensions/rss.py b/src/contrib/markdown/extensions/rss.py
new file mode 100644
index 0000000..1274da2
--- /dev/null
+++ b/src/contrib/markdown/extensions/rss.py
@@ -0,0 +1,114 @@
+import markdown
+from markdown import etree
+
+DEFAULT_URL = "http://www.freewisdom.org/projects/python-markdown/"
+DEFAULT_CREATOR = "Yuri Takhteyev"
+DEFAULT_TITLE = "Markdown in Python"
+GENERATOR = "http://www.freewisdom.org/projects/python-markdown/markdown2rss"
+
+month_map = { "Jan" : "01",
+ "Feb" : "02",
+ "March" : "03",
+ "April" : "04",
+ "May" : "05",
+ "June" : "06",
+ "July" : "07",
+ "August" : "08",
+ "September" : "09",
+ "October" : "10",
+ "November" : "11",
+ "December" : "12" }
+
+def get_time(heading):
+
+ heading = heading.split("-")[0]
+ heading = heading.strip().replace(",", " ").replace(".", " ")
+
+ month, date, year = heading.split()
+ month = month_map[month]
+
+ return rdftime(" ".join((month, date, year, "12:00:00 AM")))
+
+def rdftime(time):
+
+ time = time.replace(":", " ")
+ time = time.replace("/", " ")
+ time = time.split()
+ return "%s-%s-%sT%s:%s:%s-08:00" % (time[0], time[1], time[2],
+ time[3], time[4], time[5])
+
+
+def get_date(text):
+ return "date"
+
+class RssExtension (markdown.Extension):
+
+ def extendMarkdown(self, md, md_globals):
+
+ self.config = { 'URL' : [DEFAULT_URL, "Main URL"],
+ 'CREATOR' : [DEFAULT_CREATOR, "Feed creator's name"],
+ 'TITLE' : [DEFAULT_TITLE, "Feed title"] }
+
+ md.xml_mode = True
+
+ # Insert a tree-processor that would actually add the title tag
+ treeprocessor = RssTreeProcessor(md)
+ treeprocessor.ext = self
+ md.treeprocessors['rss'] = treeprocessor
+ md.stripTopLevelTags = 0
+ md.docType = '<?xml version="1.0" encoding="utf-8"?>\n'
+
+class RssTreeProcessor(markdown.treeprocessors.Treeprocessor):
+
+ def run (self, root):
+
+ rss = etree.Element("rss")
+ rss.set("version", "2.0")
+
+ channel = etree.SubElement(rss, "channel")
+
+ for tag, text in (("title", self.ext.getConfig("TITLE")),
+ ("link", self.ext.getConfig("URL")),
+ ("description", None)):
+
+ element = etree.SubElement(channel, tag)
+ element.text = text
+
+ for child in root:
+
+ if child.tag in ["h1", "h2", "h3", "h4", "h5"]:
+
+ heading = child.text.strip()
+ item = etree.SubElement(channel, "item")
+ link = etree.SubElement(item, "link")
+ link.text = self.ext.getConfig("URL")
+ title = etree.SubElement(item, "title")
+ title.text = heading
+
+ guid = ''.join([x for x in heading if x.isalnum()])
+ guidElem = etree.SubElement(item, "guid")
+ guidElem.text = guid
+ guidElem.set("isPermaLink", "false")
+
+ elif child.tag in ["p"]:
+ try:
+ description = etree.SubElement(item, "description")
+ except UnboundLocalError:
+ # Item not defined - moving on
+ pass
+ else:
+ if len(child):
+ content = "\n".join([etree.tostring(node)
+ for node in child])
+ else:
+ content = child.text
+ pholder = self.markdown.htmlStash.store(
+ "<![CDATA[ %s]]>" % content)
+ description.text = pholder
+
+ return rss
+
+
+def makeExtension(configs):
+
+ return RssExtension(configs)
diff --git a/src/contrib/markdown/extensions/tables.py b/src/contrib/markdown/extensions/tables.py
new file mode 100644
index 0000000..1d3c920
--- /dev/null
+++ b/src/contrib/markdown/extensions/tables.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env Python
+"""
+Tables Extension for Python-Markdown
+====================================
+
+Added parsing of tables to Python-Markdown.
+
+A simple example:
+
+ First Header | Second Header
+ ------------- | -------------
+ Content Cell | Content Cell
+ Content Cell | Content Cell
+
+Copyright 2009 - [Waylan Limberg](http://achinghead.com)
+"""
+import markdown
+from markdown import etree
+
+
+class TableProcessor(markdown.blockprocessors.BlockProcessor):
+ """ Process Tables. """
+
+ def test(self, parent, block):
+ rows = block.split('\n')
+ return (len(rows) > 2 and '|' in rows[0] and
+ '|' in rows[1] and '-' in rows[1] and
+ rows[1][0] in ['|', ':', '-'])
+
+ def run(self, parent, blocks):
+ """ Parse a table block and build table. """
+ block = blocks.pop(0).split('\n')
+ header = block[:2]
+ rows = block[2:]
+ # Get format type (bordered by pipes or not)
+ border = False
+ if header[0].startswith('|'):
+ border = True
+ # Get alignment of columns
+ align = []
+ for c in self._split_row(header[1], border):
+ if c.startswith(':') and c.endswith(':'):
+ align.append('center')
+ elif c.startswith(':'):
+ align.append('left')
+ elif c.endswith(':'):
+ align.append('right')
+ else:
+ align.append(None)
+ # Build table
+ table = etree.SubElement(parent, 'table')
+ thead = etree.SubElement(table, 'thead')
+ self._build_row(header[0], thead, align, border)
+ tbody = etree.SubElement(table, 'tbody')
+ for row in rows:
+ self._build_row(row, tbody, align, border)
+
+ def _build_row(self, row, parent, align, border):
+ """ Given a row of text, build table cells. """
+ tr = etree.SubElement(parent, 'tr')
+ tag = 'td'
+ if parent.tag == 'thead':
+ tag = 'th'
+ cells = self._split_row(row, border)
+ # We use align here rather than cells to ensure every row
+ # contains the same number of columns.
+ for i, a in enumerate(align):
+ c = etree.SubElement(tr, tag)
+ try:
+ c.text = cells[i].strip()
+ except IndexError:
+ c.text = ""
+ if a:
+ c.set('align', a)
+
+ def _split_row(self, row, border):
+ """ split a row of text into list of cells. """
+ if border:
+ if row.startswith('|'):
+ row = row[1:]
+ if row.endswith('|'):
+ row = row[:-1]
+ return row.split('|')
+
+
+class TableExtension(markdown.Extension):
+ """ Add tables to Markdown. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add an instance of TableProcessor to BlockParser. """
+ md.parser.blockprocessors.add('table',
+ TableProcessor(md.parser),
+ '<hashheader')
+
+
+def makeExtension(configs={}):
+ return TableExtension(configs=configs)
diff --git a/src/contrib/markdown/extensions/toc.py b/src/contrib/markdown/extensions/toc.py
new file mode 100644
index 0000000..1624ccf
--- /dev/null
+++ b/src/contrib/markdown/extensions/toc.py
@@ -0,0 +1,140 @@
+"""
+Table of Contents Extension for Python-Markdown
+* * *
+
+(c) 2008 [Jack Miller](http://codezen.org)
+
+Dependencies:
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+
+"""
+import markdown
+from markdown import etree
+import re
+
+class TocTreeprocessor(markdown.treeprocessors.Treeprocessor):
+ # Iterator wrapper to get parent and child all at once
+ def iterparent(self, root):
+ for parent in root.getiterator():
+ for child in parent:
+ yield parent, child
+
+ def run(self, doc):
+ div = etree.Element("div")
+ div.attrib["class"] = "toc"
+ last_li = None
+
+ # Add title to the div
+ if self.config["title"][0]:
+ header = etree.SubElement(div, "span")
+ header.attrib["class"] = "toctitle"
+ header.text = self.config["title"][0]
+
+ level = 0
+ list_stack=[div]
+ header_rgx = re.compile("[Hh][123456]")
+
+ # Get a list of id attributes
+ used_ids = []
+ for c in doc.getiterator():
+ if "id" in c.attrib:
+ used_ids.append(c.attrib["id"])
+
+ for (p, c) in self.iterparent(doc):
+ if not c.text:
+ continue
+
+ # To keep the output from screwing up the
+ # validation by putting a <div> inside of a <p>
+ # we actually replace the <p> in its entirety.
+ # We do not allow the marker inside a header as that
+ # would causes an enless loop of placing a new TOC
+ # inside previously generated TOC.
+
+ if c.text.find(self.config["marker"][0]) > -1 and not header_rgx.match(c.tag):
+ for i in range(len(p)):
+ if p[i] == c:
+ p[i] = div
+ break
+
+ if header_rgx.match(c.tag):
+ tag_level = int(c.tag[-1])
+
+ # Regardless of how many levels we jumped
+ # only one list should be created, since
+ # empty lists containing lists are illegal.
+
+ if tag_level < level:
+ list_stack.pop()
+ level = tag_level
+
+ if tag_level > level:
+ newlist = etree.Element("ul")
+ if last_li:
+ last_li.append(newlist)
+ else:
+ list_stack[-1].append(newlist)
+ list_stack.append(newlist)
+ level = tag_level
+
+ # Do not override pre-existing ids
+ if not "id" in c.attrib:
+ id = self.config["slugify"][0](c.text)
+ if id in used_ids:
+ ctr = 1
+ while "%s_%d" % (id, ctr) in used_ids:
+ ctr += 1
+ id = "%s_%d" % (id, ctr)
+ used_ids.append(id)
+ c.attrib["id"] = id
+ else:
+ id = c.attrib["id"]
+
+ # List item link, to be inserted into the toc div
+ last_li = etree.Element("li")
+ link = etree.SubElement(last_li, "a")
+ link.text = c.text
+ link.attrib["href"] = '#' + id
+
+ if int(self.config["anchorlink"][0]):
+ anchor = etree.SubElement(c, "a")
+ anchor.text = c.text
+ anchor.attrib["href"] = "#" + id
+ anchor.attrib["class"] = "toclink"
+ c.text = ""
+
+ list_stack[-1].append(last_li)
+
+class TocExtension(markdown.Extension):
+ def __init__(self, configs):
+ self.config = { "marker" : ["[TOC]",
+ "Text to find and replace with Table of Contents -"
+ "Defaults to \"[TOC]\""],
+ "slugify" : [self.slugify,
+ "Function to generate anchors based on header text-"
+ "Defaults to a built in slugify function."],
+ "title" : [None,
+ "Title to insert into TOC <div> - "
+ "Defaults to None"],
+ "anchorlink" : [0,
+ "1 if header should be a self link"
+ "Defaults to 0"]}
+
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ # This is exactly the same as Django's slugify
+ def slugify(self, value):
+ """ Slugify a string, to make it URL friendly. """
+ import unicodedata
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+ value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
+ return re.sub('[-\s]+','-',value)
+
+ def extendMarkdown(self, md, md_globals):
+ tocext = TocTreeprocessor(md)
+ tocext.config = self.config
+ md.treeprocessors.add("toc", tocext, "_begin")
+
+def makeExtension(configs={}):
+ return TocExtension(configs=configs)
diff --git a/src/contrib/markdown/extensions/wikilinks.py b/src/contrib/markdown/extensions/wikilinks.py
new file mode 100644
index 0000000..df44e1c
--- /dev/null
+++ b/src/contrib/markdown/extensions/wikilinks.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+
+'''
+WikiLinks Extension for Python-Markdown
+======================================
+
+Converts [[WikiLinks]] to relative links. Requires Python-Markdown 2.0+
+
+Basic usage:
+
+ >>> import markdown
+ >>> text = "Some text with a [[WikiLink]]."
+ >>> html = markdown.markdown(text, ['wikilinks'])
+ >>> html
+ u'<p>Some text with a <a class="wikilink" href="/WikiLink/">WikiLink</a>.</p>'
+
+Whitespace behavior:
+
+ >>> markdown.markdown('[[ foo bar_baz ]]', ['wikilinks'])
+ u'<p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p>'
+ >>> markdown.markdown('foo [[ ]] bar', ['wikilinks'])
+ u'<p>foo bar</p>'
+
+To define custom settings the simple way:
+
+ >>> markdown.markdown(text,
+ ... ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']
+ ... )
+ u'<p>Some text with a <a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>'
+
+Custom settings the complex way:
+
+ >>> md = markdown.Markdown(
+ ... extensions = ['wikilinks'],
+ ... extension_configs = {'wikilinks': [
+ ... ('base_url', 'http://example.com/'),
+ ... ('end_url', '.html'),
+ ... ('html_class', '') ]},
+ ... safe_mode = True)
+ >>> md.convert(text)
+ u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>'
+
+Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
+
+ >>> text = """wiki_base_url: http://example.com/
+ ... wiki_end_url: .html
+ ... wiki_html_class:
+ ...
+ ... Some text with a [[WikiLink]]."""
+ >>> md = markdown.Markdown(extensions=['meta', 'wikilinks'])
+ >>> md.convert(text)
+ u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>'
+
+MetaData should not carry over to next document:
+
+ >>> md.convert("No [[MetaData]] here.")
+ u'<p>No <a class="wikilink" href="/MetaData/">MetaData</a> here.</p>'
+
+Define a custom URL builder:
+
+ >>> def my_url_builder(label, base, end):
+ ... return '/bar/'
+ >>> md = markdown.Markdown(extensions=['wikilinks'],
+ ... extension_configs={'wikilinks' : [('build_url', my_url_builder)]})
+ >>> md.convert('[[foo]]')
+ u'<p><a class="wikilink" href="/bar/">foo</a></p>'
+
+From the command line:
+
+ python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt
+
+By [Waylan Limberg](http://achinghead.com/).
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+'''
+
+import markdown
+import re
+
+def build_url(label, base, end):
+ """ Build a url from the label, a base, and an end. """
+ clean_label = re.sub(r'([ ]+_)|(_[ ]+)|([ ]+)', '_', label)
+ return '%s%s%s'% (base, clean_label, end)
+
+
+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.'],
+ 'build_url' : [build_url, 'Callable formats URL from label.'],
+ }
+
+ # Override defaults with user settings
+ for key, value in configs :
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ self.md = md
+
+ # append to end of inline patterns
+ WIKILINK_RE = r'\[\[([A-Za-z0-9_ -]+)\]\]'
+ wikilinkPattern = WikiLinks(WIKILINK_RE, self.config)
+ wikilinkPattern.md = md
+ md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong")
+
+
+class WikiLinks(markdown.inlinepatterns.Pattern):
+ def __init__(self, pattern, config):
+ markdown.inlinepatterns.Pattern.__init__(self, pattern)
+ self.config = config
+
+ def handleMatch(self, m):
+ if m.group(2).strip():
+ base_url, end_url, html_class = self._getMeta()
+ label = m.group(2).strip()
+ url = self.config['build_url'][0](label, base_url, end_url)
+ a = markdown.etree.Element('a')
+ a.text = label
+ a.set('href', url)
+ if html_class:
+ a.set('class', html_class)
+ else:
+ a = ''
+ return a
+
+ def _getMeta(self):
+ """ Return meta data or config data. """
+ base_url = self.config['base_url'][0]
+ end_url = self.config['end_url'][0]
+ html_class = self.config['html_class'][0]
+ if hasattr(self.md, 'Meta'):
+ if self.md.Meta.has_key('wiki_base_url'):
+ base_url = self.md.Meta['wiki_base_url'][0]
+ if self.md.Meta.has_key('wiki_end_url'):
+ end_url = self.md.Meta['wiki_end_url'][0]
+ if self.md.Meta.has_key('wiki_html_class'):
+ html_class = self.md.Meta['wiki_html_class'][0]
+ return base_url, end_url, html_class
+
+
+def makeExtension(configs=None) :
+ return WikiLinkExtension(configs=configs)
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+