Permalink
Browse files

Add syntax highlighter filter.

  • Loading branch information...
2 parents bca36b3 + a4aab8a commit 969b3062809b86fb8ec9c9691765a4f4481485c8 @rsenk330 committed Dec 19, 2012
View
@@ -35,14 +35,14 @@ Filters that are currently completed:
* **MarkdownFilter**: Markdown -> HTML
* **PlainTextInputFilter**: Escapes HTML tags and wraps in a div
* **MentionFilter**: Replace `@mentions` with a URL
+* **SyntaxHighlightFilter**: Syntax highlighting
Filters that are being worked on:
* **AutolinkFilter**: Automatic linking of URLs
* **EmojiFilter**: Replace [emoji](http://www.emoji-cheat-sheet.com/) tags with images
* **HttpsFilter**: Replace HTTP URLs with HTTPS
* **ImageMaxWidthFilter**: Link to the full size image when creating image previews
-* **SyntaxHighlightFilter**: Syntax highlighting
* **TextileFilter**: Textile -> HTML
* **TableOfContentsFilter**: Adds the `name` attribute to headers
@@ -1,3 +1,4 @@
-from .markdown import markdown
+from .markdown import markdown, SyntaxRenderer
from .mention import Mention as mention
from .plaintext import plaintext
+from .syntax_highlighter import SyntaxHighlighter as syntax_highlighter
@@ -1,5 +1,14 @@
+from xml.sax import saxutils
+
import misaka as m
+class SyntaxRenderer(m.HtmlRenderer):
+ def block_code(self, text, lang):
+ if lang:
+ return '\n<pre><code lang="{0}">{1}</code></pre>\n'.format(lang, saxutils.escape(text.strip()))
+ else:
+ return '\n<pre><code>{0}</code></pre>\n'.format(saxutils.escape(text.strip()))
+
def markdown(context={}):
"""Renders HTML from Makrdown text.
@@ -10,7 +19,9 @@ def markdown(context={}):
:returns: The filter function pointer to render the HTML
"""
+ md = m.Markdown(SyntaxRenderer(), extensions=m.EXT_FENCED_CODE | m.EXT_STRIKETHROUGH | m.EXT_NO_INTRA_EMPHASIS)
+
def render(content):
- return m.html(content)
+ return md.render(content)
return render
@@ -0,0 +1,49 @@
+from lxml.html import fromstring, tostring
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+from pygments.lexers import get_lexer_by_name
+from pygments.util import ClassNotFound
+
+class SyntaxHighlighter(object):
+ """Highlights syntax with pygments.
+
+ To highlight HTML, wrap the code in a ``<code lang="..."></code>`` tag.
+ The `lang` attribute specifies the language the code block is written in.
+
+ In Markdown, you can using fenced code blocks. The markdown filter will
+ automatically replace that with the correct HTML code.
+
+ Example markdown:
+
+ ```python
+ print("Here is sample python code")
+ ```
+
+ """
+
+ def __init__(self, context={}):
+ self.context = context
+ self.formatter = HtmlFormatter(linenos=False, cssclass="source")
+
+ def __call__(self, content):
+ fragment = fromstring(content)
+
+ for el in fragment.findall("..//code"):
+ lang = el.attrib.get('lang', None)
+ if lang is None: continue
+
+ highlighted = self._highlight(lang, tostring(el))
+
+ el.clear()
+ el.append(fromstring(highlighted))
+
+ return tostring(fragment)
+
+ def _highlight(self, language, code):
+ try:
+ lexar = get_lexer_by_name(language, strip_all=True)
+ except ClassNotFound:
+ # Fallback to the plain-text lexar
+ lexar = get_lexer_by_name('text', strip_all=True)
+
+ return highlight(code, lexar, self.formatter)
@@ -20,3 +20,17 @@ def test_markdown_render(self):
html = self.renderer.render(markdown)
self.assertEqual(html, "<h1>Test Header</h1>\n\n<p>This is test markdown. It <strong>should</strong> render in <em>HTML</em>.</p>\n")
+
+class SyntaxRendererTests(unittest.TestCase):
+ def setUp(self):
+ self.renderer = pypeline.filters.SyntaxRenderer()
+
+ def test_block_code_lang_set(self):
+ """Test to make sure that the proper code block is rendered (with lang)"""
+ expected = '\n<pre><code lang="python">code sample</code></pre>\n'
+ self.assertEqual(self.renderer.block_code("code sample", "python"), expected)
+
+ def test_block_code_lang_not_set(self):
+ """Test to make sure that the proper code block is rendered (without lang)"""
+ expected = '\n<pre><code>code sample</code></pre>\n'
+ self.assertEqual(self.renderer.block_code("code sample", None), expected)
@@ -0,0 +1,86 @@
+from lxml.html import fromstring
+
+from pypeline.filters import syntax_highlighter
+from pypeline.utils import unittest
+
+class SyntaxHighlighterTests(unittest.TestCase):
+ def setUp(self):
+ self.filter = syntax_highlighter()
+
+ def test_set_context(self):
+ """Tests to make sure the default context is set"""
+ self.assertEqual(self.filter.context, {})
+
+ def test_highlight_valid_lang(self):
+ """Tests to make sure syntax is highlighted with a valid lang"""
+ html = """\
+def test():
+ for i in range(10):
+ print(i)
+"""
+
+ highlighted = fromstring(self.filter._highlight('python', html))
+
+ self.assertTrue(highlighted.findall("..//pre"))
+ self.assertTrue(highlighted.findall("..//span"))
+
+ def test_highlight_invalid_lang(self):
+ """Tests to make sure syntax is highlighted with a invalid lang"""
+ html = """\
+def test():
+ for i in range(10):
+ print(i)
+"""
+
+ highlighted = fromstring(self.filter._highlight('invalid', html))
+
+ self.assertTrue(highlighted.findall(".//pre"))
+ self.assertFalse(highlighted.findall(".//code"))
+
+ def test_render_valid_lang(self):
+ """Tests to make sure syntax is rendered with a valid lang"""
+ html = """\
+<code lang="python">
+ def test():
+ for i in range(10):
+ print(i)
+</code>
+"""
+ highlighted = fromstring(self.filter(html))
+
+ self.assertEqual(len(highlighted.findall("..//div[@class='source']")), 1)
+ self.assertTrue(highlighted.findall("..//div"))
+ self.assertTrue(highlighted.findall("..//pre"))
+ self.assertTrue(highlighted.findall("..//span"))
+ self.assertTrue(highlighted.findall("..//code"))
+
+ def test_render_invalid_lang(self):
+ """Tests to make sure syntax is rendered with an invalid lang"""
+ html = """\
+<code lang="invalid">
+ def test():
+ for i in range(10):
+ print(i)
+</code>
+"""
+ highlighted = fromstring(self.filter(html))
+
+ self.assertEqual(len(highlighted.findall("..//div[@class='source']")), 1)
+ self.assertTrue(highlighted.findall("..//div"))
+ self.assertTrue(highlighted.findall("..//pre"))
+ self.assertTrue(highlighted.findall("..//code"))
+ self.assertFalse(highlighted.findall("..//span"))
+
+ def test_render_html_escape(self):
+ """Make sure tags are escaped"""
+ html = """\
+<code lang="html">
+ <script type="text/html">
+ alert("Hello world!");
+ </script>
+</code>
+"""
+
+ highlighted = fromstring(self.filter(html))
+
+ self.assertFalse(highlighted.findall("..//script"))
View
@@ -1,2 +1,3 @@
lxml
misaka==1.0.2
+pygments

0 comments on commit 969b306

Please sign in to comment.