remove unused support for max_loop_depth
[libfirm] / scripts / jinja2 / debug.py
index 53dac4d..2af2222 100644 (file)
     ugly stuff with the Python traceback system in order to achieve tracebacks
     with correct line numbers, locals and contents.
 
-    :copyright: Copyright 2008 by Armin Ronacher.
-    :license: BSD.
+    :copyright: (c) 2010 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
 """
 import sys
-from jinja2.utils import CodeType
+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 translate_exception(exc_info):
+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.
     """
-    result_tb = prev_tb = None
-    initial_tb = tb = exc_info[2].tb_next
+    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, prev_tb)[2]
-        if result_tb is None:
-            result_tb = tb
-        prev_tb = tb
-        tb = tb.tb_next
+                               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 exc_info[:2] + (result_tb or initial_tb,)
+    return ProcessedTraceback(exc_info[0], exc_info[1], frames)
 
 
-def fake_exc_info(exc_info, filename, lineno, tb_back=None):
+def fake_exc_info(exc_info, filename, lineno):
     """Helper for `translate_exception`."""
     exc_type, exc_value, tb = exc_info
 
     # figure the real context out
-    real_locals = tb.tb_frame.f_locals.copy()
-    ctx = real_locals.get('context')
-    if ctx:
-        locals = ctx.get_all()
+    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 = {}
-    for name, value in real_locals.iteritems():
-        if name.startswith('l_'):
-            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)
 
     # assamble fake globals we need
     globals = {
         '__name__':             filename,
         '__file__':             filename,
-        '__jinja_exception__':  exc_info[:2]
+        '__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 __jinja_exception__[0], ' +
-                   '__jinja_exception__[1]', filename, 'exec')
+    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:
-        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:
+        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,
@@ -90,13 +259,6 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
         exc_info = sys.exc_info()
         new_tb = exc_info[2].tb_next
 
-    # now we can patch the exc info accordingly
-    if tb_set_next is not None:
-        if tb_back is not None:
-            tb_set_next(tb_back, new_tb)
-        if tb is not None:
-            tb_set_next(new_tb, tb.tb_next)
-
     # return without this frame
     return exc_info[:2] + (new_tb,)
 
@@ -104,7 +266,8 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
 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.
+    any python traceback object.  Do not attempt to use this on non cpython
+    interpreters
     """
     import ctypes
     from types import TracebackType
@@ -124,7 +287,7 @@ def _init_ugly_crap():
     ]
 
     # python with trace
-    if object.__basicsize__ != ctypes.sizeof(_PyObject):
+    if hasattr(sys, 'getobjects'):
         class _PyObject(ctypes.Structure):
             pass
         _PyObject._fields_ = [
@@ -162,12 +325,15 @@ def _init_ugly_crap():
     return tb_set_next
 
 
-# try to get a tb_set_next implementation
-try:
-    from jinja2._speedups import tb_set_next
-except ImportError:
+# 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:
-        tb_set_next = None
-del _init_ugly_crap
+        from jinja2._debugsupport import tb_set_next
+    except ImportError:
+        try:
+            tb_set_next = _init_ugly_crap()
+        except:
+            pass
+    del _init_ugly_crap