added new python only debug hack
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 16 Apr 2008 17:43:16 +0000 (19:43 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 16 Apr 2008 17:43:16 +0000 (19:43 +0200)
--HG--
branch : trunk

examples/cycle.py [new file with mode: 0644]
examples/translate.py
jinja2/_debugger.c [deleted file]
jinja2/_native.py [deleted file]
jinja2/_speedups.c [deleted file]
jinja2/compiler.py
jinja2/debug.py [new file with mode: 0644]
jinja2/environment.py
jinja2/loaders.py

diff --git a/examples/cycle.py b/examples/cycle.py
new file mode 100644 (file)
index 0000000..73dd632
--- /dev/null
@@ -0,0 +1,13 @@
+from jinja2 import Environment
+
+
+env = Environment(line_statement_prefix="#", variable_start_string="${", variable_end_string="}")
+
+
+print env.from_string("""\
+<ul>
+# for item in range(10)
+    <li class="${loop.cycle('odd', 'even')}">${item}</li>
+# endfor
+</ul>\
+""").render()
index d5f792f8234e0637c564a4d33bdfd44f89e4d20b..9043a2112540fd64de78ae406844995d38693e0b 100644 (file)
@@ -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 (file)
index 50462e1..0000000
+++ /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 <Python.h>
-
-
-/**
- * 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 (file)
index 1d9747a..0000000
+++ /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 (file)
index 198b7a0..0000000
+++ /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 <Python.h>
-#include <structmember.h>
-
-/* 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);
-}
index abaf861652d4f14262fd49ddfd59c3452c117f36..06017f808b4b91a4c85d0f38cb6fc24e0660610b 100644 (file)
@@ -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 (file)
index 0000000..909a852
--- /dev/null
@@ -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
index 60a7a2be4e6c6c9baf40be5c9e47ae35699db69a..47d99e38ea85700896d4af1526d3ea830b63bacf 100644 (file)
@@ -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>' % (
index 4d1c335e47eee9fa46927890aab97ec84653be4c..6c03ad37dcec92ded78ef106c4b8c360f63337da 100644 (file)
@@ -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 {})