aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mdx_headerid.py173
-rw-r--r--mdx_meta.py81
-rw-r--r--setup.py12
3 files changed, 265 insertions, 1 deletions
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/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",
+ ],
)