123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- # mako/exceptions.py
- # Copyright (C) 2006-2016 the Mako authors and contributors <see AUTHORS file>
- #
- # This module is part of Mako and is released under
- # the MIT License: http://www.opensource.org/licenses/mit-license.php
- """exception classes"""
- import traceback
- import sys
- from mako import util, compat
- class MakoException(Exception):
- pass
- class RuntimeException(MakoException):
- pass
- def _format_filepos(lineno, pos, filename):
- if filename is None:
- return " at line: %d char: %d" % (lineno, pos)
- else:
- return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)
- class CompileException(MakoException):
- def __init__(self, message, source, lineno, pos, filename):
- MakoException.__init__(
- self,
- message + _format_filepos(lineno, pos, filename))
- self.lineno = lineno
- self.pos = pos
- self.filename = filename
- self.source = source
- class SyntaxException(MakoException):
- def __init__(self, message, source, lineno, pos, filename):
- MakoException.__init__(
- self,
- message + _format_filepos(lineno, pos, filename))
- self.lineno = lineno
- self.pos = pos
- self.filename = filename
- self.source = source
- class UnsupportedError(MakoException):
- """raised when a retired feature is used."""
- class NameConflictError(MakoException):
- """raised when a reserved word is used inappropriately"""
- class TemplateLookupException(MakoException):
- pass
- class TopLevelLookupException(TemplateLookupException):
- pass
- class RichTraceback(object):
- """Pull the current exception from the ``sys`` traceback and extracts
- Mako-specific template information.
- See the usage examples in :ref:`handling_exceptions`.
- """
- def __init__(self, error=None, traceback=None):
- self.source, self.lineno = "", 0
- if error is None or traceback is None:
- t, value, tback = sys.exc_info()
- if error is None:
- error = value or t
- if traceback is None:
- traceback = tback
- self.error = error
- self.records = self._init(traceback)
- if isinstance(self.error, (CompileException, SyntaxException)):
- self.source = self.error.source
- self.lineno = self.error.lineno
- self._has_source = True
- self._init_message()
- @property
- def errorname(self):
- return compat.exception_name(self.error)
- def _init_message(self):
- """Find a unicode representation of self.error"""
- try:
- self.message = compat.text_type(self.error)
- except UnicodeError:
- try:
- self.message = str(self.error)
- except UnicodeEncodeError:
- # Fallback to args as neither unicode nor
- # str(Exception(u'\xe6')) work in Python < 2.6
- self.message = self.error.args[0]
- if not isinstance(self.message, compat.text_type):
- self.message = compat.text_type(self.message, 'ascii', 'replace')
- def _get_reformatted_records(self, records):
- for rec in records:
- if rec[6] is not None:
- yield (rec[4], rec[5], rec[2], rec[6])
- else:
- yield tuple(rec[0:4])
- @property
- def traceback(self):
- """Return a list of 4-tuple traceback records (i.e. normal python
- format) with template-corresponding lines remapped to the originating
- template.
- """
- return list(self._get_reformatted_records(self.records))
- @property
- def reverse_records(self):
- return reversed(self.records)
- @property
- def reverse_traceback(self):
- """Return the same data as traceback, except in reverse order.
- """
- return list(self._get_reformatted_records(self.reverse_records))
- def _init(self, trcback):
- """format a traceback from sys.exc_info() into 7-item tuples,
- containing the regular four traceback tuple items, plus the original
- template filename, the line number adjusted relative to the template
- source, and code line from that line number of the template."""
- import mako.template
- mods = {}
- rawrecords = traceback.extract_tb(trcback)
- new_trcback = []
- for filename, lineno, function, line in rawrecords:
- if not line:
- line = ''
- try:
- (line_map, template_lines) = mods[filename]
- except KeyError:
- try:
- info = mako.template._get_module_info(filename)
- module_source = info.code
- template_source = info.source
- template_filename = info.template_filename or filename
- except KeyError:
- # A normal .py file (not a Template)
- if not compat.py3k:
- try:
- fp = open(filename, 'rb')
- encoding = util.parse_encoding(fp)
- fp.close()
- except IOError:
- encoding = None
- if encoding:
- line = line.decode(encoding)
- else:
- line = line.decode('ascii', 'replace')
- new_trcback.append((filename, lineno, function, line,
- None, None, None, None))
- continue
- template_ln = 1
- source_map = mako.template.ModuleInfo.\
- get_module_source_metadata(
- module_source, full_line_map=True)
- line_map = source_map['full_line_map']
- template_lines = [line_ for line_ in
- template_source.split("\n")]
- mods[filename] = (line_map, template_lines)
- template_ln = line_map[lineno - 1]
- if template_ln <= len(template_lines):
- template_line = template_lines[template_ln - 1]
- else:
- template_line = None
- new_trcback.append((filename, lineno, function,
- line, template_filename, template_ln,
- template_line, template_source))
- if not self.source:
- for l in range(len(new_trcback) - 1, 0, -1):
- if new_trcback[l][5]:
- self.source = new_trcback[l][7]
- self.lineno = new_trcback[l][5]
- break
- else:
- if new_trcback:
- try:
- # A normal .py file (not a Template)
- fp = open(new_trcback[-1][0], 'rb')
- encoding = util.parse_encoding(fp)
- fp.seek(0)
- self.source = fp.read()
- fp.close()
- if encoding:
- self.source = self.source.decode(encoding)
- except IOError:
- self.source = ''
- self.lineno = new_trcback[-1][1]
- return new_trcback
- def text_error_template(lookup=None):
- """Provides a template that renders a stack trace in a similar format to
- the Python interpreter, substituting source template filenames, line
- numbers and code for that of the originating source template, as
- applicable.
- """
- import mako.template
- return mako.template.Template(r"""
- <%page args="error=None, traceback=None"/>
- <%!
- from mako.exceptions import RichTraceback
- %>\
- <%
- tback = RichTraceback(error=error, traceback=traceback)
- %>\
- Traceback (most recent call last):
- % for (filename, lineno, function, line) in tback.traceback:
- File "${filename}", line ${lineno}, in ${function or '?'}
- ${line | trim}
- % endfor
- ${tback.errorname}: ${tback.message}
- """)
- def _install_pygments():
- global syntax_highlight, pygments_html_formatter
- from mako.ext.pygmentplugin import syntax_highlight # noqa
- from mako.ext.pygmentplugin import pygments_html_formatter # noqa
- def _install_fallback():
- global syntax_highlight, pygments_html_formatter
- from mako.filters import html_escape
- pygments_html_formatter = None
- def syntax_highlight(filename='', language=None):
- return html_escape
- def _install_highlighting():
- try:
- _install_pygments()
- except ImportError:
- _install_fallback()
- _install_highlighting()
- def html_error_template():
- """Provides a template that renders a stack trace in an HTML format,
- providing an excerpt of code as well as substituting source template
- filenames, line numbers and code for that of the originating source
- template, as applicable.
- The template's default ``encoding_errors`` value is
- ``'htmlentityreplace'``. The template has two options. With the
- ``full`` option disabled, only a section of an HTML document is
- returned. With the ``css`` option disabled, the default stylesheet
- won't be included.
- """
- import mako.template
- return mako.template.Template(r"""
- <%!
- from mako.exceptions import RichTraceback, syntax_highlight,\
- pygments_html_formatter
- %>
- <%page args="full=True, css=True, error=None, traceback=None"/>
- % if full:
- <html>
- <head>
- <title>Mako Runtime Error</title>
- % endif
- % if css:
- <style>
- body { font-family:verdana; margin:10px 30px 10px 30px;}
- .stacktrace { margin:5px 5px 5px 5px; }
- .highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; }
- .nonhighlight { padding:0px; background-color:#DFDFDF; }
- .sample { padding:10px; margin:10px 10px 10px 10px;
- font-family:monospace; }
- .sampleline { padding:0px 10px 0px 10px; }
- .sourceline { margin:5px 5px 10px 5px; font-family:monospace;}
- .location { font-size:80%; }
- .highlight { white-space:pre; }
- .sampleline { white-space:pre; }
- % if pygments_html_formatter:
- ${pygments_html_formatter.get_style_defs()}
- .linenos { min-width: 2.5em; text-align: right; }
- pre { margin: 0; }
- .syntax-highlighted { padding: 0 10px; }
- .syntax-highlightedtable { border-spacing: 1px; }
- .nonhighlight { border-top: 1px solid #DFDFDF;
- border-bottom: 1px solid #DFDFDF; }
- .stacktrace .nonhighlight { margin: 5px 15px 10px; }
- .sourceline { margin: 0 0; font-family:monospace; }
- .code { background-color: #F8F8F8; width: 100%; }
- .error .code { background-color: #FFBDBD; }
- .error .syntax-highlighted { background-color: #FFBDBD; }
- % endif
- </style>
- % endif
- % if full:
- </head>
- <body>
- % endif
- <h2>Error !</h2>
- <%
- tback = RichTraceback(error=error, traceback=traceback)
- src = tback.source
- line = tback.lineno
- if src:
- lines = src.split('\n')
- else:
- lines = None
- %>
- <h3>${tback.errorname}: ${tback.message|h}</h3>
- % if lines:
- <div class="sample">
- <div class="nonhighlight">
- % for index in range(max(0, line-4),min(len(lines), line+5)):
- <%
- if pygments_html_formatter:
- pygments_html_formatter.linenostart = index + 1
- %>
- % if index + 1 == line:
- <%
- if pygments_html_formatter:
- old_cssclass = pygments_html_formatter.cssclass
- pygments_html_formatter.cssclass = 'error ' + old_cssclass
- %>
- ${lines[index] | syntax_highlight(language='mako')}
- <%
- if pygments_html_formatter:
- pygments_html_formatter.cssclass = old_cssclass
- %>
- % else:
- ${lines[index] | syntax_highlight(language='mako')}
- % endif
- % endfor
- </div>
- </div>
- % endif
- <div class="stacktrace">
- % for (filename, lineno, function, line) in tback.reverse_traceback:
- <div class="location">${filename}, line ${lineno}:</div>
- <div class="nonhighlight">
- <%
- if pygments_html_formatter:
- pygments_html_formatter.linenostart = lineno
- %>
- <div class="sourceline">${line | syntax_highlight(filename)}</div>
- </div>
- % endfor
- </div>
- % if full:
- </body>
- </html>
- % endif
- """, output_encoding=sys.getdefaultencoding(),
- encoding_errors='htmlentityreplace')
|