1c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley# mako/exceptions.py
2c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley# Copyright (C) 2006-2015 the Mako authors and contributors <see AUTHORS file>
3c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley#
4c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley# This module is part of Mako and is released under
5c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley# the MIT License: http://www.opensource.org/licenses/mit-license.php
6c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
7c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley"""exception classes"""
8c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
9c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyimport traceback
10c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyimport sys
11c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyfrom mako import util, compat
12c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
13c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass MakoException(Exception):
14c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    pass
15c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
16c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass RuntimeException(MakoException):
17c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    pass
18c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
19c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef _format_filepos(lineno, pos, filename):
20c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    if filename is None:
21c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return " at line: %d char: %d" % (lineno, pos)
22c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    else:
23c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)
24c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
25c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
26c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass CompileException(MakoException):
27c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def __init__(self, message, source, lineno, pos, filename):
28c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        MakoException.__init__(self,
29c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                              message + _format_filepos(lineno, pos, filename))
30c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.lineno = lineno
31c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.pos = pos
32c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.filename = filename
33c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.source = source
34c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
35c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass SyntaxException(MakoException):
36c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def __init__(self, message, source, lineno, pos, filename):
37c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        MakoException.__init__(self,
38c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                              message + _format_filepos(lineno, pos, filename))
39c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.lineno = lineno
40c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.pos = pos
41c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.filename = filename
42c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.source = source
43c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
44c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass UnsupportedError(MakoException):
45c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """raised when a retired feature is used."""
46c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
47c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass NameConflictError(MakoException):
48c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """raised when a reserved word is used inappropriately"""
49c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
50c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass TemplateLookupException(MakoException):
51c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    pass
52c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
53c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass TopLevelLookupException(TemplateLookupException):
54c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    pass
55c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
56c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleyclass RichTraceback(object):
57c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """Pull the current exception from the ``sys`` traceback and extracts
58c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    Mako-specific template information.
59c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
60c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    See the usage examples in :ref:`handling_exceptions`.
61c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
62c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """
63c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def __init__(self, error=None, traceback=None):
64c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.source, self.lineno = "", 0
65c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
66c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if error is None or traceback is None:
67c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            t, value, tback = sys.exc_info()
68c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
69c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if error is None:
70c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            error = value or t
71c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
72c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if traceback is None:
73c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            traceback = tback
74c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
75c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.error = error
76c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self.records = self._init(traceback)
77c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
78c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if isinstance(self.error, (CompileException, SyntaxException)):
79c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            self.source = self.error.source
80c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            self.lineno = self.error.lineno
81c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            self._has_source = True
82c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
83c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        self._init_message()
84c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
85c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    @property
86c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def errorname(self):
87c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return compat.exception_name(self.error)
88c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
89c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def _init_message(self):
90c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """Find a unicode representation of self.error"""
91c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        try:
92c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            self.message = compat.text_type(self.error)
93c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        except UnicodeError:
94c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            try:
95c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                self.message = str(self.error)
96c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            except UnicodeEncodeError:
97c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                # Fallback to args as neither unicode nor
98c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                # str(Exception(u'\xe6')) work in Python < 2.6
99c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                self.message = self.error.args[0]
100c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if not isinstance(self.message, compat.text_type):
101c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            self.message = compat.text_type(self.message, 'ascii', 'replace')
102c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
103c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def _get_reformatted_records(self, records):
104c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        for rec in records:
105c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            if rec[6] is not None:
106c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                yield (rec[4], rec[5], rec[2], rec[6])
107c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            else:
108c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                yield tuple(rec[0:4])
109c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
110c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    @property
111c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def traceback(self):
112c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """Return a list of 4-tuple traceback records (i.e. normal python
113c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        format) with template-corresponding lines remapped to the originating
114c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        template.
115c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
116c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """
117c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return list(self._get_reformatted_records(self.records))
118c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
119c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    @property
120c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def reverse_records(self):
121c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return reversed(self.records)
122c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
123c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    @property
124c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def reverse_traceback(self):
125c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """Return the same data as traceback, except in reverse order.
126c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """
127c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
128c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return list(self._get_reformatted_records(self.reverse_records))
129c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
130c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def _init(self, trcback):
131c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        """format a traceback from sys.exc_info() into 7-item tuples,
132c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        containing the regular four traceback tuple items, plus the original
133c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        template filename, the line number adjusted relative to the template
134c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        source, and code line from that line number of the template."""
135c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
136c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        import mako.template
137c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        mods = {}
138c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        rawrecords = traceback.extract_tb(trcback)
139c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        new_trcback = []
140c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        for filename, lineno, function, line in rawrecords:
141c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            if not line:
142c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                line = ''
143c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            try:
144c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                (line_map, template_lines) = mods[filename]
145c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            except KeyError:
146c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                try:
147c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    info = mako.template._get_module_info(filename)
148c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    module_source = info.code
149c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    template_source = info.source
150c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    template_filename = info.template_filename or filename
151c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                except KeyError:
152c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    # A normal .py file (not a Template)
153c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    if not compat.py3k:
154c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        try:
155c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            fp = open(filename, 'rb')
156c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            encoding = util.parse_encoding(fp)
157c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            fp.close()
158c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        except IOError:
159c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            encoding = None
160c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        if encoding:
161c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            line = line.decode(encoding)
162c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        else:
163c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            line = line.decode('ascii', 'replace')
164c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    new_trcback.append((filename, lineno, function, line,
165c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                            None, None, None, None))
166c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    continue
167c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
168c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                template_ln = 1
169c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
170c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                source_map = mako.template.ModuleInfo.\
171c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                get_module_source_metadata(
172c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                    module_source, full_line_map=True)
173c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                line_map = source_map['full_line_map']
174c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
175c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                template_lines = [line for line in
176c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                    template_source.split("\n")]
177c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                mods[filename] = (line_map, template_lines)
178c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
179c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            template_ln = line_map[lineno - 1]
180c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
181c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            if template_ln <= len(template_lines):
182c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                template_line = template_lines[template_ln - 1]
183c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            else:
184c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                template_line = None
185c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            new_trcback.append((filename, lineno, function,
186c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                line, template_filename, template_ln,
187c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                                template_line, template_source))
188c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        if not self.source:
189c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            for l in range(len(new_trcback) - 1, 0, -1):
190c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                if new_trcback[l][5]:
191c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    self.source = new_trcback[l][7]
192c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    self.lineno = new_trcback[l][5]
193c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    break
194c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            else:
195c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                if new_trcback:
196c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    try:
197c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        # A normal .py file (not a Template)
198c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        fp = open(new_trcback[-1][0], 'rb')
199c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        encoding = util.parse_encoding(fp)
200c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        fp.seek(0)
201c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        self.source = fp.read()
202c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        fp.close()
203c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        if encoding:
204c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                            self.source = self.source.decode(encoding)
205c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    except IOError:
206c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        self.source = ''
207c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                    self.lineno = new_trcback[-1][1]
208c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return new_trcback
209c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
210c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
211c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef text_error_template(lookup=None):
212c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """Provides a template that renders a stack trace in a similar format to
213c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    the Python interpreter, substituting source template filenames, line
214c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    numbers and code for that of the originating source template, as
215c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    applicable.
216c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
217c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """
218c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    import mako.template
219c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    return mako.template.Template(r"""
220c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%page args="error=None, traceback=None"/>
221c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%!
222c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    from mako.exceptions import RichTraceback
223c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley%>\
224c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%
225c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    tback = RichTraceback(error=error, traceback=traceback)
226c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley%>\
227c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim RowleyTraceback (most recent call last):
228c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% for (filename, lineno, function, line) in tback.traceback:
229c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley  File "${filename}", line ${lineno}, in ${function or '?'}
230c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    ${line | trim}
231c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endfor
232c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley${tback.errorname}: ${tback.message}
233c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley""")
234c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
235c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
236c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef _install_pygments():
237c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    global syntax_highlight, pygments_html_formatter
238c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    from mako.ext.pygmentplugin import syntax_highlight,\
239c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            pygments_html_formatter
240c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
241c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef _install_fallback():
242c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    global syntax_highlight, pygments_html_formatter
243c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    from mako.filters import html_escape
244c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    pygments_html_formatter = None
245c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    def syntax_highlight(filename='', language=None):
246c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        return html_escape
247c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
248c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef _install_highlighting():
249c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    try:
250c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        _install_pygments()
251c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    except ImportError:
252c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        _install_fallback()
253c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley_install_highlighting()
254c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
255c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowleydef html_error_template():
256c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """Provides a template that renders a stack trace in an HTML format,
257c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    providing an excerpt of code as well as substituting source template
258c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    filenames, line numbers and code for that of the originating source
259c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    template, as applicable.
260c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
261c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    The template's default ``encoding_errors`` value is
262c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    ``'htmlentityreplace'``. The template has two options. With the
263c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    ``full`` option disabled, only a section of an HTML document is
264c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    returned. With the ``css`` option disabled, the default stylesheet
265c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    won't be included.
266c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
267c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    """
268c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    import mako.template
269c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    return mako.template.Template(r"""
270c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%!
271c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    from mako.exceptions import RichTraceback, syntax_highlight,\
272c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley            pygments_html_formatter
273c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley%>
274c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%page args="full=True, css=True, error=None, traceback=None"/>
275c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% if full:
276c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<html>
277c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<head>
278c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <title>Mako Runtime Error</title>
279c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endif
280c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% if css:
281c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <style>
282c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        body { font-family:verdana; margin:10px 30px 10px 30px;}
283c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .stacktrace { margin:5px 5px 5px 5px; }
284c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; }
285c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .nonhighlight { padding:0px; background-color:#DFDFDF; }
286c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .sample { padding:10px; margin:10px 10px 10px 10px;
287c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                  font-family:monospace; }
288c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .sampleline { padding:0px 10px 0px 10px; }
289c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .sourceline { margin:5px 5px 10px 5px; font-family:monospace;}
290c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .location { font-size:80%; }
291c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .highlight { white-space:pre; }
292c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .sampleline { white-space:pre; }
293c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
294c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    % if pygments_html_formatter:
295c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        ${pygments_html_formatter.get_style_defs()}
296c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .linenos { min-width: 2.5em; text-align: right; }
297c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        pre { margin: 0; }
298c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .syntax-highlighted { padding: 0 10px; }
299c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .syntax-highlightedtable { border-spacing: 1px; }
300c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .nonhighlight { border-top: 1px solid #DFDFDF;
301c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley                        border-bottom: 1px solid #DFDFDF; }
302c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .stacktrace .nonhighlight { margin: 5px 15px 10px; }
303c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .sourceline { margin: 0 0; font-family:monospace; }
304c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .code { background-color: #F8F8F8; width: 100%; }
305c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .error .code { background-color: #FFBDBD; }
306c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        .error .syntax-highlighted { background-color: #FFBDBD; }
307c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    % endif
308c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
309c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    </style>
310c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endif
311c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% if full:
312c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley</head>
313c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<body>
314c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endif
315c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
316c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<h2>Error !</h2>
317c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<%
318c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    tback = RichTraceback(error=error, traceback=traceback)
319c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    src = tback.source
320c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    line = tback.lineno
321c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    if src:
322c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        lines = src.split('\n')
323c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    else:
324c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        lines = None
325c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley%>
326c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<h3>${tback.errorname}: ${tback.message|h}</h3>
327c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
328c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% if lines:
329c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <div class="sample">
330c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <div class="nonhighlight">
331c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% for index in range(max(0, line-4),min(len(lines), line+5)):
332c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <%
333c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley       if pygments_html_formatter:
334c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley           pygments_html_formatter.linenostart = index + 1
335c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    %>
336c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    % if index + 1 == line:
337c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <%
338c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley       if pygments_html_formatter:
339c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley           old_cssclass = pygments_html_formatter.cssclass
340c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley           pygments_html_formatter.cssclass = 'error ' + old_cssclass
341c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    %>
342c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        ${lines[index] | syntax_highlight(language='mako')}
343c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <%
344c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley       if pygments_html_formatter:
345c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley           pygments_html_formatter.cssclass = old_cssclass
346c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    %>
347c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    % else:
348c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        ${lines[index] | syntax_highlight(language='mako')}
349c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    % endif
350c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endfor
351c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    </div>
352c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    </div>
353c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endif
354c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
355c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley<div class="stacktrace">
356c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% for (filename, lineno, function, line) in tback.reverse_traceback:
357c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <div class="location">${filename}, line ${lineno}:</div>
358c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <div class="nonhighlight">
359c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    <%
360c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley       if pygments_html_formatter:
361c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley           pygments_html_formatter.linenostart = lineno
362c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    %>
363c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley      <div class="sourceline">${line | syntax_highlight(filename)}</div>
364c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley    </div>
365c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endfor
366c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley</div>
367c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley
368c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% if full:
369c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley</body>
370c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley</html>
371c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley% endif
372c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley""", output_encoding=sys.getdefaultencoding(),
373c6e67f5a9373e916a8d2333585cb5787aa5f7bb7Tim Rowley        encoding_errors='htmlentityreplace')
374