|  | # -*- coding: utf-8 -*- | 
|  | """ | 
|  | jinja2.debug | 
|  | ~~~~~~~~~~~~ | 
|  |  | 
|  | Implements the debug interface for Jinja.  This module does some pretty | 
|  | ugly stuff with the Python traceback system in order to achieve tracebacks | 
|  | with correct line numbers, locals and contents. | 
|  |  | 
|  | :copyright: (c) 2010 by the Jinja Team. | 
|  | :license: BSD, see LICENSE for more details. | 
|  | """ | 
|  | import sys | 
|  | import traceback | 
|  | from types import TracebackType, CodeType | 
|  | from jinja2.utils import missing, internal_code | 
|  | from jinja2.exceptions import TemplateSyntaxError | 
|  | from jinja2._compat import iteritems, reraise, PY2 | 
|  |  | 
|  | # on pypy we can take advantage of transparent proxies | 
|  | try: | 
|  | from __pypy__ import tproxy | 
|  | except ImportError: | 
|  | tproxy = None | 
|  |  | 
|  |  | 
|  | # how does the raise helper look like? | 
|  | try: | 
|  | exec("raise TypeError, 'foo'") | 
|  | except SyntaxError: | 
|  | raise_helper = 'raise __jinja_exception__[1]' | 
|  | except TypeError: | 
|  | raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' | 
|  |  | 
|  |  | 
|  | class TracebackFrameProxy(object): | 
|  | """Proxies a traceback frame.""" | 
|  |  | 
|  | def __init__(self, tb): | 
|  | self.tb = tb | 
|  | self._tb_next = None | 
|  |  | 
|  | @property | 
|  | def tb_next(self): | 
|  | return self._tb_next | 
|  |  | 
|  | def set_next(self, next): | 
|  | if tb_set_next is not None: | 
|  | try: | 
|  | tb_set_next(self.tb, next and next.tb or None) | 
|  | except Exception: | 
|  | # this function can fail due to all the hackery it does | 
|  | # on various python implementations.  We just catch errors | 
|  | # down and ignore them if necessary. | 
|  | pass | 
|  | self._tb_next = next | 
|  |  | 
|  | @property | 
|  | def is_jinja_frame(self): | 
|  | return '__jinja_template__' in self.tb.tb_frame.f_globals | 
|  |  | 
|  | def __getattr__(self, name): | 
|  | return getattr(self.tb, name) | 
|  |  | 
|  |  | 
|  | def make_frame_proxy(frame): | 
|  | proxy = TracebackFrameProxy(frame) | 
|  | if tproxy is None: | 
|  | return proxy | 
|  | def operation_handler(operation, *args, **kwargs): | 
|  | if operation in ('__getattribute__', '__getattr__'): | 
|  | return getattr(proxy, args[0]) | 
|  | elif operation == '__setattr__': | 
|  | proxy.__setattr__(*args, **kwargs) | 
|  | else: | 
|  | return getattr(proxy, operation)(*args, **kwargs) | 
|  | return tproxy(TracebackType, operation_handler) | 
|  |  | 
|  |  | 
|  | class ProcessedTraceback(object): | 
|  | """Holds a Jinja preprocessed traceback for printing or reraising.""" | 
|  |  | 
|  | def __init__(self, exc_type, exc_value, frames): | 
|  | assert frames, 'no frames for this traceback?' | 
|  | self.exc_type = exc_type | 
|  | self.exc_value = exc_value | 
|  | self.frames = frames | 
|  |  | 
|  | # newly concatenate the frames (which are proxies) | 
|  | prev_tb = None | 
|  | for tb in self.frames: | 
|  | if prev_tb is not None: | 
|  | prev_tb.set_next(tb) | 
|  | prev_tb = tb | 
|  | prev_tb.set_next(None) | 
|  |  | 
|  | def render_as_text(self, limit=None): | 
|  | """Return a string with the traceback.""" | 
|  | lines = traceback.format_exception(self.exc_type, self.exc_value, | 
|  | self.frames[0], limit=limit) | 
|  | return ''.join(lines).rstrip() | 
|  |  | 
|  | def render_as_html(self, full=False): | 
|  | """Return a unicode string with the traceback as rendered HTML.""" | 
|  | from jinja2.debugrenderer import render_traceback | 
|  | return u'%s\n\n<!--\n%s\n-->' % ( | 
|  | render_traceback(self, full=full), | 
|  | self.render_as_text().decode('utf-8', 'replace') | 
|  | ) | 
|  |  | 
|  | @property | 
|  | def is_template_syntax_error(self): | 
|  | """`True` if this is a template syntax error.""" | 
|  | return isinstance(self.exc_value, TemplateSyntaxError) | 
|  |  | 
|  | @property | 
|  | def exc_info(self): | 
|  | """Exception info tuple with a proxy around the frame objects.""" | 
|  | return self.exc_type, self.exc_value, self.frames[0] | 
|  |  | 
|  | @property | 
|  | def standard_exc_info(self): | 
|  | """Standard python exc_info for re-raising""" | 
|  | tb = self.frames[0] | 
|  | # the frame will be an actual traceback (or transparent proxy) if | 
|  | # we are on pypy or a python implementation with support for tproxy | 
|  | if type(tb) is not TracebackType: | 
|  | tb = tb.tb | 
|  | return self.exc_type, self.exc_value, tb | 
|  |  | 
|  |  | 
|  | def make_traceback(exc_info, source_hint=None): | 
|  | """Creates a processed traceback object from the exc_info.""" | 
|  | exc_type, exc_value, tb = exc_info | 
|  | if isinstance(exc_value, TemplateSyntaxError): | 
|  | exc_info = translate_syntax_error(exc_value, source_hint) | 
|  | initial_skip = 0 | 
|  | else: | 
|  | initial_skip = 1 | 
|  | return translate_exception(exc_info, initial_skip) | 
|  |  | 
|  |  | 
|  | def translate_syntax_error(error, source=None): | 
|  | """Rewrites a syntax error to please traceback systems.""" | 
|  | error.source = source | 
|  | error.translated = True | 
|  | exc_info = (error.__class__, error, None) | 
|  | filename = error.filename | 
|  | if filename is None: | 
|  | filename = '<unknown>' | 
|  | return fake_exc_info(exc_info, filename, error.lineno) | 
|  |  | 
|  |  | 
|  | def translate_exception(exc_info, initial_skip=0): | 
|  | """If passed an exc_info it will automatically rewrite the exceptions | 
|  | all the way down to the correct line numbers and frames. | 
|  | """ | 
|  | tb = exc_info[2] | 
|  | frames = [] | 
|  |  | 
|  | # skip some internal frames if wanted | 
|  | for x in range(initial_skip): | 
|  | if tb is not None: | 
|  | tb = tb.tb_next | 
|  | initial_tb = tb | 
|  |  | 
|  | while tb is not None: | 
|  | # skip frames decorated with @internalcode.  These are internal | 
|  | # calls we can't avoid and that are useless in template debugging | 
|  | # output. | 
|  | if tb.tb_frame.f_code in internal_code: | 
|  | tb = tb.tb_next | 
|  | continue | 
|  |  | 
|  | # save a reference to the next frame if we override the current | 
|  | # one with a faked one. | 
|  | next = tb.tb_next | 
|  |  | 
|  | # fake template exceptions | 
|  | template = tb.tb_frame.f_globals.get('__jinja_template__') | 
|  | if template is not None: | 
|  | lineno = template.get_corresponding_lineno(tb.tb_lineno) | 
|  | tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, | 
|  | lineno)[2] | 
|  |  | 
|  | frames.append(make_frame_proxy(tb)) | 
|  | tb = next | 
|  |  | 
|  | # if we don't have any exceptions in the frames left, we have to | 
|  | # reraise it unchanged. | 
|  | # XXX: can we backup here?  when could this happen? | 
|  | if not frames: | 
|  | reraise(exc_info[0], exc_info[1], exc_info[2]) | 
|  |  | 
|  | return ProcessedTraceback(exc_info[0], exc_info[1], frames) | 
|  |  | 
|  |  | 
|  | def fake_exc_info(exc_info, filename, lineno): | 
|  | """Helper for `translate_exception`.""" | 
|  | exc_type, exc_value, tb = exc_info | 
|  |  | 
|  | # figure the real context out | 
|  | if tb is not None: | 
|  | real_locals = tb.tb_frame.f_locals.copy() | 
|  | ctx = real_locals.get('context') | 
|  | if ctx: | 
|  | locals = ctx.get_all() | 
|  | else: | 
|  | locals = {} | 
|  | for name, value in iteritems(real_locals): | 
|  | if name.startswith('l_') and value is not missing: | 
|  | locals[name[2:]] = value | 
|  |  | 
|  | # if there is a local called __jinja_exception__, we get | 
|  | # rid of it to not break the debug functionality. | 
|  | locals.pop('__jinja_exception__', None) | 
|  | else: | 
|  | locals = {} | 
|  |  | 
|  | # assamble fake globals we need | 
|  | globals = { | 
|  | '__name__':             filename, | 
|  | '__file__':             filename, | 
|  | '__jinja_exception__':  exc_info[:2], | 
|  |  | 
|  | # we don't want to keep the reference to the template around | 
|  | # to not cause circular dependencies, but we mark it as Jinja | 
|  | # frame for the ProcessedTraceback | 
|  | '__jinja_template__':   None | 
|  | } | 
|  |  | 
|  | # and fake the exception | 
|  | code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') | 
|  |  | 
|  | # if it's possible, change the name of the code.  This won't work | 
|  | # on some python environments such as google appengine | 
|  | try: | 
|  | if tb is None: | 
|  | location = 'template' | 
|  | else: | 
|  | function = tb.tb_frame.f_code.co_name | 
|  | if function == 'root': | 
|  | location = 'top-level template code' | 
|  | elif function.startswith('block_'): | 
|  | location = 'block "%s"' % function[6:] | 
|  | else: | 
|  | location = 'template' | 
|  |  | 
|  | if PY2: | 
|  | code = CodeType(0, code.co_nlocals, code.co_stacksize, | 
|  | code.co_flags, code.co_code, code.co_consts, | 
|  | code.co_names, code.co_varnames, filename, | 
|  | location, code.co_firstlineno, | 
|  | code.co_lnotab, (), ()) | 
|  | else: | 
|  | code = CodeType(0, code.co_kwonlyargcount, | 
|  | code.co_nlocals, code.co_stacksize, | 
|  | code.co_flags, code.co_code, code.co_consts, | 
|  | code.co_names, code.co_varnames, filename, | 
|  | location, code.co_firstlineno, | 
|  | code.co_lnotab, (), ()) | 
|  | except Exception as e: | 
|  | pass | 
|  |  | 
|  | # execute the code and catch the new traceback | 
|  | try: | 
|  | exec(code, globals, locals) | 
|  | except: | 
|  | exc_info = sys.exc_info() | 
|  | new_tb = exc_info[2].tb_next | 
|  |  | 
|  | # return without this frame | 
|  | return exc_info[:2] + (new_tb,) | 
|  |  | 
|  |  | 
|  | def _init_ugly_crap(): | 
|  | """This function implements a few ugly things so that we can patch the | 
|  | traceback objects.  The function returned allows resetting `tb_next` on | 
|  | any python traceback object.  Do not attempt to use this on non cpython | 
|  | interpreters | 
|  | """ | 
|  | import ctypes | 
|  | from types import TracebackType | 
|  |  | 
|  | if PY2: | 
|  | # figure out size of _Py_ssize_t for Python 2: | 
|  | if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): | 
|  | _Py_ssize_t = ctypes.c_int64 | 
|  | else: | 
|  | _Py_ssize_t = ctypes.c_int | 
|  | else: | 
|  | # platform ssize_t on Python 3 | 
|  | _Py_ssize_t = ctypes.c_ssize_t | 
|  |  | 
|  | # regular python | 
|  | class _PyObject(ctypes.Structure): | 
|  | pass | 
|  | _PyObject._fields_ = [ | 
|  | ('ob_refcnt', _Py_ssize_t), | 
|  | ('ob_type', ctypes.POINTER(_PyObject)) | 
|  | ] | 
|  |  | 
|  | # python with trace | 
|  | if hasattr(sys, 'getobjects'): | 
|  | class _PyObject(ctypes.Structure): | 
|  | pass | 
|  | _PyObject._fields_ = [ | 
|  | ('_ob_next', ctypes.POINTER(_PyObject)), | 
|  | ('_ob_prev', ctypes.POINTER(_PyObject)), | 
|  | ('ob_refcnt', _Py_ssize_t), | 
|  | ('ob_type', ctypes.POINTER(_PyObject)) | 
|  | ] | 
|  |  | 
|  | class _Traceback(_PyObject): | 
|  | pass | 
|  | _Traceback._fields_ = [ | 
|  | ('tb_next', ctypes.POINTER(_Traceback)), | 
|  | ('tb_frame', ctypes.POINTER(_PyObject)), | 
|  | ('tb_lasti', ctypes.c_int), | 
|  | ('tb_lineno', ctypes.c_int) | 
|  | ] | 
|  |  | 
|  | def tb_set_next(tb, next): | 
|  | """Set the tb_next attribute of a traceback object.""" | 
|  | if not (isinstance(tb, TracebackType) and | 
|  | (next is None or isinstance(next, TracebackType))): | 
|  | raise TypeError('tb_set_next arguments must be traceback objects') | 
|  | obj = _Traceback.from_address(id(tb)) | 
|  | if tb.tb_next is not None: | 
|  | old = _Traceback.from_address(id(tb.tb_next)) | 
|  | old.ob_refcnt -= 1 | 
|  | if next is None: | 
|  | obj.tb_next = ctypes.POINTER(_Traceback)() | 
|  | else: | 
|  | next = _Traceback.from_address(id(next)) | 
|  | next.ob_refcnt += 1 | 
|  | obj.tb_next = ctypes.pointer(next) | 
|  |  | 
|  | return tb_set_next | 
|  |  | 
|  |  | 
|  | # try to get a tb_set_next implementation if we don't have transparent | 
|  | # proxies. | 
|  | tb_set_next = None | 
|  | if tproxy is None: | 
|  | try: | 
|  | tb_set_next = _init_ugly_crap() | 
|  | except: | 
|  | pass | 
|  | del _init_ugly_crap |