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