blob: 909a8527ba9c9d9160d458caa1068711e926ac1e [file] [log] [blame]
Armin Ronacherba3757b2008-04-16 19:43:16 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.debug
4 ~~~~~~~~~~~~
5
6 Implements the debug interface for Jinja.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: BSD.
10"""
11import re
12import sys
13from jinja2.exceptions import TemplateNotFound
14
15
16_line_re = re.compile(r'^\s*# line: (\d+)\s*$')
17
18
19def fake_exc_info(exc_info, filename, lineno, tb_back=None):
20 exc_type, exc_value, tb = exc_info
21
22 # figure the real context out
23 real_locals = tb.tb_frame.f_locals.copy()
24 locals = dict(real_locals.get('context', {}))
25 for name, value in real_locals.iteritems():
26 if name.startswith('l_'):
27 locals[name[2:]] = value
28
29 # assamble fake globals we need
30 globals = {
31 '__name__': filename,
32 '__file__': filename,
33 '__jinja_exception__': exc_info[:2]
34 }
35
36 # and fake the exception
37 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
38 '__jinja_exception__[1]', filename, 'exec')
39 try:
40 exec code in globals, locals
41 except:
42 exc_info = sys.exc_info()
43
44 # now we can patch the exc info accordingly
45 if tb_set_next is not None:
46 if tb_back is not None:
47 tb_set_next(tb_back, exc_info[2])
48 if tb is not None:
49 tb_set_next(exc_info[2].tb_next, tb.tb_next)
50 return exc_info
51
52
53def translate_exception(exc_info):
54 result_tb = prev_tb = None
55 initial_tb = tb = exc_info[2]
56
57 while tb is not None:
58 template = tb.tb_frame.f_globals.get('__jinja_template__')
59 if template is not None:
60 # TODO: inject faked exception with correct line number
61 lineno = template.get_corresponding_lineno(tb.tb_lineno)
62 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
63 lineno, prev_tb)[2]
64 if result_tb is None:
65 result_tb = tb
66 prev_tb = tb
67 tb = tb.tb_next
68
69 return exc_info[:2] + (result_tb or initial_tb,)
70
71
72def _init_ugly_crap():
73 """This function implements a few ugly things so that we can patch the
74 traceback objects. The function returned allows resetting `tb_next` on
75 any python traceback object.
76 """
77 import ctypes
78 from types import TracebackType
79
80 # figure out side of _Py_ssize_t
81 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
82 _Py_ssize_t = ctypes.c_int64
83 else:
84 _Py_ssize_t = ctypes.c_int
85
86 # regular python
87 class _PyObject(ctypes.Structure):
88 pass
89 _PyObject._fields_ = [
90 ('ob_refcnt', _Py_ssize_t),
91 ('ob_type', ctypes.POINTER(_PyObject))
92 ]
93
94 # python with trace
95 if object.__basicsize__ != ctypes.sizeof(_PyObject):
96 class _PyObject(ctypes.Structure):
97 pass
98 _PyObject._fields_ = [
99 ('_ob_next', ctypes.POINTER(_PyObject)),
100 ('_ob_prev', ctypes.POINTER(_PyObject)),
101 ('ob_refcnt', _Py_ssize_t),
102 ('ob_type', ctypes.POINTER(_PyObject))
103 ]
104
105 class _Traceback(_PyObject):
106 pass
107 _Traceback._fields_ = [
108 ('tb_next', ctypes.POINTER(_Traceback)),
109 ('tb_frame', ctypes.POINTER(_PyObject)),
110 ('tb_lasti', ctypes.c_int),
111 ('tb_lineno', ctypes.c_int)
112 ]
113
114 def tb_set_next(tb, next):
115 if not (isinstance(tb, TracebackType) and
116 isinstance(next, TracebackType)):
117 raise TypeError('tb_set_next arguments must be traceback objects')
118 obj = _Traceback.from_address(id(tb))
119 obj.tb_next = ctypes.pointer(_Traceback.from_address(id(next)))
120
121 return tb_set_next
122
123
124# no ctypes, no fun
125try:
126 tb_set_next = _init_ugly_crap()
127except:
128 tb_set_next = None
129del _init_ugly_crap