Added Mako report library
[odoo/odoo.git] / bin / mako / exceptions.py
1 # exceptions.py
2 # Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
3 #
4 # This module is part of Mako and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7 """exception classes"""
8
9 import traceback, sys, re
10 from mako import util
11
12 class MakoException(Exception):
13     pass
14
15 class RuntimeException(MakoException):
16     pass
17
18 def _format_filepos(lineno, pos, filename):
19     if filename is None:
20         return " at line: %d char: %d" % (lineno, pos)
21     else:
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))
26         self.lineno =lineno
27         self.pos = pos
28         self.filename = filename
29         self.source = source
30                     
31 class SyntaxException(MakoException):
32     def __init__(self, message, source, lineno, pos, filename):
33         MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
34         self.lineno =lineno
35         self.pos = pos
36         self.filename = filename
37         self.source = source
38         
39 class TemplateLookupException(MakoException):
40     pass
41
42 class TopLevelLookupException(TemplateLookupException):
43     pass
44     
45 class RichTraceback(object):
46     """pulls the current exception from the sys traceback and extracts Mako-specific 
47     template information.
48     
49     Usage:
50     
51     RichTraceback()
52     
53     Properties:
54     
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
67     
68     """
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:
73             self.error = t
74         if isinstance(self.error, CompileException) or isinstance(self.error, SyntaxException):
75             import mako.template
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):
82         for rec in records:
83             if rec[6] is not None:
84                 yield (rec[4], rec[5], rec[2], rec[6])
85             else:
86                 yield tuple(rec[0:4])
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
90     """)
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
93     """)
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."""
99         import mako.template
100         mods = {}
101         if not trcback:
102             (type, value, trcback) = sys.exc_info()
103         rawrecords = traceback.extract_tb(trcback)
104         new_trcback = []
105         for filename, lineno, function, line in rawrecords:
106             try:
107                 (line_map, template_lines) = mods[filename]
108             except KeyError:
109                 try:
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
114                 except KeyError:
115                     new_trcback.append((filename, lineno, function, line, None, None, None, None))
116                     continue
117
118                 template_ln = module_ln = 1
119                 line_map = {}
120                 for line in module_source.split("\n"):
121                     match = re.match(r'\s*# SOURCE LINE (\d+)', line)
122                     if match:
123                         template_ln = int(match.group(1))
124                     else:
125                         template_ln += 1
126                     module_ln += 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)
130
131             template_ln = line_map[lineno]
132             if template_ln <= len(template_lines):
133                 template_line = template_lines[template_ln - 1]
134             else:
135                 template_line = None
136             new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source))
137         if not self.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]
142                     break
143             else:
144                 try:
145                     # A normal .py file (not a Template)
146                     fp = open(new_trcback[-1][0])
147                     encoding = util.parse_encoding(fp)
148                     fp.seek(0)
149                     self.source = fp.read()
150                     fp.close()
151                     if encoding:
152                         self.source = self.source.decode(encoding)
153                 except IOError:
154                     self.source = ''
155                 self.lineno = new_trcback[-1][1]
156         return (type, value, new_trcback)
157
158                 
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."""
163     import mako.template
164     return mako.template.Template(r"""
165 <%page args="traceback=None"/>
166 <%!
167     from mako.exceptions import RichTraceback
168 %>\
169 <%
170     tback = RichTraceback(traceback=traceback)
171 %>\
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}
176 % endfor
177 ${str(tback.error.__class__.__name__)}: ${str(tback.error)}
178 """)
179
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.
184
185     the template's default encoding_errors value is 'htmlentityreplace'. the template has
186     two options:
187
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."""
190     import mako.template
191     return mako.template.Template(r"""
192 <%!
193     from mako.exceptions import RichTraceback
194 %>
195 <%page args="full=True, css=True, traceback=None"/>
196 % if full:
197 <html>
198 <head>
199     <title>Mako Runtime Error</title>
200 % endif
201 % if css:
202     <style>
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%; }
211     </style>
212 % endif
213 % if full:
214 </head>
215 <body>
216 % endif
217
218 <h2>Error !</h2>
219 <%
220     tback = RichTraceback(traceback=traceback)
221     src = tback.source
222     line = tback.lineno
223     if src:
224         lines = src.split('\n')
225     else:
226         lines = None
227 %>
228 <h3>${str(tback.error.__class__.__name__)}: ${str(tback.error)}</h3>
229
230 % if lines:
231     <div class="sample">
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>
236     % else:
237 <div class="sampleline">${index + 1} ${lines[index] | h}</div>
238     % endif
239 % endfor
240     </div>
241     </div>
242 % endif
243
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>
248 % endfor
249 </div>
250
251 % if full:
252 </body>
253 </html>
254 % endif
255 """, output_encoding=sys.getdefaultencoding(), encoding_errors='htmlentityreplace')