diff options
| -rw-r--r-- | CHANGES | 14 | ||||
| -rw-r--r-- | mako/ast.py | 2 | ||||
| -rw-r--r-- | mako/codegen.py | 8 | ||||
| -rw-r--r-- | mako/exceptions.py | 51 | ||||
| -rw-r--r-- | mako/ext/pygmentplugin.py | 33 | ||||
| -rw-r--r-- | mako/parsetree.py | 2 | ||||
| -rw-r--r-- | mako/pygen.py | 2 | ||||
| -rw-r--r-- | mako/runtime.py | 6 | ||||
| -rw-r--r-- | test/test_ast.py | 16 | ||||
| -rw-r--r-- | test/test_call.py | 11 | ||||
| -rw-r--r-- | test/test_exceptions.py | 65 |
11 files changed, 179 insertions, 31 deletions
@@ -1,8 +1,22 @@ 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] + - [bug] Fixed some Py3K resource warnings due to filehandles being implicitly closed. [ticket:182] +- [bug] Fixed endless recursion bug when + nesting multiple def-calls with content. + Thanks to Jeff Dairiki. [ticket:186] + 0.6.2 - [bug] The ${{"foo":"bar"}} parsing issue is fixed!! The legendary Eevee has slain the dragon! diff --git a/mako/ast.py b/mako/ast.py index 515d7b8..f2f09d6 100644 --- a/mako/ast.py +++ b/mako/ast.py @@ -83,6 +83,8 @@ class PythonFragment(PythonCode): code = "if False:pass\n" + code + "pass" elif keyword == 'except': code = "try:pass\n" + code + "pass" + elif keyword == 'with': + code = code + "pass" else: raise exceptions.CompileException( "Unsupported control keyword: '%s'" % diff --git a/mako/codegen.py b/mako/codegen.py index 2e15124..704330c 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -231,7 +231,8 @@ class _GenerateRenderMethod(object): self.printer.writelines( "def %s(%s):" % (name, ','.join(args)), - "context.caller_stack._push_frame()", + # push new frame, assign current frame to __M_caller + "__M_caller = context.caller_stack._push_frame()", "try:" ) if buffered or filtered or cached: @@ -516,7 +517,8 @@ class _GenerateRenderMethod(object): buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) self.printer.writelines( - "context.caller_stack._push_frame()", + # push new frame, assign current frame to __M_caller + "__M_caller = context.caller_stack._push_frame()", "try:" ) if buffered or filtered or cached: @@ -848,8 +850,6 @@ class _GenerateRenderMethod(object): ) self.printer.writelines( - # get local reference to current caller, if any - "__M_caller = context.caller_stack._get_caller()", # push on caller for nested call "context.caller_stack.nextcaller = " "runtime.Namespace('caller', context, callables=ccall(__M_caller))", 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/mako/parsetree.py b/mako/parsetree.py index 8aa95d4..52bd156 100644 --- a/mako/parsetree.py +++ b/mako/parsetree.py @@ -64,7 +64,7 @@ class ControlLine(Node): self.text = text self.keyword = keyword self.isend = isend - self.is_primary = keyword in ['for','if', 'while', 'try'] + self.is_primary = keyword in ['for','if', 'while', 'try', 'with'] if self.isend: self._declared_identifiers = [] self._undeclared_identifiers = [] diff --git a/mako/pygen.py b/mako/pygen.py index e38b383..b50e60e 100644 --- a/mako/pygen.py +++ b/mako/pygen.py @@ -108,7 +108,7 @@ class PythonPrinter(object): # keep track of what the keyword was that indented us, # if it is a python compound statement keyword # where we might have to look for an "unindent" keyword - match = re.match(r"^\s*(if|try|elif|while|for)", line) + match = re.match(r"^\s*(if|try|elif|while|for|with)", line) if match: # its a "compound" keyword, so we will check for "unindentors" indentor = match.group(1) diff --git a/mako/runtime.py b/mako/runtime.py index 65c03e1..b56fa67 100644 --- a/mako/runtime.py +++ b/mako/runtime.py @@ -158,12 +158,16 @@ class CallerStack(list): def __nonzero__(self): return self._get_caller() and True or False def _get_caller(self): + # this method can be removed once + # codegen MAGIC_NUMBER moves past 7 return self[-1] def __getattr__(self, key): return getattr(self._get_caller(), key) def _push_frame(self): - self.append(self.nextcaller or None) + frame = self.nextcaller or None + self.append(frame) self.nextcaller = None + return frame def _pop_frame(self): self.nextcaller = self.pop() diff --git a/test/test_ast.py b/test/test_ast.py index adea08a..60ad6ec 100644 --- a/test/test_ast.py +++ b/test/test_ast.py @@ -189,6 +189,22 @@ def x(q): eq_(parsed.declared_identifiers, set(['x'])) eq_(parsed.undeclared_identifiers, set()) + def test_locate_identifiers_12(self): + code = """ +class ContextManager(object): + def __enter__(self): + return 1 + def __exit__(self, exc_type, exc_value, traceback): + pass + +with ContextManager() as x, ContextManager(): + print x +""" + parsed = ast.PythonCode(code, **exception_kwargs) + eq_(parsed.declared_identifiers, set(['ContextManager', 'x'])) + eq_(parsed.undeclared_identifiers, set()) + + def test_no_global_imports(self): code = """ from foo import * diff --git a/test/test_call.py b/test/test_call.py index 5f13e95..0bb6079 100644 --- a/test/test_call.py +++ b/test/test_call.py @@ -385,6 +385,17 @@ class CallTest(TemplateTest): """) assert result_lines(t.render()) == ['this is a', 'this is b', 'this is c:', "this is the body in b's call"] + def test_composed_def(self): + t = Template(""" + <%def name="f()"><f>${caller.body()}</f></%def> + <%def name="g()"><g>${caller.body()}</g></%def> + <%def name="fg()"> + <%self:f><%self:g>${caller.body()}</%self:g></%self:f> + </%def> + <%self:fg>fgbody</%self:fg> + """) + assert result_lines(t.render()) == ['<f><g>fgbody</g></f>'] + def test_regular_defs(self): t = Template(""" <%! 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): |
