From 10228fad9797a85a879286eb51ec4ea287225c97 Mon Sep 17 00:00:00 2001
From: Mike Bayer <mike_mp@zzzcomputing.com>
Date: Mon, 12 Mar 2012 14:22:46 -0400
Subject: [PATCH] - [feature] The html_error_template() will now   apply
 Pygments highlighting to the source   code displayed in the traceback, if
 Pygments   if available.  Courtesy Ben Trofatter   [ticket:95]

---
 CHANGES                   |  6 ++++
 mako/exceptions.py        | 51 +++++++++++++++++++++++++++---
 mako/ext/pygmentplugin.py | 33 ++++++++++++--------
 test/test_exceptions.py   | 65 ++++++++++++++++++++++++++++++++++-----
 4 files changed, 131 insertions(+), 24 deletions(-)

diff --git a/CHANGES b/CHANGES
index c84fb92..b07a295 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,10 @@
 0.6.3
+- [feature] The html_error_template() will now
+  apply Pygments highlighting to the source
+  code displayed in the traceback, if Pygments 
+  if available.  Courtesy Ben Trofatter 
+  [ticket:95]
+
 - [feature] Added support for context managers,
   i.e. "% with x as e:/ % endwith" support.
   Courtesy Ben Trofatter [ticket:147]
diff --git a/mako/exceptions.py b/mako/exceptions.py
index bce99b7..c9e04ea 100644
--- a/mako/exceptions.py
+++ b/mako/exceptions.py
@@ -227,6 +227,15 @@ Traceback (most recent call last):
 ${tback.errorname}: ${tback.message}
 """)
 
+
+try:
+    from mako.ext.pygmentplugin import syntax_highlight, pygments_html_formatter
+except ImportError:
+    from mako.filters import html_escape
+    pygments_html_formatter = None
+    def syntax_highlight(filename='', language=None):
+        return html_escape
+
 def html_error_template():
     """Provides a template that renders a stack trace in an HTML format,
     providing an excerpt of code as well as substituting source template
