aboutsummaryrefslogtreecommitdiffstats
path: root/markdown_extensions
diff options
context:
space:
mode:
Diffstat (limited to 'markdown_extensions')
-rw-r--r--markdown_extensions/__init__.py0
-rw-r--r--markdown_extensions/codehilite.py218
-rw-r--r--markdown_extensions/extra.py52
-rw-r--r--markdown_extensions/fenced_code.py105
-rw-r--r--markdown_extensions/footnotes.py276
-rw-r--r--markdown_extensions/headerid.py202
-rw-r--r--markdown_extensions/imagelinks.py135
-rw-r--r--markdown_extensions/meta.py81
-rw-r--r--markdown_extensions/rss.py118
-rw-r--r--markdown_extensions/tables.py72
-rw-r--r--markdown_extensions/wikilink.py140
11 files changed, 1399 insertions, 0 deletions
diff --git a/markdown_extensions/__init__.py b/markdown_extensions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/markdown_extensions/__init__.py
diff --git a/markdown_extensions/codehilite.py b/markdown_extensions/codehilite.py
new file mode 100644
index 0000000..73c1a79
--- /dev/null
+++ b/markdown_extensions/codehilite.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+
+"""
+CodeHilite Extension for Python-Markdown
+=======================================
+
+Adds code/syntax highlighting to standard Python-Markdown code blocks.
+
+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.2 (April 30, 2008)
+
+Dependencies:
+* [Python 2.3+](http://python.org/)
+* [Markdown 1.7+](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 = text)
+ >>> html = code.hilite()
+
+ * src: Can be a string or any object with a .readline attribute.
+
+ * linenos: (Boolen) Turns line numbering 'on' or 'off' (off by default).
+
+ Low Level Usage:
+ >>> code = CodeHilite()
+ >>> code.src = text # String or anything with a .readline attribute
+ >>> code.linenos = True # True or False; Turns line numbering on or of.
+ >>> html = code.hilite()
+
+ """
+
+ def __init__(self, src=None, linenos = False):
+ self.src = src
+ self.lang = None
+ self.linenos = linenos
+
+ 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 num:
+ txt = self._number(txt)
+ else :
+ txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt'''
+ txt = self._number(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="codehilite")
+ 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 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")
+
+
+
+# ------------------ The Markdown Extension -------------------------------
+class CodeHiliteExtention(markdown.Extension):
+ def __init__(self, configs):
+ # define default configs
+ self.config = {
+ '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 `_processCodeBlock` method 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.
+
+ * parent_elem: DOM element to which the content will be added
+ * lines: a list of lines
+ * inList: a level
+
+ returns: None
+
+ """
+
+ detabbed, theRest = md.blockGuru.detectTabbed(lines)
+ text = "\n".join(detabbed).rstrip()+"\n"
+ code = CodeHilite(text, linenos=self.config['force_linenos'][0])
+ placeholder = md.htmlStash.store(code.hilite(), safe=True)
+ if parent_elem.text:
+ parent_elem.text += placeholder
+ else:
+ parent_elem.text = placeholder
+ md._processSection(parent_elem, theRest, inList)
+
+ md._processCodeBlock = _hiliteCodeBlock
+
+def makeExtension(configs={}):
+ return CodeHiliteExtention(configs=configs)
+
diff --git a/markdown_extensions/extra.py b/markdown_extensions/extra.py
new file mode 100644
index 0000000..e99bec4
--- /dev/null
+++ b/markdown_extensions/extra.py
@@ -0,0 +1,52 @@
+#!/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/).
+
+As no-one has yet written a Definition List extension for Python-
+Markdown, definition lists are not yet supported by 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 simply continue without that
+extension. If you would like to be notified of such failures,
+you may set Python-Markdown's logger level to "WARN".
+
+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',
+ '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/markdown_extensions/fenced_code.py b/markdown_extensions/fenced_code.py
new file mode 100644
index 0000000..c3d9f7f
--- /dev/null
+++ b/markdown_extensions/fenced_code.py
@@ -0,0 +1,105 @@
+#!/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:\\n</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:\\n</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 = '''
+ ... ~~~~
+ ... block one
+ ... ~~~~{.python}
+ ...
+ ... ~~~~
+ ... <p>block two</p>
+ ... ~~~~{.html}'''
+ >>> 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>'
+
+"""
+
+import markdown, re
+
+# Global vars
+FENCED_BLOCK_RE = re.compile( \
+ r'(?P<fence>^~{3,})[ ]*\n(?P<code>.*?)(?P=fence)[ ]*(\{\.(?P<lang>[a-zA-Z0-9_-]*)\})?[ ]*$',
+ 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. """
+
+ FENCED_BLOCK_PREPROCESSOR = FencedBlockPreprocessor()
+ FENCED_BLOCK_PREPROCESSOR.md = md
+ md.textPreprocessors.insert(0, FENCED_BLOCK_PREPROCESSOR)
+
+
+class FencedBlockPreprocessor(markdown.TextPreprocessor):
+
+ def run(self, text):
+ """ Match and store Fenced Code Blocks in the HtmlStash. """
+ 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.md.htmlStash.store(code, safe=True)
+ text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
+ else:
+ break
+ return text
+
+ 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/markdown_extensions/footnotes.py b/markdown_extensions/footnotes.py
new file mode 100644
index 0000000..b46efbb
--- /dev/null
+++ b/markdown_extensions/footnotes.py
@@ -0,0 +1,276 @@
+"""
+========================= 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
+
+"""
+
+FN_BACKLINK_TEXT = "zz1337820767766393qq"
+
+
+import re, markdown, random
+from markdown import etree
+
+class FootnoteExtension (markdown.Extension):
+
+ DEF_RE = re.compile(r'(\ ?\ ?\ ?)\[\^([^\]]*)\]:\s*(.*)')
+ SHORT_USE_RE = re.compile(r'\[\^([^\]]*)\]', re.M) # [^a]
+
+ def __init__ (self, 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) :
+
+ self.md = md
+
+ # Stateless extensions do not need to be registered
+ md.registerExtension(self)
+
+ # Insert a preprocessor before ReferencePreprocessor
+ index = md.preprocessors.index(md_globals['REFERENCE_PREPROCESSOR'])
+ preprocessor = FootnotePreprocessor(self)
+ preprocessor.md = md
+ md.preprocessors.insert(index, preprocessor)
+
+ # Insert an inline pattern before ImageReferencePattern
+ FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
+ index = md.inlinePatterns.index(md_globals['IMAGE_REFERENCE_PATTERN'])
+ md.inlinePatterns.insert(index, FootnotePattern(FOOTNOTE_RE, self))
+
+ # Insert a post-processor that would actually add the footnote div
+ postprocessor = FootnotePostprocessor(self)
+ postprocessor.extension = self
+
+ md.postprocessors.append(postprocessor)
+
+ textPostprocessor = FootnoteTextPostprocessor(self)
+
+ md.textPostprocessors.append(textPostprocessor)
+
+
+ def reset(self) :
+ # May be called by Markdown is state reset is desired
+
+ self.footnote_suffix = "-" + str(int(random.random()*1000000000))
+ self.used_footnotes={}
+ self.footnotes = {}
+
+ def findFootnotesPlaceholder(self, root):
+
+ 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) :
+ self.footnotes[id] = text
+
+ def makeFootnoteId(self, num) :
+ return 'fn%d%s' % (num, self.footnote_suffix)
+
+ def makeFootnoteRefId(self, num) :
+ return 'fnr%d%s' % (num, self.footnote_suffix)
+
+ def makeFootnotesDiv (self, root) :
+ """Creates the div with class='footnote' and populates it with
+ the text of the footnotes.
+
+ @returns: the footnote div as a dom 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")
+
+
+ footnotes = [(self.used_footnotes[id], id)
+ for id in self.footnotes.keys()]
+ footnotes.sort()
+
+ for i, id in footnotes :
+ li = etree.SubElement(ol, "li")
+ li.set("id", self.makeFootnoteId(i))
+
+ self.md._processSection(li, self.footnotes[id].split("\n"), looseList=1)
+
+ backlink = etree.Element("a")
+ backlink.set("href", "#" + self.makeFootnoteRefId(i))
+ backlink.set("class", "footnoteBackLink")
+ backlink.set("title",
+ "Jump back to footnote %d in the text" % i)
+ backlink.text = FN_BACKLINK_TEXT
+
+ if li.getchildren():
+ node = li[-1]
+ if node.text:
+ li.append(backlink)
+ elif node.tag == "p":
+ node.append(backlink)
+ else:
+ p = etree.SubElement(li, "p")
+ p.append(backlink)
+ div = self.md.applyInlinePatterns(etree.ElementTree(div)).getroot()
+ return div
+
+
+class FootnotePreprocessor :
+
+ def __init__ (self, footnotes) :
+ self.footnotes = footnotes
+
+ def run(self, lines) :
+
+ self.blockGuru = markdown.BlockGuru()
+ lines = self._handleFootnoteDefinitions (lines)
+
+ # Make a hash of all footnote marks in the text so that we
+ # know in what order they are supposed to appear. (This
+ # function call doesn't really substitute anything - it's just
+ # a way to get a callback for each occurence.
+
+ text = "\n".join(lines)
+ self.footnotes.SHORT_USE_RE.sub(self.recordFootnoteUse, text)
+
+ return text.split("\n")
+
+
+ def recordFootnoteUse(self, match) :
+
+ id = match.group(1)
+ id = id.strip()
+ nextNum = len(self.footnotes.used_footnotes.keys()) + 1
+ self.footnotes.used_footnotes[id] = nextNum
+
+
+ def _handleFootnoteDefinitions(self, lines) :
+ """Recursively finds all footnote definitions in the lines.
+
+ @param lines: a list of lines of text
+ @returns: a string representing the text with footnote
+ definitions removed """
+
+ i, id, footnote = self._findFootnoteDefinition(lines)
+
+ if id :
+
+ plain = lines[:i]
+
+ detabbed, theRest = self.blockGuru.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) :
+ """Finds the first line of a footnote definition.
+
+ @param lines: a list of lines of text
+ @returns: the index of the line containing a footnote definition """
+
+ counter = 0
+ for line in lines :
+ m = self.footnotes.DEF_RE.match(line)
+ if m :
+ return counter, m.group(2), m.group(3)
+ counter += 1
+ return counter, None, None
+
+
+class FootnotePattern (markdown.Pattern) :
+
+ def __init__ (self, pattern, footnotes) :
+
+ markdown.Pattern.__init__(self, pattern)
+ self.footnotes = footnotes
+
+ def handleMatch(self, m) :
+ sup = etree.Element("sup")
+ a = etree.SubElement(sup, "a")
+ id = m.group(2)
+ num = self.footnotes.used_footnotes[id]
+ sup.set('id', self.footnotes.makeFootnoteRefId(num))
+ a.set('href', '#' + self.footnotes.makeFootnoteId(num))
+ a.text = str(num)
+ return sup
+
+class FootnotePostprocessor (markdown.Postprocessor):
+
+ def __init__ (self, footnotes) :
+ self.footnotes = footnotes
+
+ def run(self, root):
+ footnotesDiv = self.footnotes.makeFootnotesDiv(root)
+ if footnotesDiv:
+ result = self.extension.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 FootnoteTextPostprocessor (markdown.Postprocessor):
+
+ def __init__ (self, footnotes) :
+ self.footnotes = footnotes
+
+ def run(self, text) :
+ return text.replace(FN_BACKLINK_TEXT, "&#8617;")
+
+def makeExtension(configs=[]):
+ return FootnoteExtension(configs=configs)
+
diff --git a/markdown_extensions/headerid.py b/markdown_extensions/headerid.py
new file mode 100644
index 0000000..8947338
--- /dev/null
+++ b/markdown_extensions/headerid.py
@@ -0,0 +1,202 @@
+#!/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\\n<h1 id="header_1">Another Header</h1>\\n\\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\\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\\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>'
+
+By [Waylan Limberg](http://achinghead.com/).
+
+Project website: http://achinghead.com/markdown-headerid/
+Contact: waylan [at] gmail [dot] com
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
+
+Version: 0.1 (May 2, 2008)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 1.7+](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 + '-_'
+
+HEADER_RE = re.compile(r'''^(\#{1,6}) # group(1) = string of hashes
+ ( [^{^#]*) # group(2) = Header text
+ [\#]* # optional closing hashes (not counted)
+ (?:[ \t]*\{[ \t]*\#([-_:a-zA-Z0-9]+)[ \t]*\})? # group(3) = id attr''',
+ re.VERBOSE)
+
+IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
+
+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.'],
+ 'toc_id' : ['toc', 'Set html id of wrapper div for TOC.'],
+ 'toc_marker': ['///TOC///', 'Marker to identify position of TOC.']
+ }
+
+ for key, value in configs:
+ self.setConfig(key, value)
+
+
+ def extendMarkdown(self, md, md_globals) :
+
+ md.IDs = []
+ md.toc = Toc(self.getConfig('toc_id'), self.getConfig('toc_marker'))
+
+ def _processHeaderId(parent_elem, paragraph) :
+ '''
+ Overrides _processHeader of Markdown() and
+ adds an 'id' to the header.
+ '''
+ m = HEADER_RE.match(paragraph[0])
+ if m :
+ start_level, force_id = _get_meta()
+ level = len(m.group(1)) + start_level
+ if level > 6:
+ level = 6
+ h = etree.Element("h%d" % level)
+ parent_elem.append(h)
+ inline = etree.SubElement(h, "inline")
+ inline.text = m.group(2).strip()
+ i = ''
+ if m.group(3):
+ i = _unique_id(m.group(3))
+ elif force_id:
+ i = _create_id(m.group(2).strip())
+ if i:
+ h.set('id', i)
+ md.toc.append(i, inline.text)
+ else :
+ message(CRITICAL, "We've got a problem header!")
+
+ md._processHeader = _processHeaderId
+
+ def _get_meta():
+ ''' Return meta data suported by this ext as a tuple '''
+ level = int(self.config['level'][0]) - 1
+ force = _str2bool(self.config['forceid'][0])
+ if hasattr(md, 'Meta'):
+ if md.Meta.has_key('header_level'):
+ level = int(md.Meta['header_level'][0]) - 1
+ if md.Meta.has_key('header_forceid'):
+ force = _str2bool(md.Meta['header_forceid'][0])
+ return level, force
+
+ def _str2bool(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(id):
+ ''' Ensure ID is unique. Append '_1', '_2'... if not '''
+ while id in md.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)
+ md.IDs.append(id)
+ return id
+
+
+ def _create_id(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 _unique_id(h)
+
+class Toc():
+ """ Store a Table of Contents from a documents Headers. """
+ def __init__(self, html_id, marker):
+ self.html_id = html_id
+ self.marker = marker
+ self.ids = []
+ self.labels = []
+
+ def append(self, id, label):
+ """ Append an item to the store. """
+ self.ids.append(id)
+ self.labels.append(label)
+
+ def render(self):
+ """ Render the TOC as HTML and return unicode. """
+ out = u'<div id="%s"><ul>\n' % self.html_id
+ for c in range(len(self.ids)):
+ out += u'<li><a href="#%s">%s</a></li>\n'%(self.ids[c], self.labels[c])
+ out += u'</ul></div>'
+ return out
+
+
+def makeExtension(configs=None) :
+ return HeaderIdExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+
diff --git a/markdown_extensions/imagelinks.py b/markdown_extensions/imagelinks.py
new file mode 100644
index 0000000..e545b24
--- /dev/null
+++ b/markdown_extensions/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/markdown_extensions/meta.py b/markdown_extensions/meta.py
new file mode 100644
index 0000000..30dea8a
--- /dev/null
+++ b/markdown_extensions/meta.py
@@ -0,0 +1,81 @@
+#!usr/bin/python
+
+'''
+Meta Data Extension for Python-Markdown
+==========================================
+
+This extension adds Meta Data handling to markdown.
+
+ >>> import markdown
+ >>> text = """Title: A Test Doc.
+ ... Author: Waylan Limberg
+ ... John Doe
+ ... Blank_Data:
+ ...
+ ... The body. This is paragraph one.
+ ... """
+ >>> md = markdown.Markdown(text, ['meta'])
+ >>> md.convert()
+ u'<p>The body. This is paragraph one.\\n</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(text, ['meta'])
+ >>> md.convert()
+ u'<pre><code>Some Code - not extra lines of meta data.\\n</code></pre>'
+ >>> md.Meta
+ {}
+
+'''
+
+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) :
+ def __init__(self, configs):
+ pass
+
+ def extendMarkdown(self, md, md_globals) :
+ self.md = md
+
+ # Insert meta preprocessor first
+ META_PREPROCESSOR = MetaPreprocessor()
+ META_PREPROCESSOR.md = md
+ md.preprocessors.insert(0, META_PREPROCESSOR)
+
+class MetaPreprocessor(markdown.Preprocessor) :
+ def run(self, lines) :
+ 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.md.Meta = meta
+ return lines
+
+
+def makeExtension(configs=None) :
+ return MetaExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/markdown_extensions/rss.py b/markdown_extensions/rss.py
new file mode 100644
index 0000000..b88b9b5
--- /dev/null
+++ b/markdown_extensions/rss.py
@@ -0,0 +1,118 @@
+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 post-processor that would actually add the title tag
+ postprocessor = RssPostProcessor(self)
+ postprocessor.ext = self
+ md.postprocessors.append(postprocessor)
+ md.stripTopLevelTags = 0
+ md.docType = '<?xml version="1.0" encoding="utf-8"?>\n'
+
+class RssPostProcessor (markdown.Postprocessor):
+
+ def __init__(self, md):
+
+ pass
+
+ 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"] :
+ if item:
+ description = etree.SubElement(item, "description")
+ if len(child):
+ content = "\n".join([etree.tostring(node)
+ for node in child])
+ else:
+ content = child.text
+ pholder = self.stash.store("<![CDATA[ %s]]>" % content)
+ description.text = pholder
+
+ return rss
+
+
+def makeExtension(configs):
+
+ return RssExtension(configs)
diff --git a/markdown_extensions/tables.py b/markdown_extensions/tables.py
new file mode 100644
index 0000000..829044c
--- /dev/null
+++ b/markdown_extensions/tables.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+"""
+Table extension for Python-Markdown
+"""
+
+import markdown
+from markdown import etree
+
+class TablePattern(markdown.Pattern) :
+ def __init__ (self, md):
+ markdown.Pattern.__init__(self, r'(^|\n)\|([^\n]*)\|')
+ self.md = md
+
+ def handleMatch(self, m):
+
+ # a single line represents a row
+ tr = etree.Element('tr')
+
+ # chunks between pipes represent cells
+
+ for t in m.group(3).split('|'):
+
+ if len(t) >= 2 and t.startswith('*') and t.endswith('*'):
+ # if a cell is bounded by asterisks, it is a <th>
+ td = etree.Element('th')
+ t = t[1:-1]
+ else:
+ # otherwise it is a <td>
+ td = etree.Element('td')
+
+ # add text ot inline section, later it will be
+ # processed by core
+ inline = etree.SubElement(td, "inline")
+ inline.text = t
+
+ tr.append(td)
+ tr.tail = "\n"
+
+ return tr
+
+
+class TablePostprocessor:
+
+ def _findElement(self, element, name):
+ result = []
+ for child in element:
+ if child.tag == name:
+ result.append(child)
+ result += self._findElement(child, name)
+ return result
+
+ def run(self, root):
+
+ for element in self._findElement(root, "p"):
+ for child in element:
+ if child.tail:
+ element.tag = "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/markdown_extensions/wikilink.py b/markdown_extensions/wikilink.py
new file mode 100644
index 0000000..47037a6
--- /dev/null
+++ b/markdown_extensions/wikilink.py
@@ -0,0 +1,140 @@
+#!/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
+ u'<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.\\n</p>'
+
+To define custom settings the simple way:
+
+ >>> md = markdown.markdown(text,
+ ... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)']
+ ... )
+ >>> md
+ u'<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.\\n</p>'
+
+Custom settings the complex way:
+
+ >>> md = markdown.Markdown(
+ ... extensions = ['wikilink'],
+ ... extension_configs = {'wikilink': [
+ ... ('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>.\\n</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', 'wikilink'])
+ >>> md.convert(text)
+ u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>'
+
+MetaData should not carry over to next document:
+
+ >>> md.convert("No MetaData here.")
+ u'<p>No <a href="/MetaData/" class="wikilink">MetaData</a> here.\\n</p>'
+
+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.6 (May 2, 2008)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 1.6+](http://www.freewisdom.org/projects/python-markdown/)
+'''
+
+import markdown
+from markdown import etree
+
+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.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ self.md = md
+
+ # append to end of inline patterns
+ WIKILINK_RE = r'''(?P<escape>\\|\b)(?P<camelcase>([A-Z]+[a-z-_]+){2,})\b'''
+ WIKILINK_PATTERN = WikiLinks(WIKILINK_RE, self.config)
+ WIKILINK_PATTERN.md = md
+ md.inlinePatterns.append(WIKILINK_PATTERN)
+
+
+class WikiLinks (markdown.BasePattern) :
+ def __init__(self, pattern, config):
+ markdown.BasePattern.__init__(self, pattern)
+ self.config = config
+
+ def handleMatch(self, m):
+ if m.group('escape') == '\\':
+ a = m.group('camelcase')
+ else:
+ base_url, end_url, html_class = self._getMeta()
+ url = '%s%s%s'% (base_url, m.group('camelcase'), end_url)
+ label = m.group('camelcase').replace('_', ' ')
+ a = etree.Element('a')
+ a.text = label
+ a.set('href', url)
+ if html_class:
+ a.set('class', html_class)
+ 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 type(self):
+ return "WLink"
+
+
+def makeExtension(configs=None) :
+ return WikiLinkExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+