Broken overlong line.
[jinja2.git] / jinja2 / debug.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.debug
4     ~~~~~~~~~~~~
5
6     Implements the debug interface for Jinja.  This module does some pretty
7     ugly stuff with the Python traceback system in order to achieve tracebacks
8     with correct line numbers, locals and contents.
9
10     :copyright: (c) 2009 by the Jinja Team.
11     :license: BSD.
12 """
13 import sys
14 from jinja2.utils import CodeType, missing, internal_code
15
16
17 def translate_syntax_error(error, source=None):
18     """Rewrites a syntax error to please traceback systems."""
19     error.source = source
20     error.translated = True
21     exc_info = (type(error), error, None)
22     filename = error.filename
23     if filename is None:
24         filename = '<unknown>'
25     return fake_exc_info(exc_info, filename, error.lineno)
26
27
28 def translate_exception(exc_info):
29     """If passed an exc_info it will automatically rewrite the exceptions
30     all the way down to the correct line numbers and frames.
31     """
32     result_tb = prev_tb = None
33     initial_tb = tb = exc_info[2].tb_next
34
35     while tb is not None:
36         # skip frames decorated with @internalcode.  These are internal
37         # calls we can't avoid and that are useless in template debugging
38         # output.
39         if tb_set_next is not None and tb.tb_frame.f_code in internal_code:
40             tb_set_next(prev_tb, tb.tb_next)
41             tb = tb.tb_next
42             continue
43
44         template = tb.tb_frame.f_globals.get('__jinja_template__')
45         if template is not None:
46             lineno = template.get_corresponding_lineno(tb.tb_lineno)
47             tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
48                                lineno, prev_tb)[2]
49         if result_tb is None:
50             result_tb = tb
51         prev_tb = tb
52         tb = tb.tb_next
53
54     return exc_info[:2] + (result_tb or initial_tb,)
55
56
57 def fake_exc_info(exc_info, filename, lineno, tb_back=None):
58     """Helper for `translate_exception`."""
59     exc_type, exc_value, tb = exc_info
60
61     # figure the real context out
62     if tb is not None:
63         real_locals = tb.tb_frame.f_locals.copy()
64         ctx = real_locals.get('context')
65         if ctx:
66             locals = ctx.get_all()
67         else:
68             locals = {}
69         for name, value in real_locals.iteritems():
70             if name.startswith('l_') and value is not missing:
71                 locals[name[2:]] = value
72
73         # if there is a local called __jinja_exception__, we get
74         # rid of it to not break the debug functionality.
75         locals.pop('__jinja_exception__', None)
76     else:
77         locals = {}
78
79     # assamble fake globals we need
80     globals = {
81         '__name__':             filename,
82         '__file__':             filename,
83         '__jinja_exception__':  exc_info[:2]
84     }
85
86     # and fake the exception
87     code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
88                    '__jinja_exception__[1]', filename, 'exec')
89
90     # if it's possible, change the name of the code.  This won't work
91     # on some python environments such as google appengine
92     try:
93         if tb is None:
94             location = 'template'
95         else:
96             function = tb.tb_frame.f_code.co_name
97             if function == 'root':
98                 location = 'top-level template code'
99             elif function.startswith('block_'):
100                 location = 'block "%s"' % function[6:]
101             else:
102                 location = 'template'
103         code = CodeType(0, code.co_nlocals, code.co_stacksize,
104                         code.co_flags, code.co_code, code.co_consts,
105                         code.co_names, code.co_varnames, filename,
106                         location, code.co_firstlineno,
107                         code.co_lnotab, (), ())
108     except:
109         pass
110
111     # execute the code and catch the new traceback
112     try:
113         exec code in globals, locals
114     except:
115         exc_info = sys.exc_info()
116         new_tb = exc_info[2].tb_next
117
118     # now we can patch the exc info accordingly
119     if tb_set_next is not None:
120         if tb_back is not None:
121             tb_set_next(tb_back, new_tb)
122         if tb is not None:
123             tb_set_next(new_tb, tb.tb_next)
124
125     # return without this frame
126     return exc_info[:2] + (new_tb,)
127
128
129 def _init_ugly_crap():
130     """This function implements a few ugly things so that we can patch the
131     traceback objects.  The function returned allows resetting `tb_next` on
132     any python traceback object.
133     """
134     import ctypes
135     from types import TracebackType
136
137     # figure out side of _Py_ssize_t
138     if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
139         _Py_ssize_t = ctypes.c_int64
140     else:
141         _Py_ssize_t = ctypes.c_int
142
143     # regular python
144     class _PyObject(ctypes.Structure):
145         pass
146     _PyObject._fields_ = [
147         ('ob_refcnt', _Py_ssize_t),
148         ('ob_type', ctypes.POINTER(_PyObject))
149     ]
150
151     # python with trace
152     if object.__basicsize__ != ctypes.sizeof(_PyObject):
153         class _PyObject(ctypes.Structure):
154             pass
155         _PyObject._fields_ = [
156             ('_ob_next', ctypes.POINTER(_PyObject)),
157             ('_ob_prev', ctypes.POINTER(_PyObject)),
158             ('ob_refcnt', _Py_ssize_t),
159             ('ob_type', ctypes.POINTER(_PyObject))
160         ]
161
162     class _Traceback(_PyObject):
163         pass
164     _Traceback._fields_ = [
165         ('tb_next', ctypes.POINTER(_Traceback)),
166         ('tb_frame', ctypes.POINTER(_PyObject)),
167         ('tb_lasti', ctypes.c_int),
168         ('tb_lineno', ctypes.c_int)
169     ]
170
171     def tb_set_next(tb, next):
172         """Set the tb_next attribute of a traceback object."""
173         if not (isinstance(tb, TracebackType) and
174                 (next is None or isinstance(next, TracebackType))):
175             raise TypeError('tb_set_next arguments must be traceback objects')
176         obj = _Traceback.from_address(id(tb))
177         if tb.tb_next is not None:
178             old = _Traceback.from_address(id(tb.tb_next))
179             old.ob_refcnt -= 1
180         if next is None:
181             obj.tb_next = ctypes.POINTER(_Traceback)()
182         else:
183             next = _Traceback.from_address(id(next))
184             next.ob_refcnt += 1
185             obj.tb_next = ctypes.pointer(next)
186
187     return tb_set_next
188
189
190 # try to get a tb_set_next implementation
191 try:
192     from jinja2._speedups import tb_set_next
193 except ImportError:
194     try:
195         tb_set_next = _init_ugly_crap()
196     except:
197         tb_set_next = None
198 del _init_ugly_crap