2
# Copyright (C) 2006, 2007 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
11
class MakoException(Exception):
14
class RuntimeException(MakoException):
17
def _format_filepos(lineno, pos, filename):
19
return " at line: %d char: %d" % (lineno, pos)
21
return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)
22
class CompileException(MakoException):
23
def __init__(self, message, source, lineno, pos, filename):
24
MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
27
self.filename = filename
30
class SyntaxException(MakoException):
31
def __init__(self, message, source, lineno, pos, filename):
32
MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
35
self.filename = filename
38
class TemplateLookupException(MakoException):
41
class TopLevelLookupException(TemplateLookupException):
44
class RichTraceback(object):
45
"""pulls the current exception from the sys traceback and extracts Mako-specific
54
error - the exception instance.
55
source - source code of the file where the error occured. if the error occured within a compiled template,
56
this is the template source.
57
lineno - line number where the error occured. if the error occured within a compiled template, the line number
58
is adjusted to that of the template source
59
records - a list of 8-tuples containing the original python traceback elements, plus the
60
filename, line number, source line, and full template source for the traceline mapped back to its originating source
61
template, if any for that traceline (else the fields are None).
62
reverse_records - the list of records in reverse
63
traceback - a list of 4-tuples, in the same format as a regular python traceback, with template-corresponding
64
traceback records replacing the originals
65
reverse_traceback - the traceback list in reverse
68
(self.source, self.lineno) = ("", 0)
69
(t, self.error, self.records) = self._init()
70
if self.error is None:
72
if isinstance(self.error, CompileException) or isinstance(self.error, SyntaxException):
74
self.source = self.error.source
75
self.lineno = self.error.lineno
76
self._has_source = True
77
self.reverse_records = [r for r in self.records]
78
self.reverse_records.reverse()
79
def _get_reformatted_records(self, records):
81
if rec[6] is not None:
82
yield (rec[4], rec[5], rec[2], rec[6])
85
traceback = property(lambda self:self._get_reformatted_records(self.records), doc="""
86
return a list of 4-tuple traceback records (i.e. normal python format)
87
with template-corresponding lines remapped to the originating template
89
reverse_traceback = property(lambda self:self._get_reformatted_records(self.reverse_records), doc="""
90
return the same data as traceback, except in reverse order
93
"""format a traceback from sys.exc_info() into 7-item tuples, containing
94
the regular four traceback tuple items, plus the original template
95
filename, the line number adjusted relative to the template source, and
96
code line from that line number of the template."""
99
(type, value, trcback) = sys.exc_info()
100
rawrecords = traceback.extract_tb(trcback)
102
for filename, lineno, function, line in rawrecords:
104
(line_map, template_lines) = mods[filename]
107
info = mako.template._get_module_info(filename)
108
module_source = info.code
109
template_source = info.source
110
template_filename = info.template_filename or filename
112
new_trcback.append((filename, lineno, function, line, None, None, None, None))
115
template_ln = module_ln = 1
117
for line in module_source.split("\n"):
118
match = re.match(r'\s*# SOURCE LINE (\d+)', line)
120
template_ln = int(match.group(1))
124
line_map[module_ln] = template_ln
125
template_lines = [line for line in template_source.split("\n")]
126
mods[filename] = (line_map, template_lines)
128
template_ln = line_map[lineno]
129
if template_ln <= len(template_lines):
130
template_line = template_lines[template_ln - 1]
133
new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source))
135
for l in range(len(new_trcback)-1, 0, -1):
136
if new_trcback[l][5]:
137
self.source = new_trcback[l][7]
138
self.lineno = new_trcback[l][5]
142
self.source = file(new_trcback[-1][0]).read()
145
self.lineno = new_trcback[-1][1]
146
return (type, value, new_trcback)
149
def text_error_template(lookup=None):
150
"""provides a template that renders a stack trace in a similar format to the Python interpreter,
151
substituting source template filenames, line numbers and code for that of the originating
152
source template, as applicable."""
154
return mako.template.Template(r"""
156
from mako.exceptions import RichTraceback
159
tback = RichTraceback()
161
Traceback (most recent call last):
162
% for (filename, lineno, function, line) in tback.traceback:
163
File "${filename}", line ${lineno}, in ${function or '?'}
164
${line | unicode.strip}
166
${str(tback.error.__class__.__name__)}: ${str(tback.error)}
169
def html_error_template():
170
"""provides a template that renders a stack trace in an HTML format, providing an excerpt of
171
code as well as substituting source template filenames, line numbers and code
172
for that of the originating source template, as applicable.
174
the template's default encoding_errors value is 'htmlentityreplace'. the template has
177
with the full option disabled, only a section of an HTML document is returned.
178
with the css option disabled, the default stylesheet won't be included."""
180
return mako.template.Template(r"""
182
from mako.exceptions import RichTraceback
184
<%page args="full=True, css=True"/>
188
<title>Mako Runtime Error</title>
192
body { font-family:verdana; margin:10px 30px 10px 30px;}
193
.stacktrace { margin:5px 5px 5px 5px; }
194
.highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; }
195
.nonhighlight { padding:0px; background-color:#DFDFDF; }
196
.sample { padding:10px; margin:10px 10px 10px 10px; font-family:monospace; }
197
.sampleline { padding:0px 10px 0px 10px; }
198
.sourceline { margin:5px 5px 10px 5px; font-family:monospace;}
199
.location { font-size:80%; }
209
tback = RichTraceback()
213
lines = src.split('\n')
217
<h3>${str(tback.error.__class__.__name__)}: ${str(tback.error)}</h3>
221
<div class="nonhighlight">
222
% for index in range(max(0, line-4),min(len(lines), line+5)):
223
% if index + 1 == line:
224
<div class="highlight">${index + 1} ${lines[index] | h}</div>
226
<div class="sampleline">${index + 1} ${lines[index] | h}</div>
233
<div class="stacktrace">
234
% for (filename, lineno, function, line) in tback.reverse_traceback:
235
<div class="location">${filename}, line ${lineno}:</div>
236
<div class="sourceline">${line | h}</div>
244
""", output_encoding=sys.getdefaultencoding(), encoding_errors='htmlentityreplace')