From cd7fdd8c918f1ccccd62910f2bb1837b7265e19d Mon Sep 17 00:00:00 2001
From: Philip Jenvey <pjenvey@underboss.org>
Date: Fri, 22 Jan 2010 01:54:38 +0000
Subject: [PATCH] Further fixes to unicode handling of .py files with the
 html_error_template. fixes #88

---
 CHANGES                |  3 +++
 lib/mako/exceptions.py | 32 ++++++++++++++++++++++++++++++--
 test/exceptions_.py    |  9 +++++++++
 3 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/CHANGES b/CHANGES
index b9de2ad..46aa4da 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,9 @@
 - Support the <%namespacename:defname> syntax in
   the babel extractor. [ticket:118]
 
+- Further fixes to unicode handling of .py files with the
+  html_error_template. [ticket:88]
+
 0.2.5
 - Added a "decorator" kw argument to <%def>,
   allows custom decoration functions to wrap
diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py
index 444f004..aa529d7 100644
--- a/lib/mako/exceptions.py
+++ b/lib/mako/exceptions.py
@@ -53,6 +53,7 @@ class RichTraceback(object):
     Properties:
     
     error - the exception instance.  
+    message - the exception error message as unicode
     source - source code of the file where the error occured.  if the error occured within a compiled template,
     this is the template source.
     lineno - line number where the error occured.  if the error occured within a compiled template, the line number
@@ -78,6 +79,22 @@ class RichTraceback(object):
             self._has_source = True
         self.reverse_records = [r for r in self.records]
         self.reverse_records.reverse()
+        self.init_message()
+
+    def init_message(self):
+        """Find a unicode representation of self.error"""
+        try:
+            self.message = unicode(self.error)
+        except UnicodeError:
+            try:
+                self.message = str(self.error)
+            except UnicodeEncodeError:
+                # Fallback to args as neither unicode nor
+                # str(Exception(u'\xe6')) work in Python < 2.6
+                self.message = self.error.args[0]
+        if not isinstance(self.message, unicode):
+            self.message = unicode(self.message, 'ascii', 'replace')
+
     def _get_reformatted_records(self, records):
         for rec in records:
             if rec[6] is not None:
@@ -112,6 +129,17 @@ class RichTraceback(object):
                     template_source = info.source
                     template_filename = info.template_filename or filename
                 except KeyError:
+                    # A normal .py file (not a Template)
+                    try:
+                        fp = open(filename)
+                        encoding = util.parse_encoding(fp)
+                        fp.close()
+                    except IOError:
+                        encoding = None
+                    if encoding:
+                        line = line.decode(encoding)
+                    else:
+                        line = line.decode('ascii', 'replace')
                     new_trcback.append((filename, lineno, function, line, None, None, None, None))
                     continue
 
@@ -174,7 +202,7 @@ Traceback (most recent call last):
   File "${filename}", line ${lineno}, in ${function or '?'}
     ${line | unicode.strip}
 % endfor
-${str(tback.error.__class__.__name__)}: ${str(tback.error)}
+${str(tback.error.__class__.__name__)}: ${tback.message}
 """)
 
 def html_error_template():
@@ -225,7 +253,7 @@ def html_error_template():
     else:
         lines = None
 %>
-<h3>${str(tback.error.__class__.__name__)}: ${str(tback.error)}</h3>
+<h3>${str(tback.error.__class__.__name__)}: ${tback.message}</h3>
 
 % if lines:
     <div class="sample">
diff --git a/test/exceptions_.py b/test/exceptions_.py
index 43059f5..2cb5d1d 100644
--- a/test/exceptions_.py
+++ b/test/exceptions_.py
@@ -69,6 +69,15 @@ ${u'привет'}
             assert 'RuntimeError: test' in html_error
             assert "foo = u'&#x65E5;&#x672C;'" in html_error
 
+
+    def test_py_unicode_error_html_error_template(self):
+        try:
+            raise RuntimeError(u'日本')
+        except:
+            html_error = exceptions.html_error_template().render()
+            assert 'RuntimeError: &#x65E5;&#x672C;' in html_error
+            assert "RuntimeError(u'&#x65E5;&#x672C;')" in html_error
+
     def test_format_exceptions(self):
         l = TemplateLookup(format_exceptions=True)
 
-- 
GitLab