@@ -242,7 +251,7 @@ def html_error_template():
     import mako.template
     return mako.template.Template(r"""
 <%!
-    from mako.exceptions import RichTraceback
+    from mako.exceptions import RichTraceback, syntax_highlight, pygments_html_formatter
 %>
 <%page args="full=True, css=True, error=None, traceback=None"/>
 % if full:
@@ -262,6 +271,21 @@ def html_error_template():
         .location { font-size:80%; }
         .highlight { white-space:pre; }
         .sampleline { white-space:pre; }
+
+    % if pygments_html_formatter:
+        ${pygments_html_formatter.get_style_defs()}
+        .linenos { min-width: 2.5em; text-align: right; }
+        pre { margin: 0; }
+        .syntax-highlighted { padding: 0 10px; }
+        .syntax-highlightedtable { border-spacing: 1px; }
+        .nonhighlight { border-top: 1px solid #DFDFDF; border-bottom: 1px solid #DFDFDF; }
+        .stacktrace .nonhighlight { margin: 5px 15px 10px; }
+        .sourceline { margin: 0 0; font-family:monospace; }
+        .code { background-color: #F8F8F8; width: 100%; }
+        .error .code { background-color: #FFBDBD; }
+        .error .syntax-highlighted { background-color: #FFBDBD; }
+    % endif
+
     </style>
 % endif
 % if full:
@@ -285,10 +309,23 @@ def html_error_template():
     <div class="sample">
     <div class="nonhighlight">
 % for index in range(max(0, line-4),min(len(lines), line+5)):
+    <%
+       if pygments_html_formatter:
+           pygments_html_formatter.linenostart = index + 1
+    %>
     % if index + 1 == line:
-<div class="highlight">${index + 1} ${lines[index] | h}</div>
+    <%
+       if pygments_html_formatter:
+           old_cssclass = pygments_html_formatter.cssclass
+           pygments_html_formatter.cssclass = 'error ' + old_cssclass
+    %>
+        ${lines[index] | syntax_highlight(language='mako')}
+    <%
+       if pygments_html_formatter:
+           pygments_html_formatter.cssclass = old_cssclass
+    %>
     % else:
-<div class="sampleline">${index + 1} ${lines[index] | h}</div>
+        ${lines[index] | syntax_highlight(language='mako')}
     % endif
 % endfor
     </div>
@@ -298,7 +335,13 @@ def html_error_template():
 <div class="stacktrace">
 % for (filename, lineno, function, line) in tback.reverse_traceback:
     <div class="location">${filename}, line ${lineno}:</div>
-    <div class="sourceline">${line | h}</div>
+    <div class="nonhighlight">
+    <%
+       if pygments_html_formatter:
+           pygments_html_formatter.linenostart = lineno
+    %>
+      <div class="sourceline">${line | syntax_highlight(filename)}</div>
+    </div>
 % endfor
 </div>
 
diff --git a/mako/ext/pygmentplugin.py b/mako/ext/pygmentplugin.py
index 0b15126..98e0c5d 100644
--- a/mako/ext/pygmentplugin.py
+++ b/mako/ext/pygmentplugin.py
@@ -4,20 +4,16 @@
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
-import re
-try:
-    set
-except NameError:
-    from sets import Set as set
-
 from pygments.lexers.web import \
      HtmlLexer, XmlLexer, JavascriptLexer, CssLexer
-from pygments.lexers.agile import PythonLexer
-from pygments.lexer import Lexer, DelegatingLexer, RegexLexer, bygroups, \
-     include, using, this
-from pygments.token import Error, Punctuation, \
-     Text, Comment, Operator, Keyword, Name, String, Number, Other, Literal
-from pygments.util import html_doctype_matches, looks_like_xml
+from pygments.lexers.agile import PythonLexer, Python3Lexer
+from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, \
+     include, using
+from pygments.token import \
+     Text, Comment, Operator, Keyword, Name, String, Other
+from pygments.formatters.html import HtmlFormatter
+from pygments import highlight
+from mako import util
 
 class MakoLexer(RegexLexer):
     name = 'Mako'
@@ -105,3 +101,16 @@ class MakoCssLexer(DelegatingLexer):
     def __init__(self, **options):
         super(MakoCssLexer, self).__init__(CssLexer, MakoLexer,
                                              **options)
+
+
+pygments_html_formatter = HtmlFormatter(cssclass='syntax-highlighted', linenos=True)
+def syntax_highlight(filename='', language=None):
+    mako_lexer = MakoLexer()
+    if util.py3k:
+        python_lexer = Python3Lexer()
+    else:
+        python_lexer = PythonLexer()
+    if filename.startswith('memory:') or language == 'mako':
+        return lambda string: highlight(string, mako_lexer, pygments_html_formatter)
+    return lambda string: highlight(string, python_lexer, pygments_html_formatter)
+
diff --git a/test/test_exceptions.py b/test/test_exceptions.py
index 97987e6..ea27dda 100644
--- a/test/test_exceptions.py
+++ b/test/test_exceptions.py
@@ -87,10 +87,25 @@ ${u'привет'}
                         html_error
  
             if util.py3k:
-                assert u"3 ${&#39;привет&#39;}".encode(sys.getdefaultencoding(),
+                try:
+                    import pygments
+                    assert u"".encode(sys.getdefaultencoding(),
+                                            'htmlentityreplace') in html_error
+                except ImportError:
+                    assert u"3 ${&#39;привет&#39;}".encode(sys.getdefaultencoding(),
                                             'htmlentityreplace') in html_error
             else:
-                assert u"3 ${u&#39;привет&#39;}".encode(sys.getdefaultencoding(),
+                try:
+                    import pygments
+                    assert u'<pre>3</pre></div></td><td class="code">'\
+                            '<div class="syntax-highlighted"><pre><span '\
+                            'class="cp">${</span><span class="s">u&#39;'\
+                            '&#x43F;&#x440;&#x438;&#x432;&#x435;&#x442;'\
+                            '&#39;</span><span class="cp">}</span>'.encode(
+                                    sys.getdefaultencoding(),
+                                    'htmlentityreplace') in html_error
+                except ImportError:
+                    assert u"3 ${u&#39;привет&#39;}".encode(sys.getdefaultencoding(),
                                             'htmlentityreplace') in html_error
         else:
             assert False, ("This function should trigger a CompileException, "
@@ -138,7 +153,16 @@ ${foobar}
         ${self.body()}
         """)
 
