| Index: third_party/jinja2/debug.py
|
| diff --git a/third_party/jinja2/debug.py b/third_party/jinja2/debug.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2af2222322782c628697dbe7bfef661eb084559d
|
| --- /dev/null
|
| +++ b/third_party/jinja2/debug.py
|
| @@ -0,0 +1,339 @@
|
| +# -*- 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
|
| +from jinja2.utils import CodeType, missing, internal_code
|
| +from jinja2.exceptions import TemplateSyntaxError
|
| +
|
| +# 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 priting 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 xrange(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:
|
| + raise 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 real_locals.iteritems():
|
| + 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'
|
| + 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, (), ())
|
| + except:
|
| + pass
|
| +
|
| + # execute the code and catch the new traceback
|
| + try:
|
| + exec code in 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
|
| +
|
| + # figure out side of _Py_ssize_t
|
| + if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
| + _Py_ssize_t = ctypes.c_int64
|
| + else:
|
| + _Py_ssize_t = ctypes.c_int
|
| +
|
| + # 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:
|
| + from jinja2._debugsupport import tb_set_next
|
| + except ImportError:
|
| + try:
|
| + tb_set_next = _init_ugly_crap()
|
| + except:
|
| + pass
|
| + del _init_ugly_crap
|
|
|