aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES14
-rw-r--r--mako/ast.py2
-rw-r--r--mako/codegen.py8
-rw-r--r--mako/exceptions.py51
-rw-r--r--mako/ext/pygmentplugin.py33
-rw-r--r--mako/parsetree.py2
-rw-r--r--mako/pygen.py2
-rw-r--r--mako/runtime.py6
-rw-r--r--test/test_ast.py16
-rw-r--r--test/test_call.py11
-rw-r--r--test/test_exceptions.py65
11 files changed, 179 insertions, 31 deletions
diff --git a/CHANGES b/CHANGES
index 4783699..b07a295 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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 ${&#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):