- RichTraceback() now accepts an optional traceback object to be used in place of sys.exc_info()[2]. html_error_template() and text_error_template() accept an optional render()-time argument "traceback" which is passed to the RichTraceback object. - lexer tests now rely upon an always-sorted dict repr()
diff --git a/CHANGES b/CHANGES index ba12855..29c999d 100644 --- a/CHANGES +++ b/CHANGES
@@ -47,6 +47,12 @@ - fixed the html_error_template not handling tracebacks from normal .py files with a magic encoding comment [ticket:88] +- RichTraceback() now accepts an optional traceback object + to be used in place of sys.exc_info()[2]. html_error_template() + and text_error_template() accept an optional + render()-time argument "traceback" which is passed to the + RichTraceback object. + - added ModuleTemplate class, which allows the construction of a Template given a Python module generated by a previous Template. This allows Python modules alone to be used
diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py index 3f4e3a7..1e78964 100644 --- a/lib/mako/exceptions.py +++ b/lib/mako/exceptions.py
@@ -64,10 +64,11 @@ traceback - a list of 4-tuples, in the same format as a regular python traceback, with template-corresponding traceback records replacing the originals reverse_traceback - the traceback list in reverse + """ - def __init__(self): + def __init__(self, traceback=None): (self.source, self.lineno) = ("", 0) - (t, self.error, self.records) = self._init() + (t, self.error, self.records) = self._init(traceback) if self.error is None: self.error = t if isinstance(self.error, CompileException) or isinstance(self.error, SyntaxException): @@ -90,14 +91,15 @@ reverse_traceback = property(lambda self:self._get_reformatted_records(self.reverse_records), doc=""" return the same data as traceback, except in reverse order """) - def _init(self): + def _init(self, trcback): """format a traceback from sys.exc_info() into 7-item tuples, containing the regular four traceback tuple items, plus the original template filename, the line number adjusted relative to the template source, and code line from that line number of the template.""" import mako.template mods = {} - (type, value, trcback) = sys.exc_info() + if not trcback: + (type, value, trcback) = sys.exc_info() rawrecords = traceback.extract_tb(trcback) new_trcback = [] for filename, lineno, function, line in rawrecords: @@ -160,11 +162,12 @@ source template, as applicable.""" import mako.template return mako.template.Template(r""" +<%page args="traceback=None"/> <%! from mako.exceptions import RichTraceback %>\ <% - tback = RichTraceback() + tback = RichTraceback(traceback=traceback) %>\ Traceback (most recent call last): % for (filename, lineno, function, line) in tback.traceback: @@ -189,7 +192,7 @@ <%! from mako.exceptions import RichTraceback %> -<%page args="full=True, css=True"/> +<%page args="full=True, css=True, traceback=None"/> % if full: <html> <head> @@ -214,7 +217,7 @@ <h2>Error !</h2> <% - tback = RichTraceback() + tback = RichTraceback(traceback=traceback) src = tback.source line = tback.lineno if src:
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index 84d4548..fa18eae 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py
@@ -43,7 +43,7 @@ return self.nodes def __repr__(self): - return "TemplateNode(%r, %r)" % (self.page_attributes, self.nodes) + return "TemplateNode(%s, %r)" % (util.sorted_dict_repr(self.page_attributes), self.nodes) class ControlLine(Node): """defines a control line, a line-oriented python line or end tag. @@ -293,9 +293,9 @@ return self.expression_undeclared_identifiers def __repr__(self): - return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, + return "%s(%r, %s, %r, %r)" % (self.__class__.__name__, self.keyword, - self.attributes, + util.sorted_dict_repr(self.attributes), (self.lineno, self.pos), [repr(x) for x in self.nodes] )
diff --git a/lib/mako/util.py b/lib/mako/util.py index 58a18a7..0141093 100644 --- a/lib/mako/util.py +++ b/lib/mako/util.py
@@ -180,6 +180,16 @@ finally: fp.seek(pos) +def sorted_dict_repr(d): + """repr() a dictionary with the keys in order. + + Used by the lexer unit test to compare parse trees based on strings. + + """ + keys = d.keys() + keys.sort() + return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}" + def restore__ast(_ast): """Attempt to restore the required classes to the _ast module if it appears to be missing them
diff --git a/test/lexer.py b/test/lexer.py index ce990f2..fa5ecef 100644 --- a/test/lexer.py +++ b/test/lexer.py
@@ -129,7 +129,7 @@ <%self:go x="1" y="2" z="${'hi' + ' ' + 'there'}"/> """ nodes = Lexer(template).parse() - assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'y': u'2', u'x': u'1', u'z': u"${'hi' + ' ' + 'there'}"}, (3, 13), []), Text(u'\n ', (3, 64))])""" + assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'2', u'z': u"${'hi' + ' ' + 'there'}"}, (3, 13), []), Text(u'\n ', (3, 64))])""" def test_ns_tag_open(self): template = """ @@ -139,7 +139,7 @@ </%self:go> """ nodes = Lexer(template).parse() - assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'y': u'${process()}', u'x': u'1'}, (3, 13), ["Text(u'\\n this is the body\\n ', (3, 46))"]), Text(u'\n ', (5, 24))])""" + assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'${process()}'}, (3, 13), ["Text(u'\\n this is the body\\n ', (3, 46))"]), Text(u'\n ', (5, 24))])""" def test_expr_in_attribute(self): """test some slightly trickier expressions. @@ -160,7 +160,7 @@ some template """ nodes = Lexer(template).parse() - assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), PageTag(u'page', {u'cached': u'True', u'args': u'a, b'}, (2, 13), []), Text(u'\n \n some template\n ', (2, 48))])""" + assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), PageTag(u'page', {u'args': u'a, b', u'cached': u'True'}, (2, 13), []), Text(u'\n \n some template\n ', (2, 48))])""" def test_nesting(self): template = """ @@ -397,15 +397,8 @@ </table> """ nodes = Lexer(template).parse() - expected = r"""TemplateNode({}, [NamespaceTag(u'namespace', {u'name': u'foo', u'file': u'somefile.html'}, (1, 1), []), Text(u'\n', (1, 46)), Comment(u'inherit from foobar.html', (2, 1)), InheritTag(u'inherit', {u'file': u'foobar.html'}, (3, 1), []), Text(u'\n\n', (3, 31)), DefTag(u'def', {u'name': u'header()'}, (5, 1), ["Text(u'\\n <div>header</div>\\n', (5, 23))"]), Text(u'\n', (7, 8)), DefTag(u'def', {u'name': u'footer()'}, (8, 1), ["Text(u'\\n <div> footer</div>\\n', (8, 23))"]), Text(u'\n\n<table>\n', (10, 8)), ControlLine(u'for', u'for j in data():', False, (13, 1)), Text(u' <tr>\n', (14, 1)), ControlLine(u'for', u'for x in j:', False, (15, 1)), Text(u' <td>Hello ', (16, 1)), Expression(u'x', ['h'], (16, 23)), Text(u'</td>\n', (16, 30)), ControlLine(u'for', u'endfor', True, (17, 1)), Text(u' </tr>\n', (18, 1)), ControlLine(u'for', u'endfor', True, (19, 1)), Text(u'</table>\n', (20, 1))])""" - result = repr(nodes) - # Don't assume dict ordering. Annoying - assert len(result) == len(expected) - start = expected.find("{u'name':") - end = expected.find("somefile.html'},") - assert expected[:start] == result[:start] - assert expected[end:] == result[end:] - assert result[start:end] in ({u'name': u'foo', u'file': u'somefile.html'}, {u'file': u'somefile.html', u'name': u'foo'}) + expected = r"""TemplateNode({}, [NamespaceTag(u'namespace', {u'file': u'somefile.html', u'name': u'foo'}, (1, 1), []), Text(u'\n', (1, 46)), Comment(u'inherit from foobar.html', (2, 1)), InheritTag(u'inherit', {u'file': u'foobar.html'}, (3, 1), []), Text(u'\n\n', (3, 31)), DefTag(u'def', {u'name': u'header()'}, (5, 1), ["Text(u'\\n <div>header</div>\\n', (5, 23))"]), Text(u'\n', (7, 8)), DefTag(u'def', {u'name': u'footer()'}, (8, 1), ["Text(u'\\n <div> footer</div>\\n', (8, 23))"]), Text(u'\n\n<table>\n', (10, 8)), ControlLine(u'for', u'for j in data():', False, (13, 1)), Text(u' <tr>\n', (14, 1)), ControlLine(u'for', u'for x in j:', False, (15, 1)), Text(u' <td>Hello ', (16, 1)), Expression(u'x', ['h'], (16, 23)), Text(u'</td>\n', (16, 30)), ControlLine(u'for', u'endfor', True, (17, 1)), Text(u' </tr>\n', (18, 1)), ControlLine(u'for', u'endfor', True, (19, 1)), Text(u'</table>\n', (20, 1))])""" + assert repr(nodes) == expected def test_comment_after_statement(self): template = """