From ba3757ba35d903543cb498aedf2c7fdaf6cba3b5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 16 Apr 2008 19:43:16 +0200 Subject: [PATCH] added new python only debug hack --HG-- branch : trunk --- examples/cycle.py | 13 ++ examples/translate.py | 1 + jinja2/_debugger.c | 65 ------ jinja2/_native.py | 85 ------- jinja2/_speedups.c | 499 ------------------------------------------ jinja2/compiler.py | 21 +- jinja2/debug.py | 129 +++++++++++ jinja2/environment.py | 35 ++- jinja2/loaders.py | 4 +- 9 files changed, 189 insertions(+), 663 deletions(-) create mode 100644 examples/cycle.py delete mode 100644 jinja2/_debugger.c delete mode 100644 jinja2/_native.py delete mode 100644 jinja2/_speedups.c create mode 100644 jinja2/debug.py diff --git a/examples/cycle.py b/examples/cycle.py new file mode 100644 index 0000000..73dd632 --- /dev/null +++ b/examples/cycle.py @@ -0,0 +1,13 @@ +from jinja2 import Environment + + +env = Environment(line_statement_prefix="#", variable_start_string="${", variable_end_string="}") + + +print env.from_string("""\ +\ +""").render() diff --git a/examples/translate.py b/examples/translate.py index d5f792f..9043a21 100644 --- a/examples/translate.py +++ b/examples/translate.py @@ -1,6 +1,7 @@ from jinja2 import Environment print Environment().from_string("""\ +{{ foo.bar }} {% trans %}Hello {{ user }}!{% endtrans %} {% trans count=users|count %}{{ count }} user{% pluralize %}{{ count }} users{% endtrans %} """).render(user="someone") diff --git a/jinja2/_debugger.c b/jinja2/_debugger.c deleted file mode 100644 index 50462e1..0000000 --- a/jinja2/_debugger.c +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Jinja Extended Debugger - * ~~~~~~~~~~~~~~~~~~~~~~~ - * - * this module allows the jinja debugger to set the tb_next flag - * on traceback objects. This is required to inject a traceback into - * another one. - * - * For better windows support (not everybody has a visual studio 2003 - * at home) it would be a good thing to have a ctypes implementation, but - * because the struct is not exported there is currently no sane way. - * - * :copyright: 2007 by Armin Ronacher. - * :license: BSD, see LICENSE for more details. - */ - -#include - - -/** - * set the tb_next attribute of a traceback object - */ -static PyObject * -tb_set_next(PyObject *self, PyObject *args) -{ - PyTracebackObject *tb, *old; - PyObject *next; - - if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next)) - return NULL; - if (next == Py_None) - next = NULL; - else if (!PyTraceBack_Check(next)) { - PyErr_SetString(PyExc_TypeError, - "tb_set_next arg 2 must be traceback or None"); - return NULL; - } - else - Py_INCREF(next); - - old = tb->tb_next; - tb->tb_next = (PyTracebackObject*)next; - Py_XDECREF(old); - - Py_INCREF(Py_None); - return Py_None; -} - - -static PyMethodDef module_methods[] = { - {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS, - "Set the tb_next member of a traceback object."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ -#define PyMODINIT_FUNC void -#endif -PyMODINIT_FUNC -init_debugger(void) -{ - PyObject *module = Py_InitModule3("jinja._debugger", module_methods, ""); - if (!module) - return; -} diff --git a/jinja2/_native.py b/jinja2/_native.py deleted file mode 100644 index 1d9747a..0000000 --- a/jinja2/_native.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja._native - ~~~~~~~~~~~~~ - - This module implements the native base classes in case of not - having a jinja with the _speedups module compiled. - - Note that if you change semantics here you have to edit the - _speedups.c file to in order to support those changes for jinja - setups with enabled speedup module. - - :copyright: 2007 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from jinja.datastructure import Deferred -from jinja.utils import deque - - -class BaseContext(object): - - def __init__(self, undefined_singleton, globals, initial): - self._undefined_singleton = undefined_singleton - self.current = current = {} - self._stack = deque([current, initial, globals]) - self.globals = globals - self.initial = initial - - self._push = self._stack.appendleft - self._pop = self._stack.popleft - - def stack(self): - return list(self._stack)[::-1] - stack = property(stack) - - def pop(self): - """Pop the last layer from the stack and return it.""" - rv = self._pop() - self.current = self._stack[0] - return rv - - def push(self, data=None): - """ - Push one layer to the stack and return it. Layer must be - a dict or omitted. - """ - data = data or {} - self._push(data) - self.current = self._stack[0] - return data - - def __getitem__(self, name): - """ - Resolve one item. Restrict the access to internal variables - such as ``'::cycle1'``. Resolve deferreds. - """ - if not name.startswith('::'): - for d in self._stack: - if name in d: - rv = d[name] - if rv.__class__ is Deferred: - rv = rv(self, name) - # never touch the globals! - if d is self.globals: - self.initial[name] = rv - else: - d[name] = rv - return rv - return self._undefined_singleton - - def __setitem__(self, name, value): - """Set a variable in the outermost layer.""" - self.current[name] = value - - def __delitem__(self, name): - """Delete a variable in the outermost layer.""" - if name in self.current: - del self.current[name] - - def __contains__(self, name): - """ Check if the context contains a given variable.""" - for layer in self._stack: - if name in layer: - return True - return False diff --git a/jinja2/_speedups.c b/jinja2/_speedups.c deleted file mode 100644 index 198b7a0..0000000 --- a/jinja2/_speedups.c +++ /dev/null @@ -1,499 +0,0 @@ -/** - * jinja._speedups - * ~~~~~~~~~~~~~~~ - * - * This module implements the BaseContext, a c implementation of the - * Context baseclass. If this extension is not compiled the datastructure - * module implements a class in python. - * - * Note that if you change semantics here you have to edit the _native.py - * to in order to support those changes for jinja setups without the - * speedup module too. - * - * :copyright: 2007 by Armin Ronacher. - * :license: BSD, see LICENSE for more details. - */ - -#include -#include - -/* Set by init_constants to real values */ -static PyObject *Deferred; - -/** - * Internal struct used by BaseContext to store the - * stacked namespaces. - */ -struct StackLayer { - PyObject *dict; /* current value, a dict */ - struct StackLayer *prev; /* lower struct layer or NULL */ -}; - -/** - * BaseContext python class. - */ -typedef struct { - PyObject_HEAD - struct StackLayer *globals; /* the dict for the globals */ - struct StackLayer *initial; /* initial values */ - struct StackLayer *current; /* current values */ - long stacksize; /* current size of the stack */ - PyObject *undefined_singleton; /* the singleton returned on missing values */ -} BaseContext; - -/** - * Called by init_speedups in order to retrieve references - * to some exceptions and classes defined in jinja python modules - */ -static int -init_constants(void) -{ - PyObject *datastructure = PyImport_ImportModule("jinja.datastructure"); - if (!datastructure) - return 0; - Deferred = PyObject_GetAttrString(datastructure, "Deferred"); - Py_DECREF(datastructure); - return 1; -} - -/** - * GC Helper - */ -static int -BaseContext_clear(BaseContext *self) -{ - struct StackLayer *current = self->current, *tmp; - while (current) { - tmp = current; - Py_XDECREF(current->dict); - current->dict = NULL; - current = tmp->prev; - PyMem_Free(tmp); - } - self->current = NULL; - return 0; -} - -/** - * Deallocator for BaseContext. - * - * Frees the memory for the stack layers before freeing the object. - */ -static void -BaseContext_dealloc(BaseContext *self) -{ - BaseContext_clear(self); - self->ob_type->tp_free((PyObject*)self); -} - -/** - * GC Helper - */ -static int -BaseContext_traverse(BaseContext *self, visitproc visit, void *args) -{ - int vret; - struct StackLayer *layer = self->current; - - while (layer) { - vret = visit(layer->dict, args); - if (vret != 0) - return vret; - layer = layer->prev; - } - - return 0; -} - -/** - * Initializes the BaseContext. - * - * Like the native python class it takes a reference to the undefined - * singleton which will be used for undefined values. - * The other two arguments are the global namespace and the initial - * namespace which usually contains the values passed to the render - * function of the template. Both must be dicts. - */ -static int -BaseContext_init(BaseContext *self, PyObject *args, PyObject *kwds) -{ - PyObject *undefined = NULL, *globals = NULL, *initial = NULL; - - if (!PyArg_ParseTuple(args, "OOO", &undefined, &globals, &initial)) - return -1; - if (!PyDict_Check(globals) || !PyDict_Check(initial)) { - PyErr_SetString(PyExc_TypeError, "stack layers must be dicts."); - return -1; - } - - self->current = PyMem_Malloc(sizeof(struct StackLayer)); - self->current->prev = NULL; - self->current->dict = PyDict_New(); - if (!self->current->dict) - return -1; - - self->initial = PyMem_Malloc(sizeof(struct StackLayer)); - self->initial->prev = NULL; - self->initial->dict = initial; - Py_INCREF(initial); - self->current->prev = self->initial; - - self->globals = PyMem_Malloc(sizeof(struct StackLayer)); - self->globals->prev = NULL; - self->globals->dict = globals; - Py_INCREF(globals); - self->initial->prev = self->globals; - - self->undefined_singleton = undefined; - Py_INCREF(undefined); - - self->stacksize = 3; - return 0; -} - -/** - * Pop the highest layer from the stack and return it - */ -static PyObject* -BaseContext_pop(BaseContext *self) -{ - PyObject *result; - struct StackLayer *tmp = self->current; - - if (self->stacksize <= 3) { - PyErr_SetString(PyExc_IndexError, "stack too small."); - return NULL; - } - result = self->current->dict; - assert(result); - self->current = tmp->prev; - PyMem_Free(tmp); - self->stacksize--; - /* Took the reference to result from the struct. */ - return result; -} - -/** - * Push a new layer to the stack and return it. If no parameter - * is provided an empty dict is created. Otherwise the dict passed - * to it is used as new layer. - */ -static PyObject* -BaseContext_push(BaseContext *self, PyObject *args) -{ - PyObject *value = NULL; - struct StackLayer *new; - - if (!PyArg_ParseTuple(args, "|O:push", &value)) - return NULL; - if (!value) { - value = PyDict_New(); - if (!value) - return NULL; - } - else if (!PyDict_Check(value)) { - PyErr_SetString(PyExc_TypeError, "dict required."); - return NULL; - } - else - Py_INCREF(value); - new = PyMem_Malloc(sizeof(struct StackLayer)); - if (!new) { - Py_DECREF(value); - return NULL; - } - new->dict = value; - new->prev = self->current; - self->current = new; - self->stacksize++; - Py_INCREF(value); - return value; -} - -/** - * Getter that creates a list representation of the internal - * stack. Used for compatibility with the native python implementation. - */ -static PyObject* -BaseContext_getstack(BaseContext *self, void *closure) -{ - int idx = 0; - struct StackLayer *current = self->current; - PyObject *result = PyList_New(self->stacksize); - if (!result) - return NULL; - while (current) { - Py_INCREF(current->dict); - PyList_SET_ITEM(result, idx++, current->dict); - current = current->prev; - } - PyList_Reverse(result); - return result; -} - -/** - * Getter that returns a reference to the current layer in the context. - */ -static PyObject* -BaseContext_getcurrent(BaseContext *self, void *closure) -{ - Py_INCREF(self->current->dict); - return self->current->dict; -} - -/** - * Getter that returns a reference to the initial layer in the context. - */ -static PyObject* -BaseContext_getinitial(BaseContext *self, void *closure) -{ - Py_INCREF(self->initial->dict); - return self->initial->dict; -} - -/** - * Getter that returns a reference to the global layer in the context. - */ -static PyObject* -BaseContext_getglobals(BaseContext *self, void *closure) -{ - Py_INCREF(self->globals->dict); - return self->globals->dict; -} - -/** - * Implements the context lookup. - * - * This works exactly like the native implementation but a lot - * faster. It disallows access to internal names (names that start - * with "::") and resolves Deferred values. - */ -static PyObject* -BaseContext_getitem(BaseContext *self, PyObject *item) -{ - PyObject *result; - char *name = NULL; - int isdeferred; - struct StackLayer *current = self->current; - - /* allow unicode keys as long as they are ascii keys */ - if (PyUnicode_CheckExact(item)) { - item = PyUnicode_AsASCIIString(item); - if (!item) - goto missing; - } - else if (!PyString_Check(item)) - goto missing; - - /* disallow access to internal jinja values */ - name = PyString_AS_STRING(item); - if (name[0] == ':' && name[1] == ':') - goto missing; - - while (current) { - /* GetItemString just builds a new string from "name" again... */ - result = PyDict_GetItem(current->dict, item); - if (!result) { - current = current->prev; - continue; - } - isdeferred = PyObject_IsInstance(result, Deferred); - if (isdeferred == -1) - return NULL; - else if (isdeferred) { - PyObject *namespace; - PyObject *resolved = PyObject_CallFunctionObjArgs( - result, self, item, NULL); - if (!resolved) - return NULL; - - /* never touch the globals */ - if (current == self->globals) - namespace = self->initial->dict; - else - namespace = current->dict; - if (PyDict_SetItem(namespace, item, resolved) < 0) - return NULL; - Py_INCREF(resolved); - return resolved; - } - Py_INCREF(result); - return result; - } - -missing: - Py_INCREF(self->undefined_singleton); - return self->undefined_singleton; -} - -/** - * Check if the context contains a given value. - */ -static int -BaseContext_contains(BaseContext *self, PyObject *item) -{ - char *name; - struct StackLayer *current = self->current; - - /* allow unicode objects as keys as long as they are ASCII */ - if (PyUnicode_CheckExact(item)) { - item = PyUnicode_AsASCIIString(item); - if (!item) - return 0; - } - else if (!PyString_Check(item)) - return 0; - - name = PyString_AS_STRING(item); - if (name[0] == ':' && name[1] == ':') - return 0; - - while (current) { - /* XXX: for 2.4 and newer, use PyDict_Contains */ - if (!PyMapping_HasKey(current->dict, item)) { - current = current->prev; - continue; - } - return 1; - } - - return 0; -} - -/** - * Set an value in the highest layer or delete one. - */ -static int -BaseContext_setitem(BaseContext *self, PyObject *item, PyObject *value) -{ - /* allow unicode objects as keys as long as they are ASCII */ - if (PyUnicode_CheckExact(item)) { - item = PyUnicode_AsASCIIString(item); - if (!item) { - PyErr_Clear(); - goto error; - } - } - else if (!PyString_Check(item)) - goto error; - if (!value) - return PyDict_DelItem(self->current->dict, item); - return PyDict_SetItem(self->current->dict, item, value); - -error: - PyErr_SetString(PyExc_TypeError, "expected string argument"); - return -1; -} - - -static PyGetSetDef BaseContext_getsetters[] = { - {"stack", (getter)BaseContext_getstack, NULL, - "a read only copy of the internal stack", NULL}, - {"current", (getter)BaseContext_getcurrent, NULL, - "reference to the current layer on the stack", NULL}, - {"initial", (getter)BaseContext_getinitial, NULL, - "reference to the initial layer on the stack", NULL}, - {"globals", (getter)BaseContext_getglobals, NULL, - "reference to the global layer on the stack", NULL}, - {NULL} /* Sentinel */ -}; - -static PyMethodDef BaseContext_methods[] = { - {"pop", (PyCFunction)BaseContext_pop, METH_NOARGS, - "ctx.pop() -> dict\n\n" - "Pop the last layer from the stack and return it."}, - {"push", (PyCFunction)BaseContext_push, METH_VARARGS, - "ctx.push([layer]) -> layer\n\n" - "Push one layer to the stack. Layer must be a dict " - "or omitted."}, - {NULL} /* Sentinel */ -}; - -static PySequenceMethods BaseContext_as_sequence = { - 0, /* sq_length */ - 0, /* sq_concat */ - 0, /* sq_repeat */ - 0, /* sq_item */ - 0, /* sq_slice */ - 0, /* sq_ass_item */ - 0, /* sq_ass_slice */ - (objobjproc)BaseContext_contains,/* sq_contains */ - 0, /* sq_inplace_concat */ - 0 /* sq_inplace_repeat */ -}; - -static PyMappingMethods BaseContext_as_mapping = { - NULL, - (binaryfunc)BaseContext_getitem, - (objobjargproc)BaseContext_setitem -}; - -static PyTypeObject BaseContextType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "jinja._speedups.BaseContext", /* tp_name */ - sizeof(BaseContext), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)BaseContext_dealloc,/* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - &BaseContext_as_sequence, /* tp_as_sequence */ - &BaseContext_as_mapping, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, - /*tp_flags*/ - "", /* tp_doc */ - (traverseproc)BaseContext_traverse, /* tp_traverse */ - (inquiry)BaseContext_clear, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - BaseContext_methods, /* tp_methods */ - 0, /* tp_members */ - BaseContext_getsetters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)BaseContext_init, /* tp_init */ - 0, /* tp_alloc */ - 0 /* tp_new */ -}; - -static PyMethodDef module_methods[] = { - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ -#define PyMODINIT_FUNC void -#endif -PyMODINIT_FUNC -init_speedups(void) -{ - PyObject *module; - - BaseContextType.tp_new = (newfunc)PyType_GenericNew; - if (PyType_Ready(&BaseContextType) < 0) - return; - - if (!init_constants()) - return; - - module = Py_InitModule3("_speedups", module_methods, ""); - if (!module) - return; - - Py_INCREF(&BaseContextType); - PyModule_AddObject(module, "BaseContext", (PyObject*)&BaseContextType); -} diff --git a/jinja2/compiler.py b/jinja2/compiler.py index abaf861..06017f8 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -247,6 +247,12 @@ class CodeGenerator(NodeVisitor): # more optimizations. self.has_known_extends = False + # the current line number + self.lineno = 1 + + # the debug information + self.debug_info = [] + # the number of new lines before the next write() self._new_lines = 0 @@ -300,6 +306,7 @@ class CodeGenerator(NodeVisitor): if self._new_lines: if not self._first_write: self.stream.write('\n' * self._new_lines) + self.lineno += self._new_lines self._first_write = False self.stream.write(' ' * self._indentation) self._new_lines = 0 @@ -314,9 +321,7 @@ class CodeGenerator(NodeVisitor): """Add one or more newlines before the next write.""" self._new_lines = max(self._new_lines, 1 + extra) if node is not None and node.lineno != self._last_line: - self.write('# line: %s' % node.lineno) - self._new_lines = 1 - self._last_line = node.lineno + self.debug_info.append((node.lineno, self.lineno)) def signature(self, node, frame, have_comma=True, extra_kwargs=None): """Writes a function call to the stream for the current node. @@ -439,7 +444,7 @@ class CodeGenerator(NodeVisitor): def visit_Template(self, node, frame=None): assert frame is None, 'no root frame allowed' self.writeline('from jinja2.runtime import *') - self.writeline('filename = %r' % self.filename) + self.writeline('name = %r' % self.filename) # do we have an extends tag at all? If not, we can save some # overhead by just not processing any inheritance code. @@ -499,7 +504,13 @@ class CodeGenerator(NodeVisitor): self.blockvisit(block.body, block_frame) self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x) - for x in self.blocks), extra=1) + for x in self.blocks), + extra=1) + + # add a function that returns the debug info + self.writeline('def get_debug_info():', extra=1) + self.indent() + self.writeline('return %r' % self.debug_info) def visit_Block(self, node, frame): """Call a block and register it for the template.""" diff --git a/jinja2/debug.py b/jinja2/debug.py new file mode 100644 index 0000000..909a852 --- /dev/null +++ b/jinja2/debug.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +""" + jinja2.debug + ~~~~~~~~~~~~ + + Implements the debug interface for Jinja. + + :copyright: Copyright 2008 by Armin Ronacher. + :license: BSD. +""" +import re +import sys +from jinja2.exceptions import TemplateNotFound + + +_line_re = re.compile(r'^\s*# line: (\d+)\s*$') + + +def fake_exc_info(exc_info, filename, lineno, tb_back=None): + exc_type, exc_value, tb = exc_info + + # figure the real context out + real_locals = tb.tb_frame.f_locals.copy() + locals = dict(real_locals.get('context', {})) + for name, value in real_locals.iteritems(): + if name.startswith('l_'): + locals[name[2:]] = value + + # assamble fake globals we need + globals = { + '__name__': filename, + '__file__': filename, + '__jinja_exception__': exc_info[:2] + } + + # and fake the exception + code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' + + '__jinja_exception__[1]', filename, 'exec') + try: + exec code in globals, locals + except: + exc_info = sys.exc_info() + + # 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, exc_info[2]) + if tb is not None: + tb_set_next(exc_info[2].tb_next, tb.tb_next) + return exc_info + + +def translate_exception(exc_info): + result_tb = prev_tb = None + initial_tb = tb = exc_info[2] + + while tb is not None: + template = tb.tb_frame.f_globals.get('__jinja_template__') + if template is not None: + # TODO: inject faked exception with correct line number + 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 + + return exc_info[:2] + (result_tb or initial_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. + """ + 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 object.__basicsize__ != ctypes.sizeof(_PyObject): + 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): + if not (isinstance(tb, TracebackType) and + isinstance(next, TracebackType)): + raise TypeError('tb_set_next arguments must be traceback objects') + obj = _Traceback.from_address(id(tb)) + obj.tb_next = ctypes.pointer(_Traceback.from_address(id(next))) + + return tb_set_next + + +# no ctypes, no fun +try: + tb_set_next = _init_ugly_crap() +except: + tb_set_next = None +del _init_ugly_crap diff --git a/jinja2/environment.py b/jinja2/environment.py index 60a7a2b..47d99e3 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -8,11 +8,13 @@ :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import sys from jinja2.lexer import Lexer from jinja2.parser import Parser from jinja2.optimizer import optimize from jinja2.compiler import generate from jinja2.runtime import Undefined +from jinja2.debug import translate_exception from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE @@ -114,15 +116,15 @@ class Environment(object): except (TypeError, LookupError): return self.undefined(obj, argument) - def parse(self, source, filename=None): + def parse(self, source, name=None): """Parse the sourcecode and return the abstract syntax tree. This tree of nodes is used by the compiler to convert the template into executable source- or bytecode. """ - parser = Parser(self, source, filename) + parser = Parser(self, source, name) return parser.parse() - def lex(self, source, filename=None): + def lex(self, source, name=None): """Lex the given sourcecode and return a generator that yields tokens. The stream returned is not usable for Jinja but can be used if Jinja templates should be processed by other tools (for example @@ -130,7 +132,7 @@ class Environment(object): The tuples are returned in the form ``(lineno, token, value)``. """ - return self.lexer.tokeniter(source, filename) + return self.lexer.tokeniter(source, name) def compile(self, source, filename=None, raw=False, globals=None): """Compile a node or source.""" @@ -139,7 +141,6 @@ class Environment(object): if self.optimized: node = optimize(source, self, globals or {}) source = generate(node, self, filename) - print source if raw: return source if filename is None: @@ -183,11 +184,16 @@ class Template(object): namespace = {'environment': environment} exec code in namespace self.environment = environment - self.name = namespace['filename'] + self.name = namespace['name'] + self.filename = code.co_filename self.root_render_func = namespace['root'] self.blocks = namespace['blocks'] self.globals = globals + # debug helpers + self._get_debug_info = namespace['get_debug_info'] + namespace['__jinja_template__'] = self + def render(self, *args, **kwargs): return u''.join(self.generate(*args, **kwargs)) @@ -214,7 +220,22 @@ class Template(object): gen = self.root_render_func(dict(self.globals, **context)) # skip the first item which is a reference to the context gen.next() - return gen + + try: + for event in gen: + yield event + except: + exc_info = translate_exception(sys.exc_info()) + raise exc_info[0], exc_info[1], exc_info[2] + + def get_corresponding_lineno(self, lineno): + """Return the source line number of a line number in the + generated bytecode as they are not in sync. + """ + for template_line, code_line in reversed(self._get_debug_info()): + if code_line <= lineno: + return template_line + return 1 def __repr__(self): return '<%s %r>' % ( diff --git a/jinja2/loaders.py b/jinja2/loaders.py index 4d1c335..6c03ad3 100644 --- a/jinja2/loaders.py +++ b/jinja2/loaders.py @@ -19,8 +19,8 @@ class BaseLoader(object): def get_source(self, environment, template): raise TemplateNotFound() - def load(self, environment, template, globals=None): - source, filename = self.get_source(environment, template) + def load(self, environment, name, globals=None): + source, filename = self.get_source(environment, name) code = environment.compile(source, filename, globals=globals) return Template(environment, code, globals or {}) -- 2.26.2