Anonymous avatar Anonymous committed 7299f3e

[svn] * Added markdown.py (python-markdown) for new markdown support in text helper.
* Added textile.py (PyTextile) for new textilize support in text helper.
* Brought asset/date/text helpers up to date with revision info.

Comments (0)

Files changed (5)

 WebHelpers ChangeLog
 
 0.2 (**svn**)
-* Brought asset tag up to date with revision info.
+* Added markdown.py (python-markdown) for new markdown support in text helper.
+* Added textile.py (PyTextile) for new textilize support in text helper.
+* Brought asset/date/text helpers up to date with revision info.
 
 0.1.3 (Release)
 * Brought feedgenerator in line with Django's version, which fixed the missing

webhelpers/markdown.py

+#!/usr/bin/env python
+
+SPEED_TEST = 0
+
+"""
+====================================================================
+IF YOU ARE LOOKING TO EXTEND MARKDOWN, SEE THE "FOOTNOTES" SECTION
+====================================================================
+
+Python-Markdown
+===============
+
+Converts Markdown to HTML.  Basic usage as a module:
+
+    import markdown
+    html = markdown.markdown(your_text_string)
+
+Started by [Manfred Stienstra](http://www.dwerg.net/).  Continued and
+maintained  by [Yuri Takhteyev](http://www.freewisdom.org).
+
+Project website: http://www.freewisdom.org/projects/python-markdown
+Contact: yuri [at] freewisdom.org
+
+License: GPL 2 (http://www.gnu.org/copyleft/gpl.html) or BSD
+
+Version: 1.5 (May 15, 2006)
+
+For changelog, see end of file
+"""
+
+import re, sys, os, random
+
+# set debug level: 3 none, 2 critical, 1 informative, 0 all
+(VERBOSE, INFO, CRITICAL, NONE) = range(4)
+
+MESSAGE_THRESHOLD = CRITICAL
+
+def message(level, text) :
+    if level >= MESSAGE_THRESHOLD :
+        print text
+
+
+# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY -----------------
+
+# all tabs will be expanded to up to this many spaces
+TAB_LENGTH = 4
+ENABLE_ATTRIBUTES = 1
+SMART_EMPHASIS = 1
+
+# --------------- CONSTANTS YOU _SHOULD NOT_ HAVE TO CHANGE ----------
+
+FN_BACKLINK_TEXT = "zz1337820767766393qq"
+# a template for html placeholders
+HTML_PLACEHOLDER_PREFIX = "qaodmasdkwaspemas"
+HTML_PLACEHOLDER = HTML_PLACEHOLDER_PREFIX + "%dajkqlsmdqpakldnzsdfls"
+
+BLOCK_LEVEL_ELEMENTS = ['p', 'div', 'blockquote', 'pre', 'table',
+                        'dl', 'ol', 'ul', 'script', 'noscript',
+                        'form', 'fieldset', 'iframe', 'math', 'ins',
+                        'del', 'hr', 'hr/']
+
+def is_block_level (tag) :
+    return ( (tag in BLOCK_LEVEL_ELEMENTS) or
+             (tag[0] == 'h' and tag[1] in "0123456789") )
+
+"""
+======================================================================
+========================== NANODOM ===================================
+======================================================================
+
+The three classes below implement some of the most basic DOM
+methods.  I use this instead of minidom because I need a simpler
+functionality and do not want to require additional libraries.
+
+Importantly, NanoDom does not do normalization, which is what we
+want. It also adds extra white space when converting DOM to string
+"""
+
+
+class Document :
+
+    def appendChild(self, child) :
+        self.documentElement = child
+        child.parent = self
+        self.entities = {}
+
+    def createElement(self, tag, textNode=None) :
+        el = Element(tag)
+        el.doc = self
+        if textNode :
+            el.appendChild(self.createTextNode(textNode))
+        return el
+
+    def createTextNode(self, text) :
+        node = TextNode(text)
+        node.doc = self
+        return node
+
+    def createEntityReference(self, entity):
+        if entity not in self.entities:
+            self.entities[entity] = EntityReference(entity)
+        return self.entities[entity]
+
+    def toxml (self) :
+        return self.documentElement.toxml()
+
+    def normalizeEntities(self, text) :
+
+        pairs = [ #("&", "&"),
+                  ("<", "&lt;"),
+                  (">", "&gt;"),
+                  ("\"", "&quot;")]
+
+        for old, new in pairs :
+            text = text.replace(old, new)
+        return text
+
+    def find(self, test) :
+        return self.documentElement.find(test)
+
+    def unlink(self) :
+        self.documentElement.unlink()
+        self.documentElement = None
+
+
+class Element :
+
+    type = "element"
+
+    def __init__ (self, tag) :
+
+        self.nodeName = tag
+        self.attributes = []
+        self.attribute_values = {}
+        self.childNodes = []
+
+    def unlink(self) :
+        for child in self.childNodes :
+            if child.type == "element" :
+                child.unlink()
+        self.childNodes = None
+
+    def setAttribute(self, attr, value) :
+        if not attr in self.attributes :
+            self.attributes.append(attr)
+
+        self.attribute_values[attr] = value
+
+    def insertChild(self, position, child) :
+        self.childNodes.insert(position, child)
+        child.parent = self
+
+    def removeChild(self, child) :
+        self.childNodes.remove(child)
+
+    def replaceChild(self, oldChild, newChild) :
+        position = self.childNodes.index(oldChild)
+        self.removeChild(oldChild)
+        self.insertChild(position, newChild)
+
+    def appendChild(self, child) :
+        self.childNodes.append(child)
+        child.parent = self
+
+    def handleAttributes(self) :
+        pass
+
+    def find(self, test, depth=0) :
+        """ Returns a list of descendants that pass the test function """
+        matched_nodes = []
+        for child in self.childNodes :
+            if test(child) :
+                matched_nodes.append(child)
+            if child.type == "element" :
+                matched_nodes += child.find(test, depth+1)
+        return matched_nodes
+
+    def toxml(self):
+        if ENABLE_ATTRIBUTES :
+            for child in self.childNodes:
+                child.handleAttributes()
+        buffer = ""
+        if self.nodeName in ['h1', 'h2', 'h3', 'h4'] :
+            buffer += "\n"
+        elif self.nodeName in ['li'] :
+            buffer += "\n "
+        buffer += "<" + self.nodeName
+        for attr in self.attributes :
+            value = self.attribute_values[attr]
+            value = self.doc.normalizeEntities(value)
+            buffer += ' %s="%s"' % (attr, value)
+        if self.childNodes or self.nodeName in ['blockquote']:
+            buffer += ">"
+            for child in self.childNodes :
+                buffer += child.toxml()
+            if self.nodeName == 'p' :
+                buffer += "\n"
+            elif self.nodeName == 'li' :
+                buffer += "\n "
+            buffer += "</%s>" % self.nodeName
+        else :
+            buffer += "/>"
+        if self.nodeName in ['p', 'li', 'ul', 'ol',
+                             'h1', 'h2', 'h3', 'h4'] :
+            buffer += "\n"
+
+        return buffer
+
+
+class TextNode :
+
+    type = "text"
+    attrRegExp = re.compile(r'\{@([^\}]*)=([^\}]*)}') # {@id=123}
+
+    def __init__ (self, text) :
+        self.value = text        
+
+    def attributeCallback(self, match) :
+        self.parent.setAttribute(match.group(1), match.group(2))
+
+    def handleAttributes(self) :
+        self.value = self.attrRegExp.sub(self.attributeCallback, self.value)
+
+    def toxml(self) :
+        text = self.value
+        if not text.startswith(HTML_PLACEHOLDER_PREFIX):
+            if self.parent.nodeName == "p" :
+                text = text.replace("\n", "\n   ")
+            elif (self.parent.nodeName == "li"
+                  and self.parent.childNodes[0]==self):
+                text = "\n     " + text.replace("\n", "\n     ")
+        text = self.doc.normalizeEntities(text)
+        return text
+
+
+class EntityReference:
+
+    type = "entity_ref"
+
+    def __init__(self, entity):
+        self.entity = entity
+
+    def handleAttributes(self):
+        pass
+
+    def toxml(self):
+        return "&" + self.entity + ";"
+
+
+"""
+======================================================================
+========================== PRE-PROCESSORS ============================
+======================================================================
+
+Preprocessors munge source text before we start doing anything too
+complicated.
+
+Each preprocessor implements a "run" method that takes a pointer to
+a list of lines of the document, modifies it as necessary and
+returns either the same pointer or a pointer to a new list.
+"""
+
+class HeaderPreprocessor :
+
+    """
+       Replaces underlined headers with hashed headers to avoid
+       the nead for lookahead later.
+    """
+
+    def run (self, lines) :
+
+        for i in range(len(lines)) :
+            if not lines[i] :
+                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
+
+HEADER_PREPROCESSOR = HeaderPreprocessor()
+
+class LinePreprocessor :
+    """Deals with HR lines (needs to be done before processing lists)"""
+
+    def run (self, lines) :
+        for i in range(len(lines)) :
+            if self._isLine(lines[i]) :
+                lines[i] = "<hr />"
+        return lines
+
+    def _isLine(self, block) :
+        """Determines if a block should be replaced with an <HR>"""
+        if block.startswith("    ") : return 0  # a code block
+        text = "".join([x for x in block if not x.isspace()])
+        if len(text) <= 2 :
+            return 0
+        for pattern in ['isline1', 'isline2', 'isline3'] :
+            m = RE.regExp[pattern].match(text)
+            if (m and m.group(1)) :
+                return 1
+        else:
+            return 0
+
+LINE_PREPROCESSOR = LinePreprocessor()
+
+
+class LineBreaksPreprocessor :
+    """Replaces double spaces at the end of the lines with <br/ >."""
+
+    def run (self, lines) :
+        for i in range(len(lines)) :
+            if (lines[i].endswith("  ")
+                and not RE.regExp['tabbed'].match(lines[i]) ):
+                lines[i] += "<br />"
+        return lines
+
+LINE_BREAKS_PREPROCESSOR = LineBreaksPreprocessor()
+
+
+class HtmlBlockPreprocessor :
+    """Removes html blocks from self.lines"""
+
+    def run (self, lines) :
+        new_blocks = []
+        text = "\n".join(lines)
+        for block in text.split("\n\n") :
+            if block.startswith("\n") :
+                block = block[1:]
+            if ( (block.startswith("<") and block.rstrip().endswith(">"))
+                 and (block[1] in ["!", "?", "@", "%"]
+                      or is_block_level( block[1:].replace(">", " ")
+                                         .split()[0].lower()))) :
+                new_blocks.append(
+                    self.stash.store(block.strip()))
+            else :
+                new_blocks.append(block)
+        return "\n\n".join(new_blocks).split("\n")
+
+HTML_BLOCK_PREPROCESSOR = HtmlBlockPreprocessor()
+
+
+class ReferencePreprocessor :
+
+    def run (self, lines) :
+        new_text = [];
+        for line in lines:
+            m = RE.regExp['reference-def'].match(line)
+            if m:
+                id = m.group(2).strip().lower()
+                title = dequote(m.group(4).strip()) #.replace('"', "&quot;")
+                self.references[id] = (m.group(3), title)
+            else:
+                new_text.append(line)
+        return new_text #+ "\n"
+
+REFERENCE_PREPROCESSOR = ReferencePreprocessor()
+
+"""
+======================================================================
+========================== INLINE PATTERNS ===========================
+======================================================================
+
+Inline patterns such as *emphasis* are handled by means of auxiliary
+objects, one per pattern.  Each pattern object uses a single regular
+expression and needs support the following methods:
+
+  pattern.getCompiledRegExp() - returns a regular expression
+
+  pattern.handleMatch(m, doc) - takes a match object and returns
+                                a NanoDom node (as a part of the provided
+                                doc) or None
+
+All of python markdown's built-in patterns subclass from BasePatter,
+but you can add additional patterns that don't.
+
+Also note that all the regular expressions used by inline must
+capture the whole block.  For this reason, they all start with
+'^(.*)' and end with '(.*)!'.  In case with built-in expression
+BasePattern takes care of adding the "^(.*)" and "(.*)!".
+
+Finally, the order in which regular expressions are applied is very
+important - e.g. if we first replace http://.../ links with <a> tags
+and _then_ try to replace inline html, we would end up with a mess.
+So, we apply the expressions in the following order:
+
+       * escape and backticks have to go before everything else, so
+         that we can preempt any markdown patterns by escaping them.
+
+       * then we handle auto-links (must be done before inline html)
+
+       * then we handle inline HTML.  At this point we will simply
+         replace all inline HTML strings with a placeholder and add
+         the actual HTML to a hash.
+
+       * then inline images (must be done before links)
+
+       * then bracketed links, first regular then reference-style
+
+       * finally we apply strong and emphasis
+"""
+
+NOBRACKET = r'[^\]\[]*'
+BRK = ( r'\[('
+        + (NOBRACKET + r'(\['+NOBRACKET)*6
+        + (NOBRACKET+ r'\])*'+NOBRACKET)*6
+        + NOBRACKET + r')\]' )
+
+BACKTICK_RE = r'\`([^\`]*)\`'                    # `e= m*c^2`
+DOUBLE_BACKTICK_RE =  r'\`\`(.*)\`\`'            # ``e=f("`")``
+ESCAPE_RE = r'\\(.)'                             # \<
+EMPHASIS_RE = r'\*([^\*]*)\*'                    # *emphasis*
+STRONG_RE = r'\*\*(.*)\*\*'                      # **strong**
+STRONG_EM_RE = r'\*\*\*([^_]*)\*\*\*'            # ***strong***
+
+if SMART_EMPHASIS:
+    EMPHASIS_2_RE = r'(?<!\S)_(\S[^_]*)_'        # _emphasis_
+else :
+    EMPHASIS_2_RE = r'_([^_]*)_'                 # _emphasis_
+
+STRONG_2_RE = r'__([^_]*)__'                     # __strong__
+STRONG_EM_2_RE = r'___([^_]*)___'                # ___strong___
+
+LINK_RE = BRK + r'\s*\(([^\)]*)\)'               # [text](url)
+LINK_ANGLED_RE = BRK + r'\s*\(<([^\)]*)>\)'      # [text](<url>)
+IMAGE_LINK_RE = r'\!' + BRK + r'\s*\(([^\)]*)\)' # ![alttxt](http://x.com/)
+REFERENCE_RE = BRK+ r'\s*\[([^\]]*)\]'           # [Google][3]
+IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2]
+NOT_STRONG_RE = r'( \* )'                        # stand-alone * or _
+AUTOLINK_RE = r'<(http://[^>]*)>'                # <http://www.123.com>
+AUTOMAIL_RE = r'<([^> ]*@[^> ]*)>'               # <me@example.com>
+HTML_RE = r'(\<[^\>]*\>)'                        # <...>
+ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)'                # &amp;
+
+class BasePattern:
+
+    def __init__ (self, pattern) :
+        self.pattern = pattern
+        self.compiled_re = re.compile("^(.*)%s(.*)$" % pattern, re.DOTALL)
+
+    def getCompiledRegExp (self) :
+        return self.compiled_re
+
+class SimpleTextPattern (BasePattern) :
+
+    def handleMatch(self, m, doc) :
+        return doc.createTextNode(m.group(2))
+
+class SimpleTagPattern (BasePattern):
+
+    def __init__ (self, pattern, tag) :
+        BasePattern.__init__(self, pattern)
+        self.tag = tag
+
+    def handleMatch(self, m, doc) :
+        el = doc.createElement(self.tag)
+        el.appendChild(doc.createTextNode(m.group(2)))
+        return el
+
+class BacktickPattern (BasePattern):
+
+    def __init__ (self, pattern):
+        BasePattern.__init__(self, pattern)
+        self.tag = "code"
+
+    def handleMatch(self, m, doc) :
+        el = doc.createElement(self.tag)
+        text = m.group(2).strip()
+        text = text.replace("&", "&amp;")
+        el.appendChild(doc.createTextNode(text))
+        return el
+
+
+class DoubleTagPattern (SimpleTagPattern) :
+
+    def handleMatch(self, m, doc) :
+        tag1, tag2 = self.tag.split(",")
+        el1 = doc.createElement(tag1)
+        el2 = doc.createElement(tag2)
+        el1.appendChild(el2)
+        el2.appendChild(doc.createTextNode(m.group(2)))
+        return el1
+
+
+class HtmlPattern (BasePattern):
+
+    def handleMatch (self, m, doc) :
+        place_holder = self.stash.store(m.group(2))
+        return doc.createTextNode(place_holder)
+
+
+class LinkPattern (BasePattern):
+
+    def handleMatch(self, m, doc) :
+        el = doc.createElement('a')
+        el.appendChild(doc.createTextNode(m.group(2)))
+        parts = m.group(9).split()
+        # We should now have [], [href], or [href, title]
+        if parts :
+            el.setAttribute('href', parts[0])
+        else :
+            el.setAttribute('href', "")
+        if len(parts) > 1 :
+            # we also got a title
+            title = " ".join(parts[1:]).strip()
+            title = dequote(title) #.replace('"', "&quot;")
+            el.setAttribute('title', title)
+        return el
+
+
+class ImagePattern (BasePattern):
+
+    def handleMatch(self, m, doc):
+        el = doc.createElement('img')
+        src_parts = m.group(9).split()
+        el.setAttribute('src', src_parts[0])
+        if len(src_parts) > 1 :
+            el.setAttribute('title', dequote(" ".join(src_parts[1:])))
+        if ENABLE_ATTRIBUTES :
+            text = doc.createTextNode(m.group(2))
+            el.appendChild(text)
+            text.handleAttributes()
+            truealt = text.value
+            el.childNodes.remove(text)
+        else:
+            truealt = m.group(2)
+        el.setAttribute('alt', truealt)
+        return el
+
+class ReferencePattern (BasePattern):
+
+    def handleMatch(self, m, doc):
+        if m.group(9) :
+            id = m.group(9).lower()
+        else :
+            # if we got something like "[Google][]"
+            # we'll use "google" as the id
+            id = m.group(2).lower()
+        if not self.references.has_key(id) : # ignore undefined refs
+            return None
+        href, title = self.references[id]
+        text = m.group(2)
+        return self.makeTag(href, title, text, doc)
+
+    def makeTag(self, href, title, text, doc):
+        el = doc.createElement('a')
+        el.setAttribute('href', href)
+        if title :
+            el.setAttribute('title', title)
+        el.appendChild(doc.createTextNode(text))
+        return el
+
+
+class ImageReferencePattern (ReferencePattern):
+
+    def makeTag(self, href, title, text, doc):
+        el = doc.createElement('img')
+        el.setAttribute('src', href)
+        if title :
+            el.setAttribute('title', title)
+        el.setAttribute('alt', text)
+        return el
+
+
+class AutolinkPattern (BasePattern):
+
+    def handleMatch(self, m, doc):
+        el = doc.createElement('a')
+        el.setAttribute('href', m.group(2))
+        el.appendChild(doc.createTextNode(m.group(2)))
+        return el
+
+class AutomailPattern (BasePattern):
+
+    def handleMatch(self, m, doc) :
+        el = doc.createElement('a')
+        email = m.group(2)
+        if email.startswith("mailto:"):
+            email = email[len("mailto:"):]
+        for letter in email:
+            entity = doc.createEntityReference("#%d" % ord(letter))
+            el.appendChild(entity)
+        mailto = "mailto:" + email
+        mailto = "".join(['&#%d;' % ord(letter) for letter in mailto])
+        el.setAttribute('href', mailto)
+        return el
+
+ESCAPE_PATTERN          = SimpleTextPattern(ESCAPE_RE)
+NOT_STRONG_PATTERN      = SimpleTextPattern(NOT_STRONG_RE)
+
+BACKTICK_PATTERN        = BacktickPattern(BACKTICK_RE)
+DOUBLE_BACKTICK_PATTERN = BacktickPattern(DOUBLE_BACKTICK_RE)
+STRONG_PATTERN          = SimpleTagPattern(STRONG_RE, 'strong')
+STRONG_PATTERN_2        = SimpleTagPattern(STRONG_2_RE, 'strong')
+EMPHASIS_PATTERN        = SimpleTagPattern(EMPHASIS_RE, 'em')
+EMPHASIS_PATTERN_2      = SimpleTagPattern(EMPHASIS_2_RE, 'em')
+
+STRONG_EM_PATTERN       = DoubleTagPattern(STRONG_EM_RE, 'strong,em')
+STRONG_EM_PATTERN_2     = DoubleTagPattern(STRONG_EM_2_RE, 'strong,em')
+
+LINK_PATTERN            = LinkPattern(LINK_RE)
+LINK_ANGLED_PATTERN     = LinkPattern(LINK_ANGLED_RE)
+IMAGE_LINK_PATTERN      = ImagePattern(IMAGE_LINK_RE)
+IMAGE_REFERENCE_PATTERN = ImageReferencePattern(IMAGE_REFERENCE_RE)
+REFERENCE_PATTERN       = ReferencePattern(REFERENCE_RE)
+
+HTML_PATTERN            = HtmlPattern(HTML_RE)
+ENTITY_PATTERN          = HtmlPattern(ENTITY_RE)
+
+AUTOLINK_PATTERN        = AutolinkPattern(AUTOLINK_RE)
+AUTOMAIL_PATTERN        = AutomailPattern(AUTOMAIL_RE)
+
+
+"""
+======================================================================
+========================== POST-PROCESSORS ===========================
+======================================================================
+
+Markdown also allows post-processors, which are similar to
+preprocessors in that they need to implement a "run" method.  Unlike
+pre-processors, they take a NanoDom document as a parameter and work
+with that.
+#
+There are currently no standard post-processors, but the footnote
+extension below uses one.
+"""
+"""
+======================================================================
+========================== MISC AUXILIARY CLASSES ====================
+======================================================================
+"""
+
+class HtmlStash :
+    """This class is used for stashing HTML objects that we extract
+        in the beginning and replace with place-holders."""
+
+    def __init__ (self) :
+        self.html_counter = 0 # for counting inline html segments
+        self.rawHtmlBlocks=[]
+
+    def store(self, html) :
+        """Saves an HTML segment for later reinsertion.  Returns a
+           placeholder string that needs to be inserted into the
+           document.
+
+           @param html: an html segment
+           @returns : a placeholder string """
+        self.rawHtmlBlocks.append(html)
+        placeholder = HTML_PLACEHOLDER % self.html_counter
+        self.html_counter += 1
+        return placeholder
+
+
+class BlockGuru :
+
+    def _findHead(self, lines, fn, allowBlank=0) :
+
+        """Functional magic to help determine boundaries of indented
+           blocks.
+
+           @param lines: an array of strings
+           @param fn: a function that returns a substring of a string
+                      if the string matches the necessary criteria
+           @param allowBlank: specifies whether it's ok to have blank
+                      lines between matching functions
+           @returns: a list of post processes items and the unused
+                      remainder of the original list"""
+
+        items = []
+        item = -1
+
+        i = 0 # to keep track of where we are
+
+        for line in lines :
+
+            if not line.strip() and not allowBlank:
+                return items, lines[i:]
+
+            if not line.strip() and allowBlank:
+                # If we see a blank line, this _might_ be the end
+                i += 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 => this is the end
+                    break
+
+                # Check if the next non-blank line is still a part of the list
+
+                part = fn(next)
+
+                if part :
+                    items.append("")
+                    continue
+                else :
+                    break # found end of the list
+
+            part = fn(line)
+
+            if part :
+                items.append(part)
+                i += 1
+                continue
+            else :
+                return items, lines[i:]
+        else :
+            i += 1
+
+        return items, lines[i:]
+
+
+    def detabbed_fn(self, line) :
+        """ An auxiliary method to be passed to _findHead """
+        m = RE.regExp['tabbed'].match(line)
+        if m:
+            return m.group(4)
+        else :
+            return None
+
+
+    def detectTabbed(self, lines) :
+
+        return self._findHead(lines, self.detabbed_fn,
+                              allowBlank = 1)
+
+
+def print_error(string):
+    """Print an error string to stderr"""
+    sys.stderr.write(string +'\n')
+
+
+def dequote(string) :
+    """ Removes quotes from around a string """
+    if ( ( string.startswith('"') and string.endswith('"'))
+         or (string.startswith("'") and string.endswith("'")) ) :
+        return string[1:-1]
+    else :
+        return string
+
+"""
+======================================================================
+========================== CORE MARKDOWN =============================
+======================================================================
+
+This stuff is ugly, so if you are thinking of extending the syntax,
+see first if you can do it via pre-processors, post-processors,
+inline patterns or a combination of the three.
+"""
+
+class CorePatterns :
+    """This class is scheduled for removal as part of a refactoring
+        effort."""
+
+    patterns = {
+        'header':          r'(#*)([^#]*)(#*)', # # A title
+        'reference-def' :  r'(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)',
+                           # [Google]: http://www.google.com/
+        'containsline':    r'([-]*)$|^([=]*)', # -----, =====, etc.
+        'ol':              r'[ ]{0,3}[\d]*\.\s+(.*)', # 1. text
+        'ul':              r'[ ]{0,3}[*+-]\s+(.*)', # "* text"
+        'isline1':         r'(\**)', # ***
+        'isline2':         r'(\-*)', # ---
+        'isline3':         r'(\_*)', # ___
+        'tabbed':          r'((\t)|(    ))(.*)', # an indented line
+        'quoted' :         r'> ?(.*)', # a quoted block ("> ...")
+    }
+
+    def __init__ (self) :
+
+        self.regExp = {}
+        for key in self.patterns.keys() :
+            self.regExp[key] = re.compile("^%s$" % self.patterns[key],
+                                          re.DOTALL)
+
+        self.regExp['containsline'] = re.compile(r'^([-]*)$|^([=]*)$', re.M)
+
+RE = CorePatterns()
+
+
+class Markdown:
+    """ Markdown formatter class for creating an html document from
+        Markdown text """
+
+
+    def __init__(self, source=None):
+        """Creates a new Markdown instance.
+
+           @param source: The text in Markdown format. """
+        
+        if isinstance(source, unicode):
+            source = source.encode('utf8')
+        self.source = source
+        self.blockGuru = BlockGuru()
+        self.registeredExtensions = []
+        self.stripTopLevelTags = 1
+
+        self.preprocessors = [ HEADER_PREPROCESSOR,
+                               LINE_PREPROCESSOR,
+                               HTML_BLOCK_PREPROCESSOR,
+                               LINE_BREAKS_PREPROCESSOR,
+                               # A footnote preprocessor will
+                               # get inserted here
+                               REFERENCE_PREPROCESSOR ]
+
+
+        self.postprocessors = [] # a footnote postprocessor will get
+                                 # inserted later
+
+        self.prePatterns = []
+        
+
+        self.inlinePatterns = [ DOUBLE_BACKTICK_PATTERN,
+                                BACKTICK_PATTERN,
+                                ESCAPE_PATTERN,
+                                IMAGE_LINK_PATTERN,
+                                IMAGE_REFERENCE_PATTERN,
+                                REFERENCE_PATTERN,
+                                LINK_ANGLED_PATTERN,
+                                LINK_PATTERN,
+                                AUTOLINK_PATTERN,
+                                AUTOMAIL_PATTERN,
+                                HTML_PATTERN,
+                                ENTITY_PATTERN,
+                                NOT_STRONG_PATTERN,
+                                STRONG_EM_PATTERN,
+                                STRONG_EM_PATTERN_2,
+                                STRONG_PATTERN,
+                                STRONG_PATTERN_2,
+                                EMPHASIS_PATTERN,
+                                EMPHASIS_PATTERN_2
+                                # The order of the handlers matters!!!
+                                ]
+
+        self.reset()
+
+    def registerExtension(self, extension) :
+        self.registeredExtensions.append(extension)
+
+    def reset(self) :
+        """Resets all state variables so that we can start
+            with a new text."""
+        self.references={}
+        self.htmlStash = HtmlStash()
+
+        HTML_BLOCK_PREPROCESSOR.stash = self.htmlStash
+        REFERENCE_PREPROCESSOR.references = self.references
+        HTML_PATTERN.stash = self.htmlStash
+        ENTITY_PATTERN.stash = self.htmlStash
+        REFERENCE_PATTERN.references = self.references
+        IMAGE_REFERENCE_PATTERN.references = self.references
+
+        for extension in self.registeredExtensions :
+            extension.reset()
+
+
+    def _transform(self):
+        """Transforms the Markdown text into a XHTML body document
+
+           @returns: A NanoDom Document """
+
+        # Setup the document
+
+        self.doc = Document()
+        self.top_element = self.doc.createElement("span")
+        self.top_element.appendChild(self.doc.createTextNode('\n'))
+        self.top_element.setAttribute('class', 'markdown')
+        self.doc.appendChild(self.top_element)
+
+        # Fixup the source text
+        text = self.source.strip()
+        text = text.replace("\r\n", "\n").replace("\r", "\n")
+        text += "\n\n"
+        text = text.expandtabs(TAB_LENGTH)
+
+        # Split into lines and run the preprocessors that will work with
+        # self.lines
+
+        self.lines = text.split("\n")
+
+        # Run the pre-processors on the lines
+        for prep in self.preprocessors :
+            self.lines = prep.run(self.lines)
+
+        # Create a NanoDom tree from the lines and attach it to Document
+
+
+        buffer = []
+        for line in self.lines :
+            if line.startswith("#") :
+                self._processSection(self.top_element, buffer)
+                buffer = [line]
+            else :
+                buffer.append(line)
+        self._processSection(self.top_element, buffer)
+        
+        #self._processSection(self.top_element, self.lines)
+
+        # Not sure why I put this in but let's leave it for now.
+        self.top_element.appendChild(self.doc.createTextNode('\n'))
+
+        # Run the post-processors
+        for postprocessor in self.postprocessors :
+            postprocessor.run(self.doc)
+
+        return self.doc
+
+
+    def _processSection(self, parent_elem, lines,
+                        inList = 0, looseList = 0) :
+
+        """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.
+
+           @param parent_elem: A NanoDom element to which the content
+                               will be added
+           @param lines: a list of lines
+           @param inList: a level
+           @returns: None"""
+
+        if not lines :
+            return
+
+        # Check if this section starts with a list, a blockquote or
+        # a code block
+
+        processFn = { 'ul' :     self._processUList,
+                      'ol' :     self._processOList,
+                      'quoted' : self._processQuote,
+                      'tabbed' : self._processCodeBlock }
+
+        for regexp in ['ul', 'ol', 'quoted', 'tabbed'] :
+            m = RE.regExp[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, theRest = self._linesUntil(lines, (lambda line:
+                             RE.regExp['ul'].match(line)
+                             or RE.regExp['ol'].match(line)
+                                              or not line.strip()))
+
+            self._processSection(parent_elem, start,
+                                 inList - 1, looseList = looseList)
+            self._processSection(parent_elem, theRest,
+                                 inList - 1, looseList = looseList)
+
+
+        else : # Ok, so it's just a simple block
+
+            paragraph, theRest = self._linesUntil(lines, lambda line:
+                                                 not line.strip())
+
+            if len(paragraph) and paragraph[0].startswith('#') :
+                m = RE.regExp['header'].match(paragraph[0])
+                if m :
+                    level = len(m.group(1))
+                    h = self.doc.createElement("h%d" % level)
+                    parent_elem.appendChild(h)
+                    for item in self._handleInlineWrapper2(m.group(2).strip()) :
+                        h.appendChild(item)
+                else :
+                    message(CRITICAL, "We've got a problem header!")
+
+            elif paragraph :
+
+                list = self._handleInlineWrapper2("\n".join(paragraph))
+
+                if ( parent_elem.nodeName == 'li'
+                     and not (looseList or parent_elem.childNodes)):
+
+                    #and not parent_elem.childNodes) :
+                    # If this is the first paragraph inside "li", don't
+                    # put <p> around it - append the paragraph bits directly
+                    # onto parent_elem
+                    el = parent_elem
+                else :
+                    # Otherwise make a "p" element
+                    el = self.doc.createElement("p")
+                    parent_elem.appendChild(el)
+
+                for item in list :
+                    el.appendChild(item)
+
+            if theRest :
+                theRest = theRest[1:]  # skip the first (blank) line
+
+            self._processSection(parent_elem, theRest, inList)
+
+
+
+    def _processUList(self, parent_elem, lines, inList) :
+        self._processList(parent_elem, lines, inList,
+                         listexpr='ul', tag = 'ul')
+
+    def _processOList(self, parent_elem, lines, inList) :
+        self._processList(parent_elem, lines, inList,
+                         listexpr='ol', tag = 'ol')
+
+
+    def _processList(self, parent_elem, 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.
+
+           @param parent_elem: A dom element to which the content will be added
+           @param lines: a list of lines
+           @param inList: a level
+           @returns: None"""
+
+        ul = self.doc.createElement(tag)  # ul might actually be '<ol>'
+        parent_elem.appendChild(ul)
+
+        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 ( RE.regExp['ul'].match(next) or
+                     RE.regExp['ol'].match(next) or 
+                     RE.regExp['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 = RE.regExp[expr].match(line)
+                if m :
+                    if expr in ['ul', 'ol'] :  # We are looking at a new item
+                        if m.group(1) :
+                            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 dom elements
+        for item in items :
+            li = self.doc.createElement("li")
+            ul.appendChild(li)
+
+            self._processSection(li, item, inList + 1, looseList = looseList)
+
+        # Process the remaining part of the section
+
+        self._processSection(parent_elem, 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, parent_elem, 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.
+
+           @param parent_elem: DOM element to which the content will be added
+           @param lines: a list of lines
+           @param inList: a level
+           @returns: None """
+
+        dequoted = []
+        i = 0
+        for line in lines :
+            m = RE.regExp['quoted'].match(line)
+            if m :
+                dequoted.append(m.group(1))
+                i += 1
+            else :
+                break
+        else :
+            i += 1
+
+        blockquote = self.doc.createElement('blockquote')
+        parent_elem.appendChild(blockquote)
+
+        self._processSection(blockquote, dequoted, inList)
+        self._processSection(parent_elem, lines[i:], inList)
+
+
+
+
+    def _processCodeBlock(self, parent_elem, lines, inList) :
+        """Given a list of document lines starting with a code block
+           finds the end of the block, puts it into the dom verbatim
+           wrapped in ("<pre><code>") and recursively processes the
+           the remainder of the text file.
+
+           @param parent_elem: DOM element to which the content will be added
+           @param lines: a list of lines
+           @param inList: a level
+           @returns: None"""
+
+        detabbed, theRest = self.blockGuru.detectTabbed(lines)
+
+        pre = self.doc.createElement('pre')
+        code = self.doc.createElement('code')
+        parent_elem.appendChild(pre)
+        pre.appendChild(code)
+        text = "\n".join(detabbed).rstrip()+"\n"
+        text = text.replace("&", "&amp;")
+        code.appendChild(self.doc.createTextNode(text))
+        self._processSection(parent_elem, theRest, inList)
+
+
+    def _handleInlineWrapper2 (self, line) :
+
+
+        parts = [line]
+
+        #if not(line):
+        #    return [self.doc.createTextNode(' ')]
+
+        for pattern in self.inlinePatterns :
+
+            #print
+            #print self.inlinePatterns.index(pattern)
+
+            i = 0
+
+            #print parts
+            while i < len(parts) :
+                
+                x = parts[i]
+                #print i
+                if isinstance(x, (str, unicode)) :
+                    result = self._applyPattern(x, pattern)
+                    #print result
+                    #print result
+                    #print parts, i
+                    if result :
+                        i -= 1
+                        parts.remove(x)
+                        for y in result :
+                            parts.insert(i+1,y)
+                
+                i += 1
+
+        for i in range(len(parts)) :
+            x = parts[i]
+            if isinstance(x, (str, unicode)) :
+                parts[i] = self.doc.createTextNode(x)
+
+        return parts
+        
+
+
+    def _handleInlineWrapper (self, line) :
+
+        # A wrapper around _handleInline to avoid recursion
+
+        parts = [line]
+
+        i = 0
+        
+        while i < len(parts) :
+            x = parts[i]
+            if isinstance(x, (str, unicode)) :
+                parts.remove(x)
+                result = self._handleInline(x)
+                for y in result :
+                    parts.insert(i,y)
+            else :
+                i += 1
+
+        return parts
+
+    def _handleInline(self,  line):
+        """Transform a Markdown line with inline elements to an XHTML
+        fragment.
+
+        This function uses auxiliary objects called inline patterns.
+        See notes on inline patterns above.
+
+        @param item: A block of Markdown text
+        @return: A list of NanoDom nodes """
+
+        if not(line):
+            return [self.doc.createTextNode(' ')]
+
+        for pattern in self.inlinePatterns :
+            list = self._applyPattern( line, pattern)
+            if list: return list
+
+        return [self.doc.createTextNode(line)]
+
+    def _applyPattern(self, line, pattern) :
+        """ Given a pattern name, this function checks if the line
+        fits the pattern, creates the necessary elements, and returns
+        back a list consisting of NanoDom elements and/or strings.
+        
+        @param line: the text to be processed
+        @param pattern: the pattern to be checked
+
+        @returns: the appropriate newly created NanoDom element if the
+                  pattern matches, None otherwise.
+        """
+
+        # match the line to pattern's pre-compiled reg exp.
+        # if no match, move on.
+
+        m = pattern.getCompiledRegExp().match(line)
+        if not m :
+            return None
+
+        # if we got a match let the pattern make us a NanoDom node
+        # if it doesn't, move on
+        node = pattern.handleMatch(m, self.doc)
+
+        if node :
+            # Those are in the reverse order!
+            return ( m.groups()[-1], # the string to the left
+                     node,           # the new node
+                     m.group(1))     # the string to the right of the match
+
+        else :
+            return None
+
+    def __str__(self):
+        """Return the document in XHTML format.
+
+        @returns: A serialized XHTML body."""
+        #try :
+        doc = self._transform()
+        xml = doc.toxml()
+        #finally:
+        #    doc.unlink()
+
+        # Let's stick in all the raw html pieces
+
+        for i in range(self.htmlStash.html_counter) :
+            xml = xml.replace("<p>%s\n</p>" % (HTML_PLACEHOLDER % i),
+                              self.htmlStash.rawHtmlBlocks[i] + "\n")
+            xml = xml.replace(HTML_PLACEHOLDER % i,
+                              self.htmlStash.rawHtmlBlocks[i])
+
+        xml = xml.replace(FN_BACKLINK_TEXT, "&#8617;")
+
+        # And return everything but the top level tag
+
+        if self.stripTopLevelTags :
+            xml = xml.strip()[23:-7]
+
+        if isinstance(xml, unicode) :
+            xml = xml.encode("utf8")
+
+        return xml
+
+
+    toString = __str__
+
+
+"""
+========================= 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.
+"""
+
+class FootnoteExtension :
+
+    DEF_RE = re.compile(r'(\ ?\ ?\ ?)\[\^([^\]]*)\]:\s*(.*)')
+    SHORT_USE_RE = re.compile(r'\[\^([^\]]*)\]', re.M) # [^a]
+
+    FN_PLACE_MARKER = "///Footnotes Go Here///"
+
+    def __init__ (self) :
+        self.reset()
+
+    def extendMarkdown(self, md) :
+
+        self.md = md
+
+        # Stateless extensions do not need to be registered
+        md.registerExtension(self)
+
+        # Insert a preprocessor before ReferencePreprocessor
+        index = md.preprocessors.index(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(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)
+
+
+    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, doc) :
+        def findFootnotePlaceholderFn(node=None, indent=0):
+            if node.type == 'text':
+                if node.value.find(self.FN_PLACE_MARKER) > -1 :
+                    return True
+
+        fn_div_list = doc.find(findFootnotePlaceholderFn)
+        if fn_div_list :
+            return fn_div_list[0]
+
+
+    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, doc) :
+        """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 = doc.createElement("div")
+        div.setAttribute('class', 'footnote')
+        hr = doc.createElement("hr")
+        div.appendChild(hr)
+        ol = doc.createElement("ol")
+        div.appendChild(ol)
+
+        footnotes = [(self.used_footnotes[id], id)
+                     for id in self.footnotes.keys()]
+        footnotes.sort()
+
+        for i, id in footnotes :
+            li = doc.createElement('li')
+            li.setAttribute('id', self.makeFootnoteId(i))
+
+            self.md._processSection(li, self.footnotes[id].split("\n"))
+
+            #li.appendChild(doc.createTextNode(self.footnotes[id]))
+
+            backlink = doc.createElement('a')
+            backlink.setAttribute('href', '#' + self.makeFootnoteRefId(i))
+            backlink.setAttribute('class', 'footnoteBackLink')
+            backlink.setAttribute('title',
+                                  'Jump back to footnote %d in the text' % 1)
+            backlink.appendChild(doc.createTextNode(FN_BACKLINK_TEXT))
+
+            if li.childNodes :
+                node = li.childNodes[-1]
+                if node.type == "text" :
+                    node = li
+                node.appendChild(backlink)
+
+            ol.appendChild(li)
+
+        return div
+
+
+class FootnotePreprocessor :
+
+    def __init__ (self, footnotes) :
+        self.footnotes = footnotes
+
+    def run(self, lines) :
+
+        self.blockGuru = 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 (BasePattern) :
+
+    def __init__ (self, pattern, footnotes) :
+
+        BasePattern.__init__(self, pattern)
+        self.footnotes = footnotes
+
+    def handleMatch(self, m, doc) :
+        sup = doc.createElement('sup')
+        a = doc.createElement('a')
+        sup.appendChild(a)
+        id = m.group(2)
+        num = self.footnotes.used_footnotes[id]
+        sup.setAttribute('id', self.footnotes.makeFootnoteRefId(num))
+        a.setAttribute('href', '#' + self.footnotes.makeFootnoteId(num))
+        a.appendChild(doc.createTextNode(str(num)))
+        return sup
+
+class FootnotePostprocessor :
+
+    def __init__ (self, footnotes) :
+        self.footnotes = footnotes
+
+    def run(self, doc) :
+        footnotesDiv = self.footnotes.makeFootnotesDiv(doc)
+        if footnotesDiv :
+            fnPlaceholder = self.extension.findFootnotesPlaceholder(doc)
+            if fnPlaceholder :
+                fnPlaceholder.parent.replaceChild(fnPlaceholder, footnotesDiv)
+            else :
+                doc.documentElement.appendChild(footnotesDiv)
+
+# ====================================================================
+
+def markdown(text) :
+    message(VERBOSE, "in markdown.py, received text:\n%s" % text)
+    return Markdown(text).toString()
+
+def markdownWithFootnotes(text):
+    message(VERBOSE, "Running markdown with footnotes, "
+            + "received text:\n%s" % text)
+    md = Markdown()
+    footnoteExtension = FootnoteExtension()
+    footnoteExtension.extendMarkdown(md)
+    md.source = text
+
+    return str(md)
+
+def test_markdown(args):
+    """test markdown at the command line.
+        in each test, arg 0 is the module name"""
+    print "\nTEST 1: no arguments on command line"
+    cmd_line(["markdown.py"])
+    print "\nTEST 2a: 1 argument on command line: a good option"
+    cmd_line(["markdown.py","-footnotes"])
+    print "\nTEST 2b: 1 argument on command line: a bad option"
+    cmd_line(["markdown.py","-foodnotes"])
+    print "\nTEST 3: 1 argument on command line: non-existent input file"
+    cmd_line(["markdown.py","junk.txt"])
+    print "\nTEST 4: 1 argument on command line: existing input file"
+    lines = """
+Markdown text with[^1]:
+
+2. **bold text**,
+3. *italic text*.
+
+Then more:
+
+    beginning of code block;
+    another line of code block.
+    
+    a second paragraph of code block.
+
+more text to end our file.
+
+[^1]: "italic" means emphasis.
+"""
+    fid = "markdown-test.txt"
+    f1 = open(fid, 'w+')
+    f1.write(lines)
+    f1.close()
+    cmd_line(["markdown.py",fid])
+    print "\nTEST 5: 2 arguments on command line: nofootnotes and input file"
+    cmd_line(["markdown.py","-nofootnotes", fid])
+    print "\nTEST 6: 2 arguments on command line: footnotes and input file"
+    cmd_line(["markdown.py","-footnotes", fid])
+    print "\nTEST 7: 3 arguments on command line: nofootnotes,inputfile, outputfile"
+    fidout = "markdown-test.html"
+    cmd_line(["markdown.py","-nofootnotes", fid, fidout])
+
+
+def get_vars(args):
+    """process the command-line args received; return usable variables"""
+    #firstly get the variables
+
+    message(VERBOSE, "in get_vars(), args: %s" % args) 
+
+    if len(args) <= 1:
+        option, inFile, outFile = (None, None, None)
+    elif len(args) >= 4:
+        option, inFile, outFile = args[1:4]
+    elif len(args) == 3:
+        temp1, temp2 = args[1:3]
+        if temp1[0] == '-':
+            #then we have an option and inFile
+            option, inFile, outFile = temp1, temp2, None
+        else:
+            #we have no option, so we must have inFile and outFile
+            option, inFile, outFile = None, temp1, temp2
+    else:
+        #len(args) = 2
+        #we have only one usable arg: might be an option or a file
+        temp1 = args[1]
+        
+        message(VERBOSE, "our single arg is: %s" % str(temp1))
+
+        if temp1[0] == '-':
+            #then we have an option 
+            option, inFile, outFile = temp1, None, None
+        else:
+            #we have no option, so we must have inFile
+            option, inFile, outFile = None, temp1, None
+    
+    message(VERBOSE,
+            "prior to validation, option: %s, inFile: %s, outFile: %s" %
+            (str(option), str(inFile), str(outFile),))
+    
+    return option, inFile, outFile
+
+
+USAGE = """
+\nUsing markdown.py:
+
+    python markdown.py [option] input_file_with_markdown.txt [output_file.html]
+
+Options:
+
+    -footnotes or -fn   : generate markdown with footnotes
+    -test or -t         : run a self-test
+    -help or -h         : print this message
+
+"""
+    
+VALID_OPTIONS = ['footnotes','nofootnotes', 'fn', 'test', 't', 'f',
+                 'help', 'h']
+
+EXPANDED_OPTIONS =  { "fn" : "footnotes",
+                      "t"  : "test",
+                      "h"  : "help" }
+
+
+def validate_option(option) :
+
+    """ Check if the option makes sense and print an appropriate message
+        if it isn't.
+        
+        @return: valid option string or None
+    """
+
+    #now validate the variables
+    if (option is not None):
+        if (len(option) > 1 and option[1:] in VALID_OPTIONS) :
+            option = option[1:]
+
+            if option in EXPANDED_OPTIONS.keys() :
+                option = EXPANDED_OPTIONS[option]
+            return option
+        else:
+            message(CRITICAL,
+                    "\nSorry, I don't understand option %s" % option)
+            message(CRITICAL, USAGE)
+            return None
+
+
+def validate_input_file(inFile) :        
+    """ Check if the input file is specified and exists.
+
+        @return: valid input file path or None
+    """
+
+    if not inFile :
+        message(CRITICAL,
+                "\nI need an input filename.\n")
+        message(CRITICAL, USAGE)
+        return None
+    
+        
+    if os.access(inFile, os.R_OK):
+        return inFile
+    else :
+        message(CRITICAL, "Sorry, I can't find input file %s" % str(inFile))
+        return None
+
+    
+            
+
+def cmd_line(args):
+
+    message(VERBOSE, "in cmd_line with args: %s" % args)
+
+    option, inFile, outFile = get_vars(args)
+
+    if option :
+        option = validate_option(option)
+        if not option : return
+
+    if option == "help" :
+        message(CRITICAL, USAGE)
+        return
+    elif option == "test" :
+        test_markdown(None)
+        return
+
+    inFile = validate_input_file(inFile)
+    if not inFile :
+        return
+    else :
+        input = file(inFile).read()
+
+    message(VERBOSE, "Validated command line parameters:" +             
+             "\n\toption: %s, \n\tinFile: %s, \n\toutFile: %s" % (
+             str(option), str(inFile), str(outFile),))
+
+    if option == "footnotes" :
+        md_function = markdownWithFootnotes
+    else :
+        md_function = markdown
+
+    if outFile is None:
+        print md_function(input)
+    else:
+        output = md_function(input)
+        f1 = open(outFile, "w+")
+        f1.write(output)
+        f1.close()
+        
+        if os.access(outFile, os.F_OK):
+            message(INFO, "Successfully wrote %s" % outFile)
+        else:
+            message(INFO, "Failed to write %s" % outFile)
+
+
+if __name__ == '__main__':
+    """ Run Markdown from the command line.
+        Set debug = 3 at top of file to get diagnostic output"""
+    args = sys.argv
+        
+    #set testing=1 to test the command-line response of markdown.py
+    testing = 0
+    if testing:
+        test_markdown(args)
+    else:
+        import time
+        t0 = time.time()
+        #for x in range(10) :
+        cmd_line(args)
+        #import profile
+        #profile.run('cmd_line(args)', 'profile')
+        t1 = time.time()
+        #print "Time: %f - %f = %f" % (t1, t0, t1-t0)
+
+"""
+CHANGELOG
+=========
+
+May 15, 2006: A bug with lists, recursion on block-level elements,
+run-in headers, spaces before headers, unicode input (thanks to Aaron
+Swartz). Sourceforge tracker #s: 1489313, 1489312, 1489311, 1488370,
+1485178, 1485176. (v. 1.5)
+
+Mar. 24, 2006: Switched to a not-so-recursive algorithm with
+_handleInline.  (Version 1.4)
+
+Mar. 15, 2006: Replaced some instance variables with class variables
+(a patch from Stelios Xanthakis).  Chris Clark's new regexps that do
+not trigger midword underlining.
+
+Feb. 28, 2006: Clean-up and command-line handling by Stewart
+Midwinter. (Version 1.3)
+
+Feb. 24, 2006: Fixed a bug with the last line of the list appearing
+again as a separate paragraph.  Incorporated Chris Clark's "mailto"
+patch.  Added support for <br /> at the end of lines ending in two or
+more spaces.  Fixed a crashing bug when using ImageReferencePattern.
+Added several utility methods to Nanodom.  (Version 1.2)
+
+Jan. 31, 2006: Added "hr" and "hr/" to BLOCK_LEVEL_ELEMENTS and
+changed <hr/> to <hr />.  (Thanks to Sergej Chodarev.)
+
+Nov. 26, 2005: Fixed a bug with certain tabbed lines inside lists
+getting wrapped in <pre><code>.  (v. 1.1)
+
+Nov. 19, 2005: Made "<!...", "<?...", etc. behave like block-level
+HTML tags.
+
+Nov. 14, 2005: Added entity code and email autolink fix by Tiago
+Cogumbreiro.  Fixed some small issues with backticks to get 100%
+compliance with John's test suite.  (v. 1.0)
+
+Nov. 7, 2005: Added an unlink method for documents to aid with memory
+collection (per Doug Sauder's suggestion).
+
+Oct. 29, 2005: Restricted a set of html tags that get treated as
+block-level elements.
+
+Sept. 18, 2005: Refactored the whole script to make it easier to
+customize it and made footnote functionality into an extension.
+(v. 0.9)
+
+Sept. 5, 2005: Fixed a bug with multi-paragraph footnotes.  Added
+attribute support.
+
+Sept. 1, 2005: Changed the way headers are handled to allow inline
+syntax in headers (e.g. links) and got the lists to use p-tags
+correctly (v. 0.8)
+
+Aug. 29, 2005: Added flexible tabs, fixed a few small issues, added
+basic support for footnotes.  Got rid of xml.dom.minidom and added
+pretty-printing. (v. 0.7)
+
+Aug. 13, 2005: Fixed a number of small bugs in order to conform to the
+test suite.  (v. 0.6)
+
+Aug. 11, 2005: Added support for inline html and entities, inline
+images, autolinks, underscore emphasis. Cleaned up and refactored the
+code, added some more comments.
+
+Feb. 19, 2005: Rewrote the handling of high-level elements to allow
+multi-line list items and all sorts of nesting.
+
+Feb. 3, 2005: Reference-style links, single-line lists, backticks,
+escape, emphasis in the beginning of the paragraph.
+
+Nov. 2004: Added links, blockquotes, html blocks to Manfred
+Stienstra's code
+
+Apr. 2004: Manfred's version at http://www.dwerg.net/projects/markdown/
+
+"""
+
+
+
+
+
+

webhelpers/rails/date.py

 """Date/Time Helpers"""
 # Last synced with Rails copy at Revision 4674 on Aug 19th, 2006.
+# Note that the select_ tags are purposely not ported as they're very totally useless
+# and inefficient beyond comprehension.
 
 from datetime import datetime
 import time
 
+DEFAULT_PREFIX = 'date'
+
 def distance_of_time_in_words(from_time, to_time=0, include_seconds=False):
     """
     Reports the approximate distance in time between two datetime objects or integers. 
     """
     return distance_of_time_in_words(from_time, datetime.now(), include_seconds)
 
-def select_date(date=datetime.now(), **options):
-    """
-    Returns a set of html select-tags (one for year, month, day) pre-selected with the ``date``.
-    """
-    return select_year(date, options) + select_month(date, options) + select_day(date, options)
-
-def select_datetime(datetime=datetime.now(), **options):
-    """
-    Returns a set of html select-tags (one for year, month, day, hour and minutes) pre-selected with the ``date``.
-    """
-    return select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) + \
-        select_hour(datetime, options) + select_minute(datetime, options)
-
-def select_time(datetime=datetime.now(), **options):
-    """Returns a set of html select-tags (one for hour and minute)
-    
-    ``include_seconds``
-        Boolean to indicate if an additional select field for seconds should
-        also be included.
-    
-    """
-    seconds = ''
-    if options.get('include_seconds'):
-        del options['include_seconds']
-        seconds = select_second(datetime, options)
-    return select_hour(datetime, options) + select_minute(datetime, options) + seconds
-
-def select_second(datetime, **options):
-    """Returns a select tag with options for each of the seconds 0 through 59 with the
-    current second selected.
-    
-    The 
-    """
-    pass
-
 __all__ = ['distance_of_time_in_words', 'time_ago_in_words']

webhelpers/rails/text.py

 """
 Text Helpers
 """
+# Last synced with Rails copy at Revision 4595 on Aug 19th, 2006.
+# Purposely left out sanitize, should be included at some point likely using
+# BeautifulSoup.
+
 from routes import request_config
-from tags import content_tag, tag_options
+from webhelpers.rails.tags import content_tag, tag_options
+import webhelpers.textile as textile
+import webhelpers.markdown as markdown
 import itertools, re
 
 AUTO_LINK_RE = re.compile("""(<\w+.*?>|[^=!:'"\/]|^)((?:http[s]?:\/\/)|(?:www\.))(([\w]+:?[=?&\/.-]?)*\w+[\/]?(?:\#\w*)?)([\.,"'?!;:]|\s|<|$)""")
         >>> strip_links("<a href="something">else</a>")
         "else"
     """
-    return re.sub(r'<a.*?>(.*)</a>', r'\1', text, re.M)
+    return re.sub(r'<a\b.*?>(.*?)<\/a>', r'\1', text, re.M)
+
+def textilize(text, sanitize=False):
+    """Format the text with Textile formatting
+    
+    This function uses the `PyTextile library <http://dealmeida.net/>`_ which is included with WebHelpers.
+    
+    Additionally, the output can be sanitized which will fix tags like <img />,
+    <br /> and <hr /> for proper XHTML output.
+    
+    """
+    texer = textile.Textiler(text)
+    return texer.process(sanitize=sanitize)
+
+def markdown(text):
+    """Format the text with MarkDown formatting
+    
+    This function uses the `Python MarkDown library <http://www.freewisdom.org/projects/python-markdown/>`_
+    which is included with WebHelpers.
+    
+    """
+    return markdown.markdown(text)
 
 __all__ = ['cycle', 'reset_cycle', 'truncate', 'highlight', 'excerpt', 'word_wrap', 'simple_format',
-           'auto_link', 'strip_links']
+           'auto_link', 'strip_links', 'textilize', 'markdown']

webhelpers/textile.py

+#!/usr/bin/env python
+# _*_ coding: latin1 _*_
+
+"""This is Textile
+A Humane Web Text Generator
+
+TODO:
+* Make it work with Python 2.1.
+* Make it work with Python 1.5.2? Or that's too optimistic?
+
+---
+To get an overview of all PyTextile's features, simply
+type 'tell me about textile.' in a single line.
+"""
+
+__authors__ = ["Roberto A. F. De Almeida (roberto@dealmeida.net)",
+               "Mark Pilgrim (f8dy@diveintomark.org)"]
+__version__ = "2.0.10"
+__date__ = "2004/10/06"
+__copyright__ = """
+Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/
+Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/
+All rights reserved.
+
+Original PHP version:
+Version 1.0
+21 Feb, 2003
+
+Copyright (c) 2003, Dean Allen, www.textism.com
+All rights reserved.
+
+Parts of the documentation and some of the regular expressions are (c) Brad
+Choate, http://bradchoate.com/. Thanks, Brad!
+"""
+__license__ = """
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, 
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name Textile nor the names of its contributors may be used to
+  endorse or promote products derived from this software without specific
+  prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+"""
+__history__ = """
+1.0 - 2003/03/19 - MAP - initial release
+1.01 - 2003/03/19 - MAP - don't strip whitespace within <pre> tags;
+  map high-bit ASCII to HTML numeric entities
+1.02 - 2003/03/19 - MAP - changed hyperlink qtag expression to only
+  match valid URL characters (per RFC 2396); fixed preg_replace to
+  not match across line breaks (solves lots of problems with
+  mistakenly matching overlapping inline markup); fixed whitespace
+  stripping to only strip whitespace from beginning and end of lines,
+  not immediately before and after HTML tags.
+1.03 - 2003/03/20 - MAP - changed hyperlink qtag again to more
+  closely match original Textile (fixes problems with links
+  immediately followed by punctuation -- somewhere Dean is
+  grinning right now); handle curly apostrophe with "ve"
+  contraction; clean up empty titles at end.
+1.04 - 2003/03/23 - MAP - lstrip input to deal with extra spaces at
+  beginning of first line; tweaked list loop to handle consecutive lists
+1.1 - 2003/06/06 - MAP - created initial test suite for links and images,
+  and fixed a bunch of related bugs to pass them
+1.11 - 2003/07/20 - CL - don't demoronise unicode strings; handle
+  "they're" properly
+1.12 - 2003/07/23 - GW - print debug messages to stderr; handle bq(cite).
+1.13 - 2003/07/23 - MAP - wrap bq. text in <p>...</p>
+2 - 2004/03/26 - RAFA - rewritten from (almost) scratch to include
+  all features from Textile 2 and a little bit more.
+2.0.1 - 2004/04/02 - RAFA - Fixed validating function that uses uTidyLib.
+2.0.2 - 2004/04/02 - RAFA - Fixed problem with caps letters in URLs.
+2.0.3 - 2004/04/19 - RAFA - Multiple classes are allowed, thanks to Dave
+  Anderson. The "lang" attribute is now removed from <code>, to be valid
+  XHTML. Fixed <span class="caps">UCAS</span> problem.
+2.0.4 - 2004/05/20 - RAFA, CLB - Added inline formatting to table cells.
+  Curt Bergmann fixed a bug with the colspan formatting. Added Amazon
+  Associated id.
+2.0.5 - 2004/06/01 - CL - Applied patch from Chris Lawrence to (1) fix
+  that Amazon associates ID was being added to all search URIs, (2)
+  customize the Amazon site used with the AMAZON variable, and (3) added
+  an "isbn" URI type that links directly to an Amazon product by ISBN or
+  Amazon ASIN.
+2.0.6 - 2004/06/02 - RAFA - Fixed CAPS problem, again. I hope this is
+  the last time.
+2.0.7 - 2004/06/04 - RAFA, MW - Fixed bullet macro, thanks to Adam
+  Messinger. Added patch from Michal Wallace changing {}.pop() for
+  compatibility with Python 2.2.x.
+2.0.8 - 2004/06/25 - RAFA - Strip tags when adding the content from a
+  footnote to the reference link. Escaped '<' and '>' in the self-
+  generated documentation.
+2.0.9 - 2004/10/04 - RAFA - In images, if ALT is not defined, add an
+  empty attribute. Added "LaTeX" style open/close quotes. Fixed a bug 
+  where the acronym definition was being formatted with inline rules. 
+  Handle "broken" lines correctly, removing the <br /> from inside
+  split HTML tags.
+2.0.10 - 2004/10/06 - RAFA, LO - Escape all non-escaped ampersands.
+  Applied "trivial patch" from Ludvig Omholt to remove newline right
+  after the <pre> tag.
+"""
+
+# Set your encoding here.
+ENCODING = 'latin-1'
+
+# Output? Non-ASCII characters will be automatically
+# converted to XML entities if you choose ASCII.
+OUTPUT = 'ascii'
+
+# PyTextile can optionally validate the generated
+# XHTML code. We can use either mxTidy or uTidyLib.
+# You can change the default behaviour here.
+VALIDATE = 0
+
+# If you want h1. to be translated to something other
+# than <h1>, change this offset. You can also pass it
+# as an argument to textile().
+HEAD_OFFSET = 0
+
+# If you want to use itex2mml, specify the full path
+# to the binary here. You can download it from here:
+# http://golem.ph.utexas.edu/~distler/blog/files/itexToMML.tar.gz
+itex2mml = None
+#itex2mml = '/usr/local/bin/itex2MML'
+#itex2mml = '/usr/people/almeida/bin/itex2MML'
+
+# PyTextile can optionally sanitize the generated XHTML,
+# which is good for weblog comments or if you don't trust
+# yourself.
+SANITIZE = 0
+
+# Turn debug on?
+DEBUGLEVEL = 0
+
+# Amazon associate for links: "keywords":amazon
+# If you don't have one, please consider leaving mine here as
+# a small compensation for writing PyTextile. It's commented
+# off as default.
+#amazon_associate_id = 'bomtempo-21'
+amazon_associate_id = None 
+
+#AMAZON = 'www.amazon.co.uk'
+AMAZON = 'www.amazon.com'
+
+import re
+import sys
+import os
+import sgmllib
+import unicodedata
+
+
+def _in_tag(text, tag):
+    """Extracts text from inside a tag.
+
+    This function extracts the text from inside a given tag.
+    It's useful to get the text between <body></body> or
+    <pre></pre> when using the validators or the colorizer.
+    """
+    if text.count('<%s' % tag):
+        text = text.split('<%s' % tag, 1)[1]
+        if text.count('>'):
+            text = text.split('>', 1)[1]
+    if text.count('</%s' % tag):
+        text = text.split('</%s' % tag, 1)[0]
+
+    text = text.strip().replace('\r\n', '\n')
+
+    return text
+
+
+# If you want PyTextile to automatically colorize
+# your Python code, you need the htmlizer module
+# from Twisted. (You can just grab this file from
+# the distribution, it has no other dependencies.)
+try:
+    #from twisted.python import htmlizer
+    import htmlizer
+    from StringIO import StringIO
+
+    def _color(code):
+        """Colorizer Python code.
+
+        This function wraps a text string in a StringIO,
+        and passes it to the htmlizer function from
+        Twisted.
+        """
+        # Fix line continuations.
+        code = preg_replace(r' \\\n', ' \\\\\n', code)
+        
+        code_in  = StringIO(code)
+        code_out = StringIO()
+
+        htmlizer.filter(code_in, code_out)
+
+        # Remove <pre></pre> from input.
+        code = _in_tag(code_out.getvalue(), 'pre')
+
+        # Fix newlines.
+        code = code.replace('<span class="py-src-newline">\n</span>', '<span class="py-src-newline"></span>\n')
+
+        return code
+
+except ImportError:
+    htmlizer = None
+
+
+# PyTextile can optionally validate the generated
+# XHTML code using either mxTidy or uTidyLib.
+try:
+    # This is mxTidy.
+    from mx.Tidy import Tidy
+    
+    def _tidy1(text):
+        """mxTidy's XHTML validator.
+
+        This function is a wrapper to mxTidy's validator.
+        """
+        nerrors, nwarnings, text, errortext = Tidy.tidy(text, output_xhtml=1, numeric_entities=1, wrap=0)
+        return _in_tag(text, 'body')
+
+    _tidy = _tidy1
+
+except ImportError:
+    try:
+        # This is uTidyLib.
+        import tidy
+
+        def _tidy2(text):
+            """uTidyLib's XHTML validator.
+
+            This function is a wrapper to uTidyLib's validator.
+            """
+            text = tidy.parseString(text,  output_xhtml=1, add_xml_decl=0, indent=0, tidy_mark=0)
+            return _in_tag(str(text), 'body')
+
+        _tidy = _tidy2
+
+    except ImportError:
+        _tidy = None
+    
+
+# This is good for debugging.
+def _debug(s, level=1):
+    """Outputs debug information to sys.stderr.
+
+    This function outputs debug information if DEBUGLEVEL is
+    higher than a given treshold.
+    """
+    if DEBUGLEVEL >= level: print >> sys.stderr, s
+
+
+#############################
+# Useful regular expressions.
+parameters = {
+    # Horizontal alignment.
+    'align':    r'''(?:(?:<>|[<>=])                 # Either '<>', '<', '>' or '='
+                    (?![^\s]*(?:<>|[<>=])))         # Look-ahead to ensure it happens once
+                 ''',
+
+    # Horizontal padding.
+    'padding':  r'''(?:[\(\)]+)                     # Any number of '(' and/or ')'
+                 ''',