aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--markdown.py2
-rw-r--r--mdx_codehilite.py328
-rw-r--r--mdx_fenced_code.py105
-rw-r--r--mdx_headerid.py173
-rw-r--r--mdx_meta.py81
-rw-r--r--mdx_wikilink.py70
-rw-r--r--setup.py12
-rw-r--r--tests/misc/headers.html4
-rw-r--r--tests/misc/headers.txt2
10 files changed, 566 insertions, 215 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ac1b58
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+*.bak
+*.tmp
+tmp/*
diff --git a/markdown.py b/markdown.py
index 8978b89..422cde4 100644
--- a/markdown.py
+++ b/markdown.py
@@ -1295,7 +1295,7 @@ class CorePatterns:
"""
patterns = {
- 'header': r'(#*)([^#]*)(#*)', # # A title
+ 'header': r'(#{1,6})[ \t]*(.*?)[ \t]*(#*)', # # A title
'reference-def': r'(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)',
# [Google]: http://www.google.com/
'containsline': r'([-]*)$|^([=]*)', # -----, =====, etc.
diff --git a/mdx_codehilite.py b/mdx_codehilite.py
index ea83e9a..6f81598 100644
--- a/mdx_codehilite.py
+++ b/mdx_codehilite.py
@@ -1,197 +1,141 @@
#!/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 -----------------
-DEFAULT_HILITER = 'pygments' # one of 'enscript', 'dp', or 'pygments'
try:
TAB_LENGTH = markdown.TAB_LENGTH
except AttributeError:
TAB_LENGTH = 4
-# --------------- THE CODE -------------------------------------------
-# --------------- hiliter utility functions --------------------------
-def escape(txt) :
- '''basic html escaping'''
- txt = txt.replace('&', '&')
- txt = txt.replace('<', '&lt;')
- txt = txt.replace('>', '&gt;')
- txt = txt.replace('"', '&quot;')
- return txt
-def number(txt):
- '''use <ol> for line numbering'''
- # Fix Whitespace
- txt = txt.replace('\t', ' '*TAB_LENGTH)
- txt = txt.replace(" "*4, "&nbsp; &nbsp; ")
- txt = txt.replace(" "*3, "&nbsp; &nbsp;")
- txt = txt.replace(" "*2, "&nbsp; ")
-
- # Add line numbers
- lines = txt.splitlines()
- txt = '<div class="codehilite"><pre><ol>\n'
- for line in lines:
- txt += '\t<li>%s</li>\n'% line
- txt += '</ol></pre></div>\n'
- return txt
-
-# ---------------- The hiliters ---------------------------------------
-def enscript(src, lang=None, num=True):
- '''
-Pass source code on to [enscript] (http://www.codento.com/people/mtr/genscript/)
-command line utility for hiliting.
-
-Usage:
- >>> enscript(src [, lang [, num ]] )
-
- @param src: Can be a string or any object with a .readline attribute.
-
- @param lang: The language of code. Basic escaping only, if None.
+# ------------------ The Main CodeHilite Class ----------------------
+class CodeHilite:
+ """
+ Determine language of source code, and pass it into the pygments hilighter.
- @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
+ Basic Usage:
+ >>> code = CodeHilite(src = text)
+ >>> html = code.hilite()
+
+ * src: Can be a string or any object with a .readline attribute.
- @returns : A string of html.
- '''
- if lang:
- cmd = 'enscript --highlight=%s --color --language=html --tabsize=%d --output=-'% (lang, TAB_LENGTH)
- from os import popen3
- (i, out, err) = popen3(cmd)
- i.write(src)
- i.close()
- # check for errors
- e = err.read()
- if e != 'output left in -\n' :
- # error - just escape
- txt = escape(src)
- else :
- import re
- pattern = re.compile(r'<PRE>(?P<code>.*?)</PRE>', re.DOTALL)
- txt = pattern.search(out.read()).group('code')
- # fix enscripts output
- txt = txt.replace('\n</FONT></I>', '</FONT></I>\n').strip()
- html_map = {'<I>' : '<em>',
- '</I>' : '</em>',
- '<B>' : '<strong>',
- '</B>' : '</strong>',
- '<FONT COLOR="#' : '<span style="color:#',
- '</FONT>' : '</span>'
- }
- for k, v in html_map.items() :
- txt = txt.replace(k, v)
- else:
- txt = escape(src)
- if num :
- txt = number(txt)
- else :
- txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt
- return txt
-
-
-def dp(src, lang=None, num=True):
- '''
-Pass source code to a textarea for the [dp.SyntaxHighlighter] (http://www.dreamprojections.com/syntaxhighlighter/Default.aspx)
-
-Usage:
- >>> dp(src [, lang [, num ]] )
-
- @param src: A string.
-
- @param lang: The language of code. Undefined if None.
-
- @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
+ * linenos: (Boolen) Turns line numbering 'on' or 'off' (off by default).
- @returns : A string of html.
- '''
- gutter = ''
- if not num:
- gutter = ':nogutter'
- if not lang:
- lang = ''
-
- return '<div class="codehilite"><textarea name="code" class="%s%s" cols="60" rows="10">\n%s\n</textarea></div>\n'% (lang, gutter, src)
+ 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 pygment(src, lang = None, num = True):
- '''
-Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with
-optional line numbers. The output should then be styled with css to your liking.
-No styles are applied by default - only styling hooks (i.e.: <span class="k">).
+ """
-Usage:
- >>> pygment(src [, lang [, num ]] )
+ def __init__(self, src=None, linenos = False):
+ self.src = src
+ self.lang = None
+ self.linenos = linenos
- @param src: Can be a string or any object with a .readline attribute.
+ def hilite(self):
+ """
+ Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with
+ optional line numbers. The output should then be styled with css to
+ your liking. No styles are applied by default - only styling hooks
+ (i.e.: <span class="k">).
- @param lang: The language of code. Pygments will try to guess language if None.
+ returns : A string of html.
+
+ """
+
+ self.src = self.src.strip('\n')
+
+ self._getLang()
- @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
-
- @returns : A string of html.
- '''
- try:
- from pygments import highlight
- from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer
- from pygments.formatters import HtmlFormatter
- except ImportError:
- # just escape and pass through
- txt = escape(src)
- if num:
- txt = number(txt)
- else :
- txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt
- return txt
- else:
try:
- lexer = get_lexer_by_name(lang)
- except ValueError:
+ from pygments import highlight
+ from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer
+ from pygments.formatters import HtmlFormatter
+ except ImportError:
+ # just escape and pass through
+ txt = self._escape(self.src)
+ if num:
+ txt = self._number(txt)
+ else :
+ txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt
+ return txt
+ else:
try:
- lexer = guess_lexer(src)
+ lexer = get_lexer_by_name(self.lang)
except ValueError:
- lexer = TextLexer()
- formatter = HtmlFormatter(linenos=num, cssclass="codehilite")
- return highlight(src, lexer, formatter)
-
-
-# ------------------ The Main CodeHilite Class ----------------------
-class CodeHilite:
- '''
-A wrapper class providing a single API for various hilighting engines. Takes source code, determines which language it containes (if not provided), and passes it into the hiliter specified.
-
-Basic Usage:
- >>> code = CodeHilite(src = text)
- >>> html = code.hilite()
-
- @param src: Can be a string or any object with a .readline attribute.
-
- @param lang: A string. Accepted values determined by hiliter used. Overrides _getLang()
-
- @param linenos: (Boolen) Turns line numbering 'on' or 'off' (off by default).
-
- @param hiliter: A string. One of 'enscript', 'dp', or 'pygments'.
+ try:
+ lexer = guess_lexer(self.src)
+ except ValueError:
+ lexer = TextLexer()
+ formatter = HtmlFormatter(linenos=self.linenos, cssclass="codehilite")
+ return highlight(self.src, lexer, formatter)
+
+ def _escape(self, txt):
+ """ basic html escaping """
+ txt = txt.replace('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
-Low Level Usage:
- >>> code = CodeHilite()
- >>> code.src = text # Can be a string or any object with a .readline attribute.
- >>> code.lang = 'python' # Setting this will override _getLang()
- >>> code.linenos = True # True or False; Turns line numbering on or off.
- >>> code.hiliter = MyCustomHiliter # Where MyCustomHiliter is callable, takes three arguments (src, lang, linenos) and returns a string.
- >>> html = code.hilite()
- '''
- def __init__(self, src=None, lang=None, linenos = False, hiliter=DEFAULT_HILITER):
- self.src = src
- self.lang = lang
- self.linenos = linenos
- # map of highlighters
- hl_map = { 'enscript' : enscript, 'dp' : dp, 'pygments' : pygment }
- try :
- self.hiliter = hl_map[hiliter]
- except KeyError:
- raise "Please provide a valid hiliter as a string. One of 'enscript', 'dp', or 'pygments'"
+ def _number(self, txt):
+ """ Use <ol> for line numbering """
+ # Fix Whitespace
+ txt = txt.replace('\t', ' '*TAB_LENGTH)
+ txt = txt.replace(" "*4, "&nbsp; &nbsp; ")
+ txt = txt.replace(" "*3, "&nbsp; &nbsp;")
+ txt = txt.replace(" "*2, "&nbsp; ")
+
+ # Add line numbers
+ lines = txt.splitlines()
+ txt = '<div class="codehilite"><pre><ol>\n'
+ for line in lines:
+ txt += '\t<li>%s</li>\n'% line
+ txt += '</ol></pre></div>\n'
+ return txt
def _getLang(self):
- '''
-Determines language of a code block from shebang lines and whether said line should be removed or left in place. If the sheband line contains a path (even a single /) then it is assumed to be a real shebang lines and left alone. However, if no path is given (e.i.: #!python or :::python) then it is assumed to be a mock shebang for language identifitation of a code fragment and removed from the code block prior to processing for code highlighting. When a mock shebang (e.i: #!python) is found, line numbering is turned on. When colons are found in place of a shebang (e.i.: :::python), line numbering is left in the current state - off by default.
- '''
+ """
+ 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
@@ -224,47 +168,47 @@ Determines language of a code block from shebang lines and whether said line sho
self.src = "\n".join(lines).strip("\n")
- def hilite(self):
- '''The wrapper function which brings it all togeather'''
- self.src = self.src.strip('\n')
-
- if not self.lang : self._getLang()
-
- return self.hiliter(self.src, self.lang, self.linenos)
-# ------------------ The Markdown Extention -------------------------------
-class CodeHiliteExtention (markdown.Extension) :
+# ------------------ The Markdown Extension -------------------------------
+class CodeHiliteExtention(markdown.Extension):
def __init__(self, configs):
# define default configs
- self.config = {'hiliter' : [DEFAULT_HILITER, "one of 'enscript', 'dp', or 'pygments'"],
- 'force_linenos' : [False, "Force line numbers - Default: False"] }
+ self.config = {
+ 'force_linenos' : [False, "Force line numbers - Default: False"]
+ }
# Override defaults with user settings
- for key, value in configs :
+ for key, value in configs:
# self.config[key][0] = value
self.setConfig(key, value)
- def extendMarkdown(self, md, md_globals) :
+ def extendMarkdown(self, md, md_globals):
def _hiliteCodeBlock(parent_elem, lines, inList):
- """Overrides function of same name in standard Markdown class and
- sends code blocks to a code highlighting proccessor. The result
- is then stored in the HtmlStash, a placeholder is inserted into
- the dom and the remainder of the text file is processed recursively.
+ """
+ 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
+
+ """
- @param parent_elem: DOM element to which the content will be added
- @param lines: a list of lines
- @param inList: a level
- @returns: None"""
detabbed, theRest = md.blockGuru.detectTabbed(lines)
text = "\n".join(detabbed).rstrip()+"\n"
- code = CodeHilite(text, hiliter=self.config['hiliter'][0], linenos=self.config['force_linenos'][0])
- placeholder = md.htmlStash.store(code.hilite())
+ code = CodeHilite(text, linenos=self.config['force_linenos'][0])
+ placeholder = md.htmlStash.store(code.hilite(), safe=True)
parent_elem.appendChild(md.doc.createTextNode(placeholder))
md._processSection(parent_elem, theRest, inList)
md._processCodeBlock = _hiliteCodeBlock
-def makeExtension(configs=None) :
+def makeExtension(configs={}):
return CodeHiliteExtention(configs=configs)
+
diff --git a/mdx_fenced_code.py b/mdx_fenced_code.py
new file mode 100644
index 0000000..c3d9f7f
--- /dev/null
+++ b/mdx_fenced_code.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+"""
+Fenced Code Extension for Python Markdown
+=========================================
+
+This extension adds Fenced Code Blocks to Python-Markdown.
+
+ >>> import markdown
+ >>> text = '''
+ ... A paragraph before a fenced code block:
+ ...
+ ... ~~~
+ ... Fenced code block
+ ... ~~~
+ ... '''
+ >>> html = markdown.markdown(text, extensions=['fenced_code'])
+ >>> html
+ u'<p>A paragraph before a fenced code block:\\n</p>\\n<pre><code>Fenced code block\\n</code></pre>'
+
+Works with safe_mode also (we check this because we are using the HtmlStash):
+
+ >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
+ u'<p>A paragraph before a fenced code block:\\n</p>\\n<pre><code>Fenced code block\\n</code></pre>'
+
+Include tilde's in a code block and wrap with blank lines:
+
+ >>> text = '''
+ ... ~~~~~~~~
+ ...
+ ... ~~~~
+ ...
+ ... ~~~~~~~~'''
+ >>> markdown.markdown(text, extensions=['fenced_code'])
+ u'<pre><code>\\n~~~~\\n\\n</code></pre>'
+
+Multiple blocks and language tags:
+
+ >>> text = '''
+ ... ~~~~
+ ... block one
+ ... ~~~~{.python}
+ ...
+ ... ~~~~
+ ... <p>block two</p>
+ ... ~~~~{.html}'''
+ >>> markdown.markdown(text, extensions=['fenced_code'])
+ u'<pre><code class="python">block one\\n</code></pre>\\n\\n<pre><code class="html">&lt;p&gt;block two&lt;/p&gt;\\n</code></pre>'
+
+"""
+
+import markdown, re
+
+# Global vars
+FENCED_BLOCK_RE = re.compile( \
+ r'(?P<fence>^~{3,})[ ]*\n(?P<code>.*?)(?P=fence)[ ]*(\{\.(?P<lang>[a-zA-Z0-9_-]*)\})?[ ]*$',
+ re.MULTILINE|re.DOTALL
+ )
+CODE_WRAP = '<pre><code%s>%s</code></pre>'
+LANG_TAG = ' class="%s"'
+
+
+class FencedCodeExtension(markdown.Extension):
+
+ def extendMarkdown(self, md, md_globals):
+ """ Add FencedBlockPreprocessor to the Markdown instance. """
+
+ FENCED_BLOCK_PREPROCESSOR = FencedBlockPreprocessor()
+ FENCED_BLOCK_PREPROCESSOR.md = md
+ md.textPreprocessors.insert(0, FENCED_BLOCK_PREPROCESSOR)
+
+
+class FencedBlockPreprocessor(markdown.TextPreprocessor):
+
+ def run(self, text):
+ """ Match and store Fenced Code Blocks in the HtmlStash. """
+ while 1:
+ m = FENCED_BLOCK_RE.search(text)
+ if m:
+ lang = ''
+ if m.group('lang'):
+ lang = LANG_TAG % m.group('lang')
+ code = CODE_WRAP % (lang, self._escape(m.group('code')))
+ placeholder = self.md.htmlStash.store(code, safe=True)
+ text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
+ else:
+ break
+ return text
+
+ def _escape(self, txt):
+ """ basic html escaping """
+ txt = txt.replace('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+
+def makeExtension(configs=None):
+ return FencedCodeExtension()
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/mdx_headerid.py b/mdx_headerid.py
new file mode 100644
index 0000000..2e658cd
--- /dev/null
+++ b/mdx_headerid.py
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+
+"""
+HeaderID Extension for Python-Markdown
+======================================
+
+Adds ability to set HTML IDs for headers.
+
+Basic usage:
+
+ >>> import markdown
+ >>> text = "# Some Header # {#some_id}"
+ >>> md = markdown.markdown(text, ['headerid'])
+ >>> md
+ u'<h1 id="some_id">Some Header</h1>'
+
+All header IDs are unique:
+
+ >>> text = '''
+ ... #Header
+ ... #Another Header {#header}
+ ... #Third Header {#header}'''
+ >>> md = markdown.markdown(text, ['headerid'])
+ >>> md
+ u'<h1 id="header">Header</h1>\\n\\n<h1 id="header_1">Another Header</h1>\\n\\n<h1 id="header_2">Third Header</h1>'
+
+To fit within a html template's hierarchy, set the header base level:
+
+ >>> text = '''
+ ... #Some Header
+ ... ## Next Level'''
+ >>> md = markdown.markdown(text, ['headerid(level=3)'])
+ >>> md
+ u'<h3 id="some_header">Some Header</h3>\\n\\n<h4 id="next_level">Next Level</h4>'
+
+Turn off auto generated IDs:
+
+ >>> text = '''
+ ... # Some Header
+ ... # Header with ID # { #foo }'''
+ >>> md = markdown.markdown(text, ['headerid(forceid=False)'])
+ >>> md
+ u'<h1>Some Header</h1>\\n\\n<h1 id="foo">Header with ID</h1>'
+
+Use with MetaData extension:
+
+ >>> text = '''header_level: 2
+ ... header_forceid: Off
+ ...
+ ... # A Header'''
+ >>> md = markdown.markdown(text, ['headerid', 'meta'])
+ >>> md
+ u'<h2>A Header</h2>'
+
+By [Waylan Limberg](http://achinghead.com/).
+
+Project website: http://achinghead.com/markdown-headerid/
+Contact: waylan [at] gmail [dot] com
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
+
+Version: 0.1 (May 2, 2008)
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 1.7+](http://www.freewisdom.org/projects/python-markdown/)
+
+"""
+
+import markdown
+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.']
+ }
+
+ for key, value in configs:
+ self.setConfig(key, value)
+
+
+ def extendMarkdown(self, md, md_globals) :
+
+ md.IDs = []
+
+ 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 = md.doc.createElement("h%d" % level)
+ parent_elem.appendChild(h)
+ for item in md._handleInline(m.group(2).strip()) :
+ h.appendChild(item)
+ if m.group(3) :
+ h.setAttribute('id', _unique_id(m.group(3)))
+ elif force_id:
+ h.setAttribute('id', _create_id(m.group(2).strip()))
+ 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)
+
+
+
+def makeExtension(configs=None) :
+ return HeaderIdExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+
diff --git a/mdx_meta.py b/mdx_meta.py
new file mode 100644
index 0000000..30dea8a
--- /dev/null
+++ b/mdx_meta.py
@@ -0,0 +1,81 @@
+#!usr/bin/python
+
+'''
+Meta Data Extension for Python-Markdown
+==========================================
+
+This extension adds Meta Data handling to markdown.
+
+ >>> import markdown
+ >>> text = """Title: A Test Doc.
+ ... Author: Waylan Limberg
+ ... John Doe
+ ... Blank_Data:
+ ...
+ ... The body. This is paragraph one.
+ ... """
+ >>> md = markdown.Markdown(text, ['meta'])
+ >>> md.convert()
+ u'<p>The body. This is paragraph one.\\n</p>'
+ >>> md.Meta
+ {u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']}
+
+Make sure text without Meta Data still works (markdown < 1.6b returns a <p>).
+
+ >>> text = ' Some Code - not extra lines of meta data.'
+ >>> md = markdown.Markdown(text, ['meta'])
+ >>> md.convert()
+ u'<pre><code>Some Code - not extra lines of meta data.\\n</code></pre>'
+ >>> md.Meta
+ {}
+
+'''
+
+import markdown, re
+
+# Global Vars
+META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)')
+META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)')
+
+class MetaExtension (markdown.Extension) :
+ def __init__(self, configs):
+ pass
+
+ def extendMarkdown(self, md, md_globals) :
+ self.md = md
+
+ # Insert meta preprocessor first
+ META_PREPROCESSOR = MetaPreprocessor()
+ META_PREPROCESSOR.md = md
+ md.preprocessors.insert(0, META_PREPROCESSOR)
+
+class MetaPreprocessor(markdown.Preprocessor) :
+ def run(self, lines) :
+ meta = {}
+ key = None
+ while 1:
+ line = lines.pop(0)
+ if line.strip() == '':
+ break # blank line - done
+ m1 = META_RE.match(line)
+ if m1:
+ key = m1.group('key').lower().strip()
+ meta[key] = [m1.group('value').strip()]
+ else:
+ m2 = META_MORE_RE.match(line)
+ if m2 and key:
+ # Add another line to existing key
+ meta[key].append(m2.group('value').strip())
+ else:
+ lines.insert(0, line)
+ break # no meta data - done
+ self.md.Meta = meta
+ return lines
+
+
+def makeExtension(configs=None) :
+ return MetaExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/mdx_wikilink.py b/mdx_wikilink.py
index 4279c1a..46d6c3f 100644
--- a/mdx_wikilink.py
+++ b/mdx_wikilink.py
@@ -12,7 +12,7 @@ Basic usage:
>>> text = "Some text with a WikiLink."
>>> md = markdown.markdown(text, ['wikilink'])
>>> md
- '\\n<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.\\n</p>\\n\\n\\n'
+ u'<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.\\n</p>'
To define custom settings the simple way:
@@ -20,20 +20,35 @@ To define custom settings the simple way:
... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)']
... )
>>> md
- '\\n<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.\\n</p>\\n\\n\\n'
+ u'<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.\\n</p>'
Custom settings the complex way:
- >>> md = markdown.Markdown(text,
+ >>> md = markdown.Markdown(
... extensions = ['wikilink'],
... extension_configs = {'wikilink': [
... ('base_url', 'http://example.com/'),
... ('end_url', '.html'),
... ('html_class', '') ]},
- ... encoding='utf8',
... safe_mode = True)
- >>> str(md)
- '\\n<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>\\n\\n\\n'
+ >>> md.convert(text)
+ u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>'
+
+Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
+
+ >>> text = """wiki_base_url: http://example.com/
+ ... wiki_end_url: .html
+ ... wiki_html_class:
+ ...
+ ... Some text with a WikiLink."""
+ >>> md = markdown.Markdown(extensions=['meta', 'wikilink'])
+ >>> md.convert(text)
+ u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>'
+
+MetaData should not carry over to next document:
+
+ >>> md.convert("No MetaData here.")
+ u'<p>No <a href="/MetaData/" class="wikilink">MetaData</a> here.\\n</p>'
From the command line:
@@ -46,13 +61,11 @@ Contact: waylan [at] gmail [dot] com
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
-Version: 0.4 (Oct 14, 2006)
+Version: 0.6 (May 2, 2008)
Dependencies:
* [Python 2.3+](http://python.org)
* [Markdown 1.6+](http://www.freewisdom.org/projects/python-markdown/)
-* For older dependencies use [WikiLink Version 0.3]
-(http://code.limberg.name/svn/projects/py-markdown-ext/wikilinks/tags/release-0.3/)
'''
import markdown
@@ -68,16 +81,16 @@ class WikiLinkExtension (markdown.Extension) :
# Override defaults with user settings
for key, value in configs :
- # self.config[key][0] = value
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals):
self.md = md
- #md.registerExtension(self) #???
# append to end of inline patterns
WIKILINK_RE = r'''(?P<escape>\\|\b)(?P<camelcase>([A-Z]+[a-z-_]+){2,})\b'''
- md.inlinePatterns.append(WikiLinks(WIKILINK_RE, self.config))
+ 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):
@@ -87,19 +100,36 @@ class WikiLinks (markdown.BasePattern) :
def handleMatch(self, m, doc) :
if m.group('escape') == '\\':
a = doc.createTextNode(m.group('camelcase'))
- else :
- url = '%s%s%s'% (self.config['base_url'][0], m.group('camelcase'), self.config['end_url'][0])
+ 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 = doc.createElement('a')
a.appendChild(doc.createTextNode(label))
a.setAttribute('href', url)
- if self.config['html_class'][0] :
- a.setAttribute('class', self.config['html_class'][0])
+ if html_class:
+ a.setAttribute('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 makeExtension(configs=None) :
return WikiLinkExtension(configs=configs)
-if __name__ == "__main__":
- import doctest
- doctest.testmod() \ No newline at end of file
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+
diff --git a/setup.py b/setup.py
index de36dfc..5596eca 100644
--- a/setup.py
+++ b/setup.py
@@ -10,5 +10,15 @@ setup(
maintainer_email = "waylan [at] gmail.com",
url = "http://www.freewisdom.org/projects/python-markdown",
license = "BSD License, GNU Public License (GPL)",
- py_modules = ["markdown","mdx_footnotes", "mdx_rss"],
+ py_modules = ["markdown",
+ "mdx_codehilite",
+ "mdx_fenced_code",
+ "mdx_footnotes",
+ "mdx_headerid",
+ "mdx_imagelinks",
+ "mdx_meta",
+ "mdx_rss",
+ "mdx_tables",
+ "mdx_wikilink",
+ ],
)
diff --git a/tests/misc/headers.html b/tests/misc/headers.html
index 61bc266..7041eda 100644
--- a/tests/misc/headers.html
+++ b/tests/misc/headers.html
@@ -7,4 +7,6 @@
<h1><a href="http://some.link.com/">Markdown</a></h1>
-<h1>[5]: http://foo.com/</h1> \ No newline at end of file
+<h1>[5]: http://foo.com/</h1>
+
+<h1>Issue #1: Markdown</h1>
diff --git a/tests/misc/headers.txt b/tests/misc/headers.txt
index 1cb9bc0..2ddb391 100644
--- a/tests/misc/headers.txt
+++ b/tests/misc/headers.txt
@@ -7,3 +7,5 @@ Line 3
# [Markdown](http://some.link.com/)
# [5]: http://foo.com/
+
+# Issue #1: Markdown