2 # Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
4 # This module is part of Mako and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
7 """exception classes"""
9 import traceback, sys, re
12 class MakoException(Exception):
15 class RuntimeException(MakoException):
18 def _format_filepos(lineno, pos, filename):
20 return " at line: %d char: %d" % (lineno, pos)
22 return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)
23 class CompileException(MakoException):
24 def __init__(self, message, source, lineno, pos, filename):
25 MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
28 self.filename = filename
31 class SyntaxException(MakoException):
32 def __init__(self, message, source, lineno, pos, filename):
33 MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
36 self.filename = filename
39 class TemplateLookupException(MakoException):
42 class TopLevelLookupException(TemplateLookupException):
45 class RichTraceback(object):
46 """pulls the current exception from the sys traceback and extracts Mako-specific
55 error - the exception instance.
56 source - source code of the file where the error occured. if the error occured within a compiled template,
57 this is the template source.
58 lineno - line number where the error occured. if the error occured within a compiled template, the line number
59 is adjusted to that of the template source
60 records - a list of 8-tuples containing the original python traceback elements, plus the
61 filename, line number, source line, and full template source for the traceline mapped back to its originating source
62 template, if any for that traceline (else the fields are None).
63 reverse_records - the list of records in reverse
64 traceback - a list of 4-tuples, in the same format as a regular python traceback, with template-corresponding
65 traceback records replacing the originals
66 reverse_traceback - the traceback list in reverse
69 def __init__(self, traceback=None):
70 (self.source, self.lineno) = ("", 0)
71 (t, self.error, self.records) = self._init(traceback)
72 if self.error is None:
74 if isinstance(self.error, CompileException) or isinstance(self.error, SyntaxException):
76 self.source = self.error.source
77 self.lineno = self.error.lineno
78 self._has_source = True
79 self.reverse_records = [r for r in self.records]
80 self.reverse_records.reverse()
81 def _get_reformatted_records(self, records):
83 if rec[6] is not None:
84 yield (rec[4], rec[5], rec[2], rec[6])
87 traceback = property(lambda self:self._get_reformatted_records(self.records), doc="""
88 return a list of 4-tuple traceback records (i.e. normal python format)
89 with template-corresponding lines remapped to the originating template
91 reverse_traceback = property(lambda self:self._get_reformatted_records(self.reverse_records), doc="""
92 return the same data as traceback, except in reverse order
94 def _init(self, trcback):
95 """format a traceback from sys.exc_info() into 7-item tuples, containing
96 the regular four traceback tuple items, plus the original template
97 filename, the line number adjusted relative to the template source, and
98 code line from that line number of the template."""
102 (type, value, trcback) = sys.exc_info()
103 rawrecords = traceback.extract_tb(trcback)
105 for filename, lineno, function, line in rawrecords:
107 (line_map, template_lines) = mods[filename]
110 info = mako.template._get_module_info(filename)
111 module_source = info.code
112 template_source = info.source
113 template_filename = info.template_filename or filename
115 new_trcback.append((filename, lineno, function, line, None, None, None, None))
118 template_ln = module_ln = 1
120 for line in module_source.split("\n"):
121 match = re.match(r'\s*# SOURCE LINE (\d+)', line)
123 template_ln = int(match.group(1))
127 line_map[module_ln] = template_ln
128 template_lines = [line for line in template_source.split("\n")]
129 mods[filename] = (line_map, template_lines)
131 template_ln = line_map[lineno]
132 if template_ln <= len(template_lines):
133 template_line = template_lines[template_ln - 1]
136 new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source))
138 for l in range(len(new_trcback)-1, 0, -1):
139 if new_trcback[l][5]:
140 self.source = new_trcback[l][7]
141 self.lineno = new_trcback[l][5]
145 # A normal .py file (not a Template)
146 fp = open(new_trcback[-1][0])
147 encoding = util.parse_encoding(fp)
149 self.source = fp.read()
152 self.source = self.source.decode(encoding)
155 self.lineno = new_trcback[-1][1]
156 return (type, value, new_trcback)
159 def text_error_template(lookup=None):
160 """provides a template that renders a stack trace in a similar format to the Python interpreter,
161 substituting source template filenames, line numbers and code for that of the originating
162 source template, as applicable."""
164 return mako.template.Template(r"""
165 <%page args="traceback=None"/>
167 from mako.exceptions import RichTraceback
170 tback = RichTraceback(traceback=traceback)
172 Traceback (most recent call last):
173 % for (filename, lineno, function, line) in tback.traceback:
174 File "${filename}", line ${lineno}, in ${function or '?'}
175 ${line | unicode.strip}
177 ${str(tback.error.__class__.__name__)}: ${str(tback.error)}
180 def html_error_template():
181 """provides a template that renders a stack trace in an HTML format, providing an excerpt of
182 code as well as substituting source template filenames, line numbers and code
183 for that of the originating source template, as applicable.
185 the template's default encoding_errors value is 'htmlentityreplace'. the template has
188 with the full option disabled, only a section of an HTML document is returned.
189 with the css option disabled, the default stylesheet won't be included."""
191 return mako.template.Template(r"""
193 from mako.exceptions import RichTraceback
195 <%page args="full=True, css=True, traceback=None"/>
199 <title>Mako Runtime Error</title>
203 body { font-family:verdana; margin:10px 30px 10px 30px;}
204 .stacktrace { margin:5px 5px 5px 5px; }
205 .highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; }
206 .nonhighlight { padding:0px; background-color:#DFDFDF; }
207 .sample { padding:10px; margin:10px 10px 10px 10px; font-family:monospace; }
208 .sampleline { padding:0px 10px 0px 10px; }
209 .sourceline { margin:5px 5px 10px 5px; font-family:monospace;}
210 .location { font-size:80%; }
220 tback = RichTraceback(traceback=traceback)
224 lines = src.split('\n')
228 <h3>${str(tback.error.__class__.__name__)}: ${str(tback.error)}</h3>
232 <div class="nonhighlight">
233 % for index in range(max(0, line-4),min(len(lines), line+5)):
234 % if index + 1 == line:
235 <div class="highlight">${index + 1} ${lines[index] | h}</div>
237 <div class="sampleline">${index + 1} ${lines[index] | h}</div>
244 <div class="stacktrace">
245 % for (filename, lineno, function, line) in tback.reverse_traceback:
246 <div class="location">${filename}, line ${lineno}:</div>
247 <div class="sourceline">${line | h}</div>
255 """, output_encoding=sys.getdefaultencoding(), encoding_errors='htmlentityreplace')