-        assert '<div class="sourceline">${foobar}</div>' in \
+        try:
+            import pygments
+            assert '<div class="sourceline"><table class="syntax-highlightedtable">'\
+                    '<tr><td class="linenos"><div class="linenodiv"><pre>3</pre>'\
+                    '</div></td><td class="code"><div class="syntax-highlighted">'\
+                    '<pre><span class="err">$</span><span class="p">{</span>'\
+                    '<span class="n">foobar</span><span class="p">}</span>' in \
+                result_lines(l.get_template("foo.html").render_unicode())
+        except ImportError:
+            assert '<div class="sourceline">${foobar}</div>' in \
                 result_lines(l.get_template("foo.html").render_unicode())
  
     def test_utf8_format_exceptions(self):
@@ -152,12 +176,37 @@ ${foobar}
             l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${u'привет' + foobar}""")
 
         if util.py3k:
-            assert u'<div class="sourceline">${&#39;привет&#39; + foobar}</div>'\
-                in result_lines(l.get_template("foo.html").render().decode('utf-8'))
+            try:
+                import pygments
+                assert '<table class="error syntax-highlightedtable"><tr><td '\
+                        'class="linenos"><div class="linenodiv"><pre>2</pre>'\
+                        '</div></td><td class="code"><div class="error '\
+                        'syntax-highlighted"><pre><span class="cp">${</span>'\
+                        '<span class="s">&#39;привет&#39;</span> <span class="o">+</span> '\
+                        '<span class="n">foobar</span><span class="cp">}</span>'\
+                        '<span class="x"></span>' in \
+                    result_lines(l.get_template("foo.html").render().decode('utf-8'))
+            except ImportError:
+                assert u'<div class="sourceline">${&#39;привет&#39; + foobar}</div>'\
+                    in result_lines(l.get_template("foo.html").render().decode('utf-8'))
         else:
-            assert '<div class="highlight">2 ${u&#39;&#x43F;&#x440;'\
-                    '&#x438;&#x432;&#x435;&#x442;&#39; + foobar}</div>' \
-                in result_lines(l.get_template("foo.html").render().decode('utf-8'))
+            try:
+                import pygments
+
+                assert '<table class="error syntax-highlightedtable"><tr><td '\
+                        'class="linenos"><div class="linenodiv"><pre>2</pre>'\
+                        '</div></td><td class="code"><div class="error '\
+                        'syntax-highlighted"><pre><span class="cp">${</span>'\
+                        '<span class="s">u&#39;&#x43F;&#x440;&#x438;&#x432;'\
+                        '&#x435;&#x442;&#39;</span> <span class="o">+</span> '\
+                        '<span class="n">foobar</span><span class="cp">}</span>'\
+                        '<span class="x"></span>' in \
+                    result_lines(l.get_template("foo.html").render().decode('utf-8'))
+
+            except ImportError:
+                assert '<div class="highlight">2 ${u&#39;&#x43F;&#x440;'\
+                        '&#x438;&#x432;&#x435;&#x442;&#39; + foobar}</div>' \
+                    in result_lines(l.get_template("foo.html").render().decode('utf-8'))
  
  
     def test_custom_tback(self):
-- 
GitLab