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 ${'привет'}".encode(sys.getdefaultencoding(), + try: + import pygments + assert u"".encode(sys.getdefaultencoding(), + 'htmlentityreplace') in html_error + except ImportError: + assert u"3 ${'привет'}".encode(sys.getdefaultencoding(), 'htmlentityreplace') in html_error else: - assert u"3 ${u'привет'}".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''\ + 'привет'\ + ''</span><span class="cp">}</span>'.encode( + sys.getdefaultencoding(), + 'htmlentityreplace') in html_error + except ImportError: + assert u"3 ${u'привет'}".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">${'привет' + 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">'привет'</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">${'привет' + foobar}</div>'\ + in result_lines(l.get_template("foo.html").render().decode('utf-8')) else: - assert '<div class="highlight">2 ${u'пр'\ - 'ивет' + 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'прив'\ + 'ет'</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'пр'\ + 'ивет' + foobar}</div>' \ + in result_lines(l.get_template("foo.html").render().decode('utf-8')) def test_custom_tback(self): -- GitLab