- 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 = """