From f9bc5d2b86a94fb98e791966bb26e1abd5ef0ed2 Mon Sep 17 00:00:00 2001 From: Waylan Limberg Date: Mon, 11 Aug 2008 20:12:28 -0400 Subject: renamed extension module and set import to extension module first, then mdx_filename --- MANIFEST | 20 +-- markdown.py | 14 +- markdown_extensions/__init__.py | 0 markdown_extensions/codehilite.py | 218 +++++++++++++++++++++++++++++ markdown_extensions/extra.py | 52 +++++++ markdown_extensions/fenced_code.py | 105 ++++++++++++++ markdown_extensions/footnotes.py | 276 +++++++++++++++++++++++++++++++++++++ markdown_extensions/headerid.py | 202 +++++++++++++++++++++++++++ markdown_extensions/imagelinks.py | 135 ++++++++++++++++++ markdown_extensions/meta.py | 81 +++++++++++ markdown_extensions/rss.py | 118 ++++++++++++++++ markdown_extensions/tables.py | 72 ++++++++++ markdown_extensions/wikilink.py | 140 +++++++++++++++++++ mdx/__init__.py | 0 mdx/mdx_codehilite.py | 218 ----------------------------- mdx/mdx_extra.py | 52 ------- mdx/mdx_fenced_code.py | 105 -------------- mdx/mdx_footnotes.py | 276 ------------------------------------- mdx/mdx_headerid.py | 202 --------------------------- mdx/mdx_imagelinks.py | 135 ------------------ mdx/mdx_meta.py | 81 ----------- mdx/mdx_rss.py | 118 ---------------- mdx/mdx_tables.py | 72 ---------- mdx/mdx_wikilink.py | 140 ------------------- setup.py | 2 +- 25 files changed, 1418 insertions(+), 1416 deletions(-) create mode 100644 markdown_extensions/__init__.py create mode 100644 markdown_extensions/codehilite.py create mode 100644 markdown_extensions/extra.py create mode 100644 markdown_extensions/fenced_code.py create mode 100644 markdown_extensions/footnotes.py create mode 100644 markdown_extensions/headerid.py create mode 100644 markdown_extensions/imagelinks.py create mode 100644 markdown_extensions/meta.py create mode 100644 markdown_extensions/rss.py create mode 100644 markdown_extensions/tables.py create mode 100644 markdown_extensions/wikilink.py delete mode 100644 mdx/__init__.py delete mode 100644 mdx/mdx_codehilite.py delete mode 100644 mdx/mdx_extra.py delete mode 100644 mdx/mdx_fenced_code.py delete mode 100644 mdx/mdx_footnotes.py delete mode 100644 mdx/mdx_headerid.py delete mode 100644 mdx/mdx_imagelinks.py delete mode 100644 mdx/mdx_meta.py delete mode 100644 mdx/mdx_rss.py delete mode 100644 mdx/mdx_tables.py delete mode 100644 mdx/mdx_wikilink.py diff --git a/MANIFEST b/MANIFEST index 1200503..4e7e3df 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,14 +1,14 @@ markdown.py -mdx/__init__.py -mdx/mdx_codehilite.py -mdx/mdx_fenced_code.py -mdx/mdx_footnotes.py -mdx/mdx_headerid.py -mdx/mdx_imagelinks.py -mdx/mdx_meta.py -mdx/mdx_rss.py -mdx/mdx_tables.py -mdx/mdx_wikilink.py +markdown_extnesions/__init__.py +markdown_extensions/codehilite.py +markdown_extensions/fenced_code.py +markdown_extensions/footnotes.py +markdown_extensions/headerid.py +markdown_extensions/imagelinks.py +markdown_extensions/meta.py +markdown_extensions/rss.py +markdown_extensions/tables.py +markdown_extensions/wikilink.py setup.py scripts/pymarkdown.py docs/README diff --git a/markdown.py b/markdown.py index 38923ac..af611c8 100755 --- a/markdown.py +++ b/markdown.py @@ -2129,18 +2129,20 @@ def load_extension(ext_name, configs = []): pairs = [x.split("=") for x in ext_args.split(",")] configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) - extension_module_name = "mdx_" + ext_name + ext_module = 'markdown_extensions' + module_name = '.'.join([ext_module, ext_name]) + extension_module_name = '_'.join(['mdx', ext_name]) try: - module = __import__(extension_module_name) - + module = __import__(module_name, {}, {}, [ext_module]) except ImportError: try: - module = __import__('.'.join(['mdx', extension_module_name]), {}, {}, ['mdx']) + module = __import__(extension_module_name) except: message(WARN, - "Couldn't load extension '%s' from \"%s\" - continuing without." - % (ext_name, extension_module_name) ) + "Failed loading extension '%s' from '%s' or '%s' " + "- continuing without." + % (ext_name, module_name, extension_module_name) ) # Return a dummy (do nothing) Extension as silent failure return Extension(configs={}) diff --git a/markdown_extensions/__init__.py b/markdown_extensions/__init__.py new file mode 100644 index 0000000..e69de29 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.: ). + + 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 = '
%s
\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('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + + def _number(self, txt): + """ Use
    for line numbering """ + # Fix Whitespace + txt = txt.replace('\t', ' '*TAB_LENGTH) + txt = txt.replace(" "*4, "    ") + txt = txt.replace(" "*3, "   ") + txt = txt.replace(" "*2, "  ") + + # Add line numbers + lines = txt.splitlines() + txt = '
      \n' + for line in lines: + txt += '\t
    1. %s
    2. \n'% line + txt += '
    \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 or 2 or more colons + (?P(?:/\w+)*[/ ])? # zero or 1 path ending in either a / or a single space + (?P\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'

    A paragraph before a fenced code block:\\n

    \\n
    Fenced code block\\n
    ' + +Works with safe_mode also (we check this because we are using the HtmlStash): + + >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace') + u'

    A paragraph before a fenced code block:\\n

    \\n
    Fenced code block\\n
    ' + +Include tilde's in a code block and wrap with blank lines: + + >>> text = ''' + ... ~~~~~~~~ + ... + ... ~~~~ + ... + ... ~~~~~~~~''' + >>> markdown.markdown(text, extensions=['fenced_code']) + u'
    \\n~~~~\\n\\n
    ' + +Multiple blocks and language tags: + + >>> text = ''' + ... ~~~~ + ... block one + ... ~~~~{.python} + ... + ... ~~~~ + ...

    block two

    + ... ~~~~{.html}''' + >>> markdown.markdown(text, extensions=['fenced_code']) + u'
    block one\\n
    \\n\\n
    <p>block two</p>\\n
    ' + +""" + +import markdown, re + +# Global vars +FENCED_BLOCK_RE = re.compile( \ + r'(?P^~{3,})[ ]*\n(?P.*?)(?P=fence)[ ]*(\{\.(?P[a-zA-Z0-9_-]*)\})?[ ]*$', + re.MULTILINE|re.DOTALL + ) +CODE_WRAP = '
    %s
    ' +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('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + 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, "↩") + +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'

    Some Header

    ' + +All header IDs are unique: + + >>> text = ''' + ... #Header + ... #Another Header {#header} + ... #Third Header {#header}''' + >>> md = markdown.markdown(text, ['headerid']) + >>> md + u'

    Header

    \\n\\n

    Another Header

    \\n\\n

    Third Header

    ' + +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'

    Some Header

    \\n\\n

    Next Level

    ' + +Turn off auto generated IDs: + + >>> text = ''' + ... # Some Header + ... # Header with ID # { #foo }''' + >>> md = markdown.markdown(text, ['headerid(forceid=False)']) + >>> md + u'

    Some Header

    \\n\\n

    Header with ID

    ' + +Use with MetaData extension: + + >>> text = '''header_level: 2 + ... header_forceid: Off + ... + ... # A Header''' + >>> md = markdown.markdown(text, ['headerid', 'meta']) + >>> md + u'

    A Header

    ' + +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'
      \n' % self.html_id + for c in range(len(self.ids)): + out += u'
    • %s
    • \n'%(self.ids[c], self.labels[c]) + out += u'
    ' + 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 = """""" +SLIDESHOW_LINK = """[slideshow]""" +ALBUM_LINK = """ [%s]""" + + +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 = "

    \n" + + album_url_hash = {} + + for row in rows : + for photo_url, title in row : + new_block += " " + new_block += IMAGE_LINK % (photo_url, + photo_url.get_thumbnail(), + title) + + album_url_hash[str(photo_url.get_album())] = 1 + + new_block += "
    " + + new_block += "
    " + 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 + "

    ") + + 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'

    The body. This is paragraph one.\\n

    ' + >>> 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

    ). + + >>> text = ' Some Code - not extra lines of meta data.' + >>> md = markdown.Markdown(text, ['meta']) + >>> md.convert() + u'

    Some Code - not extra lines of meta data.\\n
    ' + >>> md.Meta + {} + +''' + +import markdown, re + +# Global Vars +META_RE = re.compile(r'^[ ]{0,3}(?P[A-Za-z0-9_-]+):\s*(?P.*)') +META_MORE_RE = re.compile(r'^[ ]{4,}(?P.*)') + +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 = '\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("" % 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 + td = etree.Element('th') + t = t[1:-1] + else: + # otherwise it is a + 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'

    Some text with a WikiLink.\\n

    ' + +To define custom settings the simple way: + + >>> md = markdown.markdown(text, + ... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)'] + ... ) + >>> md + u'

    Some text with a WikiLink.\\n

    ' + +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'

    Some text with a WikiLink.\\n

    ' + +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'

    Some text with a WikiLink.\\n

    ' + +MetaData should not carry over to next document: + + >>> md.convert("No MetaData here.") + u'

    No MetaData here.\\n

    ' + +From the command line: + + python markdown.py -x wikilink(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt + +By [Waylan Limberg](http://achinghead.com/). + +Project website: http://achinghead.com/markdown-wikilinks/ +Contact: waylan [at] gmail [dot] com + +License: [BSD](http://www.opensource.org/licenses/bsd-license.php) + +Version: 0.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\\|\b)(?P([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() + diff --git a/mdx/__init__.py b/mdx/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mdx/mdx_codehilite.py b/mdx/mdx_codehilite.py deleted file mode 100644 index 73c1a79..0000000 --- a/mdx/mdx_codehilite.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/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.: ). - - 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 = '
    %s
    \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('&', '&') - txt = txt.replace('<', '<') - txt = txt.replace('>', '>') - txt = txt.replace('"', '"') - return txt - - def _number(self, txt): - """ Use
      for line numbering """ - # Fix Whitespace - txt = txt.replace('\t', ' '*TAB_LENGTH) - txt = txt.replace(" "*4, "    ") - txt = txt.replace(" "*3, "   ") - txt = txt.replace(" "*2, "  ") - - # Add line numbers - lines = txt.splitlines() - txt = '
        \n' - for line in lines: - txt += '\t
      1. %s
      2. \n'% line - txt += '
      \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 or 2 or more colons - (?P(?:/\w+)*[/ ])? # zero or 1 path ending in either a / or a single space - (?P\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/mdx/mdx_extra.py b/mdx/mdx_extra.py deleted file mode 100644 index e99bec4..0000000 --- a/mdx/mdx_extra.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/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/mdx/mdx_fenced_code.py b/mdx/mdx_fenced_code.py deleted file mode 100644 index c3d9f7f..0000000 --- a/mdx/mdx_fenced_code.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/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'

      A paragraph before a fenced code block:\\n

      \\n
      Fenced code block\\n
      ' - -Works with safe_mode also (we check this because we are using the HtmlStash): - - >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace') - u'

      A paragraph before a fenced code block:\\n

      \\n
      Fenced code block\\n
      ' - -Include tilde's in a code block and wrap with blank lines: - - >>> text = ''' - ... ~~~~~~~~ - ... - ... ~~~~ - ... - ... ~~~~~~~~''' - >>> markdown.markdown(text, extensions=['fenced_code']) - u'
      \\n~~~~\\n\\n
      ' - -Multiple blocks and language tags: - - >>> text = ''' - ... ~~~~ - ... block one - ... ~~~~{.python} - ... - ... ~~~~ - ...

      block two

      - ... ~~~~{.html}''' - >>> markdown.markdown(text, extensions=['fenced_code']) - u'
      block one\\n
      \\n\\n
      <p>block two</p>\\n
      ' - -""" - -import markdown, re - -# Global vars -FENCED_BLOCK_RE = re.compile( \ - r'(?P^~{3,})[ ]*\n(?P.*?)(?P=fence)[ ]*(\{\.(?P[a-zA-Z0-9_-]*)\})?[ ]*$', - re.MULTILINE|re.DOTALL - ) -CODE_WRAP = '
      %s
      ' -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('&', '&') - txt = txt.replace('<', '<') - txt = txt.replace('>', '>') - txt = txt.replace('"', '"') - return txt - - -def makeExtension(configs=None): - return FencedCodeExtension() - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/mdx/mdx_footnotes.py b/mdx/mdx_footnotes.py deleted file mode 100644 index b46efbb..0000000 --- a/mdx/mdx_footnotes.py +++ /dev/null @@ -1,276 +0,0 @@ -""" -========================= 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, "↩") - -def makeExtension(configs=[]): - return FootnoteExtension(configs=configs) - diff --git a/mdx/mdx_headerid.py b/mdx/mdx_headerid.py deleted file mode 100644 index 8947338..0000000 --- a/mdx/mdx_headerid.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/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'

      Some Header

      ' - -All header IDs are unique: - - >>> text = ''' - ... #Header - ... #Another Header {#header} - ... #Third Header {#header}''' - >>> md = markdown.markdown(text, ['headerid']) - >>> md - u'

      Header

      \\n\\n

      Another Header

      \\n\\n

      Third Header

      ' - -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'

      Some Header

      \\n\\n

      Next Level

      ' - -Turn off auto generated IDs: - - >>> text = ''' - ... # Some Header - ... # Header with ID # { #foo }''' - >>> md = markdown.markdown(text, ['headerid(forceid=False)']) - >>> md - u'

      Some Header

      \\n\\n

      Header with ID

      ' - -Use with MetaData extension: - - >>> text = '''header_level: 2 - ... header_forceid: Off - ... - ... # A Header''' - >>> md = markdown.markdown(text, ['headerid', 'meta']) - >>> md - u'

      A Header

      ' - -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'
        \n' % self.html_id - for c in range(len(self.ids)): - out += u'
      • %s
      • \n'%(self.ids[c], self.labels[c]) - out += u'
      ' - return out - - -def makeExtension(configs=None) : - return HeaderIdExtension(configs=configs) - -if __name__ == "__main__": - import doctest - doctest.testmod() - diff --git a/mdx/mdx_imagelinks.py b/mdx/mdx_imagelinks.py deleted file mode 100644 index e545b24..0000000 --- a/mdx/mdx_imagelinks.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -========================= 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 = """""" -SLIDESHOW_LINK = """[slideshow]""" -ALBUM_LINK = """ [%s]""" - - -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 = "

      \n" - - album_url_hash = {} - - for row in rows : - for photo_url, title in row : - new_block += " " - new_block += IMAGE_LINK % (photo_url, - photo_url.get_thumbnail(), - title) - - album_url_hash[str(photo_url.get_album())] = 1 - - new_block += "
      " - - new_block += "
      " - 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 + "

      ") - - elif line[1:6] == "~~~~~" : - rows.append([]) # start a new row - else : - parts = line.split() - line = parts[0] - title = " ".join(parts[1:]) - - album, photo = line.split("/") - photo_url = url.get_photo(album, photo, - len(all_images)+1) - all_images.append(photo_url) - rows[-1].append((photo_url, title)) - - if not album in albums : - albums.append(album) - - return new_lines - - -def makeExtension(configs) : - return ImageLinksExtension(configs) - diff --git a/mdx/mdx_meta.py b/mdx/mdx_meta.py deleted file mode 100644 index 30dea8a..0000000 --- a/mdx/mdx_meta.py +++ /dev/null @@ -1,81 +0,0 @@ -#!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'

      The body. This is paragraph one.\\n

      ' - >>> 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

      ). - - >>> text = ' Some Code - not extra lines of meta data.' - >>> md = markdown.Markdown(text, ['meta']) - >>> md.convert() - u'

      Some Code - not extra lines of meta data.\\n
      ' - >>> md.Meta - {} - -''' - -import markdown, re - -# Global Vars -META_RE = re.compile(r'^[ ]{0,3}(?P[A-Za-z0-9_-]+):\s*(?P.*)') -META_MORE_RE = re.compile(r'^[ ]{4,}(?P.*)') - -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/mdx/mdx_rss.py b/mdx/mdx_rss.py deleted file mode 100644 index b88b9b5..0000000 --- a/mdx/mdx_rss.py +++ /dev/null @@ -1,118 +0,0 @@ -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 = '\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("" % content) - description.text = pholder - - return rss - - -def makeExtension(configs): - - return RssExtension(configs) diff --git a/mdx/mdx_tables.py b/mdx/mdx_tables.py deleted file mode 100644 index 829044c..0000000 --- a/mdx/mdx_tables.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/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 - td = etree.Element('th') - t = t[1:-1] - else: - # otherwise it is a - 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/mdx/mdx_wikilink.py b/mdx/mdx_wikilink.py deleted file mode 100644 index 47037a6..0000000 --- a/mdx/mdx_wikilink.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/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'

      Some text with a WikiLink.\\n

      ' - -To define custom settings the simple way: - - >>> md = markdown.markdown(text, - ... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)'] - ... ) - >>> md - u'

      Some text with a WikiLink.\\n

      ' - -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'

      Some text with a WikiLink.\\n

      ' - -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'

      Some text with a WikiLink.\\n

      ' - -MetaData should not carry over to next document: - - >>> md.convert("No MetaData here.") - u'

      No MetaData here.\\n

      ' - -From the command line: - - python markdown.py -x wikilink(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt - -By [Waylan Limberg](http://achinghead.com/). - -Project website: http://achinghead.com/markdown-wikilinks/ -Contact: waylan [at] gmail [dot] com - -License: [BSD](http://www.opensource.org/licenses/bsd-license.php) - -Version: 0.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\\|\b)(?P([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() - diff --git a/setup.py b/setup.py index 75f9d38..424b7dd 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,6 @@ setup( url = "http://www.freewisdom.org/projects/python-markdown", license = "BSD License, GNU Public License (GPL)", py_modules = ["markdown"], - packages = ['mdx'], + packages = ['markdown_extensions'], scripts = ['scripts/pymarkdown.py'], ) -- cgit v1.2.3