#!/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()