a clean restart
--HG--
branch : trunk
rename : jinja/__init__.py => jinja2/__init__.py
rename : jinja/_debugger.c => jinja2/_debugger.c
rename : jinja/_native.py => jinja2/_native.py
rename : jinja/_speedups.c => jinja2/_speedups.c
rename : jinja/constants.py => jinja2/constants.py
rename : jinja/contrib/__init__.py => jinja2/contrib/__init__.py
rename : jinja/contrib/_djangosupport.py => jinja2/contrib/_djangosupport.py
rename : jinja/contrib/djangosupport.py => jinja2/contrib/djangosupport.py
rename : jinja/datastructure.py => jinja2/datastructure.py
rename : jinja/defaults.py => jinja2/defaults.py
rename : jinja/environment.py => jinja2/environment.py
rename : jinja/exceptions.py => jinja2/exceptions.py
rename : jinja/filters.py => jinja2/filters.py
rename : jinja/lexer.py => jinja2/lexer.py
rename : jinja/loaders.py => jinja2/loaders.py
rename : jinja/nodes.py => jinja2/nodes.py
rename : jinja/parser.py => jinja2/parser.py
rename : jinja/tests.py => jinja2/tests.py
rename : jinja/translators/__init__.py => jinja2/translators/__init__.py
rename : jinja/translators/python.py => jinja2/translators/python.py
rename : jinja/utils.py => jinja2/utils.py
diff --git a/jinja2/__init__.py b/jinja2/__init__.py
new file mode 100644
index 0000000..94949c1
--- /dev/null
+++ b/jinja2/__init__.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2
+ ~~~~~~
+
+ Jinja is a `sandboxed`_ template engine written in pure Python. It
+ provides a `Django`_ like non-XML syntax and compiles templates into
+ executable python code. It's basically a combination of Django templates
+ and python code.
+
+ Nutshell
+ --------
+
+ Here a small example of a Jinja template::
+
+ {% extends 'base.html' %}
+ {% block title %}Memberlist{% endblock %}
+ {% block content %}
+ <ul>
+ {% for user in users %}
+ <li><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
+
+ Philosophy
+ ----------
+
+ Application logic is for the controller but don't try to make the life
+ for the template designer too hard by giving him too few functionality.
+
+ For more informations visit the new `jinja webpage`_ and `documentation`_.
+
+ Note
+ ----
+
+ This is the Jinja 1.0 release which is completely incompatible with the
+ old "pre 1.0" branch. The old branch will still receive security updates
+ and bugfixes but the 1.0 branch will be the only version that receives
+ support.
+
+ If you have an application that uses Jinja 0.9 and won't be updated in
+ the near future the best idea is to ship a Jinja 0.9 checkout together
+ with the application.
+
+ The `Jinja tip`_ is installable via `easy_install` with ``easy_install
+ Jinja==dev``.
+
+ .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
+ .. _Django: http://www.djangoproject.com/
+ .. _jinja webpage: http://jinja.pocoo.org/
+ .. _documentation: http://jinja.pocoo.org/documentation/index.html
+ .. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
+
+
+ :copyright: 2008 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
diff --git a/jinja2/_debugger.c b/jinja2/_debugger.c
new file mode 100644
index 0000000..50462e1
--- /dev/null
+++ b/jinja2/_debugger.c
@@ -0,0 +1,65 @@
+/**
+ * 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
new file mode 100644
index 0000000..1d9747a
--- /dev/null
+++ b/jinja2/_native.py
@@ -0,0 +1,85 @@
+# -*- 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
new file mode 100644
index 0000000..198b7a0
--- /dev/null
+++ b/jinja2/_speedups.c
@@ -0,0 +1,499 @@
+/**
+ * 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);
+}
diff --git a/jinja2/constants.py b/jinja2/constants.py
new file mode 100644
index 0000000..a0b4a63
--- /dev/null
+++ b/jinja2/constants.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.constants
+ ~~~~~~~~~~~~~~~
+
+ Various constants.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+#: list of lorem ipsum words used by the lipsum() helper function
+LOREM_IPSUM_WORDS = u'''\
+a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
+auctor augue bibendum blandit class commodo condimentum congue consectetuer
+consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
+diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
+elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
+faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
+hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
+justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
+luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
+mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
+nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
+penatibus per pharetra phasellus placerat platea porta porttitor posuere
+potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
+ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
+sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
+tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
+ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
+viverra volutpat vulputate'''
diff --git a/jinja2/contrib/__init__.py b/jinja2/contrib/__init__.py
new file mode 100644
index 0000000..1770052
--- /dev/null
+++ b/jinja2/contrib/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.contrib
+ ~~~~~~~~~~~~~
+
+ This module collections various third-party helper functions and classes
+ that are useful for frameworks etc.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
diff --git a/jinja2/contrib/_djangosupport.py b/jinja2/contrib/_djangosupport.py
new file mode 100644
index 0000000..65a192e
--- /dev/null
+++ b/jinja2/contrib/_djangosupport.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.contrib._djangosupport
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Django suport layer. This module is a metamodule, do never import it
+ directly or access any of the functions defined here.
+
+ The public interface is `jinja.contrib.djangosupport` and
+ `django.contrib.jinja`. See the docstring of `jinja.contrib.djangosupport`
+ for more details.
+
+ :copyright: 2007-2008 by Armin Ronacher, Bryan McLemore, David Cramer.
+ :license: BSD, see LICENSE for more details.
+"""
+import sys
+import warnings
+import new
+from django.conf import settings
+from django.template.context import get_standard_processors
+from django.http import HttpResponse
+from django import contrib
+
+from jinja import Environment, FileSystemLoader, ChoiceLoader
+from jinja.loaders import MemcachedFileSystemLoader
+
+
+exported = ['render_to_response', 'render_to_string', 'convert_django_filter']
+
+
+#: used environment
+env = None
+
+
+#: default filters
+DEFAULT_FILTERS = (
+ 'django.template.defaultfilters.date',
+ 'django.template.defaultfilters.timesince',
+ 'django.template.defaultfilters.linebreaks',
+ 'django.contrib.humanize.templatetags.humanize.intcomma'
+)
+
+def configure(convert_filters=DEFAULT_FILTERS, loader=None, **options):
+ """
+ Initialize the system.
+ """
+ global env
+
+ if env:
+ warnings.warn("Jinja already initialized.")
+ return
+
+ # setup environment
+ if loader is None:
+ loaders = tuple(FileSystemLoader(l) for l in settings.TEMPLATE_DIRS)
+ if not loaders:
+ loader = None
+ elif len(loaders) == 1:
+ loader = loaders[0]
+ else:
+ loader = ChoiceLoader(loaders)
+ env = Environment(loader=loader, **options)
+
+ # convert requested filters
+ for name in convert_filters:
+ env.filters[name] = convert_django_filter(name)
+
+ # import templatetags of installed apps
+ for app in settings.INSTALLED_APPS:
+ try:
+ __import__(app + '.templatetags')
+ except ImportError:
+ pass
+
+ # setup the django.contrib.jinja module
+ setup_django_module()
+
+
+def setup_django_module():
+ """
+ create a new Jinja module for django.
+ """
+ from jinja.contrib import djangosupport
+ module = contrib.jinja = sys.modules['django.contrib.jinja'] = \
+ new.module('django.contrib.jinja')
+ module.env = env
+ module.__doc__ = djangosupport.__doc__
+ module.register = Library
+ public_names = module.__all__ = ['register', 'env']
+ get_name = globals().get
+ for name in exported:
+ setattr(module, name, get_name(name))
+ public_names.append(name)
+
+
+def render_to_response(template, context={}, request=None,
+ mimetype=None):
+ """This function will take a few variables and spit out a full webpage."""
+ content = render_to_string(template, context, request)
+ if mimetype is None:
+ mimetype = settings.DEFAULT_CONTENT_TYPE
+ return HttpResponse(content, mimetype)
+
+
+def render_to_string(template, context={}, request=None):
+ """Render a template to a string."""
+ assert env is not None, 'Jinja not configured for django'
+ if request is not None:
+ context['request'] = request
+ for processor in get_standard_processors():
+ context.update(processor(request))
+ template = env.get_template(template)
+ return template.render(context)
+
+
+def convert_django_filter(f):
+ """Convert a django filter into a Jinja filter."""
+ if isinstance(f, str):
+ p = f.split('.')
+ f = getattr(__import__('.'.join(p[:-1]), None, None, ['']), p[-1])
+ def filter_factory(*args):
+ def wrapped(env, ctx, value):
+ return f(value, *args)
+ return wrapped
+ filter_factory.__name__ = f.__name__
+ filter_factory.__doc__ = getattr(f, '__doc__', None)
+ return filter_factory
+
+
+class Library(object):
+ """
+ Continues a general feel of wrapping all the registration
+ methods for easy importing.
+
+ This is available in `django.contrib.jinja` as `register`.
+
+ For more details see the docstring of the `django.contrib.jinja` module.
+ """
+ def object(func, name=None):
+ """Register a new global."""
+ if name is None:
+ name = getattr(func, '__name__')
+ env.globals[name] = func
+ return func
+ object = staticmethod(object)
+
+ def filter(func, name=None):
+ """Register a new filter function."""
+ if name is None:
+ name = func.__name__
+ env.filters[name] = func
+ return func
+ filter = staticmethod(filter)
+
+ def test(func, name):
+ """Register a new test function."""
+ if name is None:
+ name = func.__name__
+ env.tests[name] = func
+ return func
+ test = staticmethod(test)
+
+ def context_inclusion(func, template, name=None):
+ """
+ Similar to the inclusion tag from django this one expects func to be a
+ function with a similar argument list to func(context, *args, **kwargs)
+
+ It passed in the current context allowing the function to edit it or read
+ from it. the function must return a dict with which to pass into the
+ renderer. Normally expected is an altered dictionary.
+
+ Note processors are NOT ran on this context.
+ """
+ def wrapper(env, context, *args, **kwargs):
+ context = func(context.to_dict(), *args, **kwargs)
+ return render_to_string(template, context)
+ wrapper.jinja_context_callable = True
+ if name is None:
+ name = func.__name__
+ try:
+ wrapper.__name__ = func.__name__
+ wrapper.__doc__ = func.__doc__
+ except:
+ pass
+ env.globals[name] = wrapper
+ context_inclusion = staticmethod(context_inclusion)
+
+ def clean_inclusion(func, template, name=None, run_processors=False):
+ """
+ Similar to above however it won't pass the context into func().
+ Also the returned context will have the context processors run upon it.
+ """
+ def wrapper(env, context, *args, **kwargs):
+ if run_processors:
+ request = context['request']
+ else:
+ request = None
+ context = func({}, *args, **kwargs)
+ return render_to_string(template, context, request)
+ wrapper.jinja_context_callable = True
+ if name is None:
+ name = func.__name__
+ try:
+ wrapper.__name__ = func.__name__
+ wrapper.__doc__ = func.__doc__
+ except:
+ pass
+ env.globals[name] = wrapper
+ clean_inclusion = staticmethod(clean_inclusion)
diff --git a/jinja2/contrib/djangosupport.py b/jinja2/contrib/djangosupport.py
new file mode 100644
index 0000000..9898a82
--- /dev/null
+++ b/jinja2/contrib/djangosupport.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.contrib.djangosupport
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Support for the django framework. This module is quite magical because it
+ just exports one single function, the `configure` function which is used
+ to create a new Jinja environment and setup a special module called
+ `django.contrib.jinja` which exports a couple of functions useful for Jinja.
+
+ Quickstart
+ ==========
+
+ To get started execute the following code at the bottom of your settings.py
+ or in some general application file such as urls.py or a central module. The
+ only thing that matters is that it's executed right *after* the settings
+ were set up and *before* `django.contrib.jinja` is imported::
+
+ from jinja.contrib import djangosupport
+ djangosupport.configure()
+
+ What this does is setting up a Jinja environment for this django instance
+ with loaders for `TEMPLATE_DIRS` etc. It also converts a couple of default
+ django filters such as `date` and `timesince` which are not available in
+ Jinja per default. If you want to change the list you can provide others
+ by passing a list with filter import names as `convert_filters` keyword
+ argument.
+
+ All other keyword arguments are forwarded to the environment. If you want
+ to provide a loader yourself pass it a loader keyword argument.
+
+ Rendering Templates
+ ===================
+
+ To render a template you can use the functions `render_to_string` or
+ `render_to_response` from the `django.contrib.jinja` module::
+
+ from django.contrib.jinja import render_to_response
+ resp = render_to_response('Hello {{ username }}!', {
+ 'username': req.session['username']
+ }, req)
+
+ `render_to_string` and `render_to_response` take at least the name of
+ the template as argument, then the optional dict which will become the
+ context. If you also provide a request object as third argument the
+ context processors will be applied.
+
+ `render_to_response` also takes a forth parameter which can be the
+ content type which defaults to `DEFAULT_CONTENT_TYPE`.
+
+ Converting Filters
+ ==================
+
+ One of the useful objects provided by `django.contrib.jinja` is the
+ `register` object which can be used to register filters, tests and
+ global objects. You can also convert any filter django provides in
+ a Jinja filter using `convert_django_filter`::
+
+ from django.contrib.jinja import register, convert_django_filter
+ from django.template.defaultfilters import floatformat
+
+ register.filter(convert_django_filter(floatformat), 'floatformat')
+
+ Available methods on the `register` object:
+
+ ``object (obj[, name])``
+ Register a new global as name or with the object's name.
+ Returns the function object unchanged so that you can use
+ it as decorator if no name is provided.
+
+ ``filter (func[, name])``
+ Register a function as filter with the name provided or
+ the object's name as filtername.
+ Returns the function object unchanged so that you can use
+ it as decorator if no name is provided.
+
+ ``test (func[, name])``
+ Register a function as test with the name provided or the
+ object's name as testname.
+ Returns the function object unchanged so that you can use
+ it as decorator if no name is provided.
+
+ ``context_inclusion (func, template[, name])``
+ Register a function with a name provided or the func object's
+ name in the global namespace that acts as subrender function.
+
+ func is called with the callers context as dict and the
+ arguments and keywords argument of the inclusion function.
+ The function should then process the context and return a
+ new context or the same context object. Afterwards the
+ template is rendered with this context.
+
+ Example::
+
+ def add_author(context, author=None):
+ if author is not None:
+ author = Author.objects.get(name=author)
+ context['author'] = author
+ return context
+
+ register.context_inclusion(add_author, 'author_details.html',
+ 'render_author_details')
+
+ You can use it in the template like this then::
+
+ {{ render_author_details('John Doe') }}
+
+ ``clean_inclusion (func, template[, name[, run_processors]]) ``
+ Works like `context_inclusion` but doesn't use the calles
+ context but an empty context. If `run_processors` is `True`
+ it will lookup the context for a `request` object and pass
+ it to the render function to apply context processors.
+
+ :copyright: 2007 by Armin Ronacher, Bryan McLemore.
+ :license: BSD, see LICENSE for more details.
+"""
+try:
+ __import__('django')
+except ImportError:
+ raise ImportError('installed django required for djangosupport')
+else:
+ from jinja.contrib._djangosupport import configure
+
+__all__ = ['configure']
diff --git a/jinja2/datastructure.py b/jinja2/datastructure.py
new file mode 100644
index 0000000..b20acd1
--- /dev/null
+++ b/jinja2/datastructure.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.datastructure
+ ~~~~~~~~~~~~~~~~~~~~
+
+ Module that helds several data types used in the template engine.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from operator import itemgetter
+from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError
+
+
+_missing = object()
+
+
+class Token(tuple):
+ """
+ Token class.
+ """
+ __slots__ = ()
+ lineno, type, value = map(itemgetter, range(3))
+
+ def __new__(cls, lineno, type, value):
+ return tuple.__new__(cls, (lineno, type, value))
+
+ def __str__(self):
+ from jinja.lexer import keywords, reverse_operators
+ if self.type in keywords:
+ return self.type
+ elif self.type in reverse_operators:
+ return reverse_operators[self.type]
+ return self.value
+
+ def __repr__(self):
+ return 'Token(%r, %r, %r)' % (
+ self.lineno,
+ self.type,
+ self.value
+ )
+
+
+class TokenStreamIterator(object):
+ """
+ The iterator for tokenstreams. Iterate over the stream
+ until the eof token is reached.
+ """
+
+ def __init__(self, stream):
+ self._stream = stream
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ token = self._stream.current
+ if token.type == 'eof':
+ self._stream.close()
+ raise StopIteration()
+ self._stream.next()
+ return token
+
+
+class TokenStream(object):
+ """
+ A token stream wraps a generator and supports pushing tokens back.
+ It also provides some functions to expect tokens and similar stuff.
+
+ Important note: Do never push more than one token back to the
+ stream. Although the stream object won't stop you
+ from doing so, the behavior is undefined. Multiple
+ pushed tokens are only used internally!
+ """
+
+ def __init__(self, generator, filename):
+ self._next = generator.next
+ self._pushed = []
+ self.current = Token(1, 'initial', '')
+ self.filename = filename
+ self.next()
+
+ def __iter__(self):
+ return TokenStreamIterator(self)
+
+ def lineno(self):
+ """The current line number."""
+ return self.current.lineno
+ lineno = property(lineno, doc=lineno.__doc__)
+
+ def __nonzero__(self):
+ """Are we at the end of the tokenstream?"""
+ return bool(self._pushed) or self.current.type != 'eof'
+
+ eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
+
+ def push(self, token):
+ """Push a token back to the stream."""
+ self._pushed.append(token)
+
+ def skip(self, n):
+ """Got n tokens ahead."""
+ for x in xrange(n):
+ self.next()
+
+ def next(self):
+ """Go one token ahead."""
+ if self._pushed:
+ self.current = self._pushed.pop()
+ elif self.current.type != 'eof':
+ try:
+ self.current = self._next()
+ except StopIteration:
+ self.close()
+
+ def close(self):
+ """Close the stream."""
+ self.current = Token(self.current.lineno, 'eof', '')
+ self._next = None
+
+ def expect(self, token_type, token_value=_missing):
+ """Expect a given token type and return it"""
+ if self.current.type != token_type:
+ raise TemplateSyntaxError("expected token %r, got %r" %
+ (token_type, self.current.type),
+ self.current.lineno,
+ self.filename)
+ elif token_value is not _missing and \
+ self.current.value != token_value:
+ raise TemplateSyntaxError("expected %r, got %r" %
+ (token_value, self.current.value),
+ self.current.lineno,
+ self.filename)
+ try:
+ return self.current
+ finally:
+ self.next()
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
new file mode 100644
index 0000000..37473ea
--- /dev/null
+++ b/jinja2/defaults.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.defaults
+ ~~~~~~~~~~~~~~
+
+ Jinja default filters and tags.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja.filters import FILTERS as DEFAULT_FILTERS
+from jinja.tests import TESTS as DEFAULT_TESTS
+DEFAULT_NAMESPACE = {}
+
+
+__all__ = ['DEFAULT_FILTERS', 'DEFAULT_TESTS', 'DEFAULT_NAMESPACE']
diff --git a/jinja2/environment.py b/jinja2/environment.py
new file mode 100644
index 0000000..a387ffa
--- /dev/null
+++ b/jinja2/environment.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.environment
+ ~~~~~~~~~~~~~~~~~
+
+ Provides a class that holds runtime and parsing time options.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja.lexer import Lexer
+from jinja.parser import Parser
+from jinja.loaders import LoaderWrapper
+from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator
+from jinja.utils import collect_translations, get_attribute
+from jinja.exceptions import FilterNotFound, TestNotFound, \
+ SecurityException, TemplateSyntaxError
+from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+
+
+__all__ = ['Environment']
+
+
+#: minor speedup
+_getattr = getattr
+
+
+class Environment(object):
+ """
+ The Jinja environment.
+
+ The core component of Jinja is the `Environment`. It contains
+ important shared variables like configuration, filters, tests,
+ globals and others.
+ """
+
+ def __init__(self,
+ block_start_string='{%',
+ block_end_string='%}',
+ variable_start_string='{{',
+ variable_end_string='}}',
+ comment_start_string='{#',
+ comment_end_string='#}',
+ trim_blocks=False,
+ loader=None):
+ """
+ Here the possible initialization parameters:
+
+ ========================= ============================================
+ `block_start_string` the string marking the begin of a block.
+ this defaults to ``'{%'``.
+ `block_end_string` the string marking the end of a block.
+ defaults to ``'%}'``.
+ `variable_start_string` the string marking the begin of a print
+ statement. defaults to ``'{{'``.
+ `comment_start_string` the string marking the begin of a
+ comment. defaults to ``'{#'``.
+ `comment_end_string` the string marking the end of a comment.
+ defaults to ``'#}'``.
+ `trim_blocks` If this is set to ``True`` the first newline
+ after a block is removed (block, not
+ variable tag!). Defaults to ``False``.
+ `loader` The loader for this environment.
+ ========================= ============================================
+ """
+
+ # lexer / parser information
+ self.block_start_string = block_start_string
+ self.block_end_string = block_end_string
+ self.variable_start_string = variable_start_string
+ self.variable_end_string = variable_end_string
+ self.comment_start_string = comment_start_string
+ self.comment_end_string = comment_end_string
+ self.trim_blocks = trim_blocks
+
+ # other stuff
+ self.template_charset = template_charset
+ self.loader = loader
+
+ # defaults
+ self.filters = DEFAULT_FILTERS.copy()
+ self.tests = DEFAULT_TESTS.copy()
+ self.globals = DEFAULT_NAMESPACE.copy()
+
+ # create lexer
+ self.lexer = Lexer(self)
+
+ def loader(self, value):
+ """
+ Get or set the template loader.
+ """
+ self._loader = LoaderWrapper(self, value)
+ loader = property(lambda s: s._loader, loader, doc=loader.__doc__)
+
+ def parse(self, source, filename=None):
+ """
+ Parse the sourcecode and return the abstract syntax tree. This tree
+ of nodes is used by the `translators`_ to convert the template into
+ executable source- or bytecode.
+
+ .. _translators: translators.txt
+ """
+ parser = Parser(self, source, filename)
+ return parser.parse()
+
+ def lex(self, source, filename=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
+ syntax highlighting etc)
+
+ The tuples are returned in the form ``(lineno, token, value)``.
+ """
+ return self.lexer.tokeniter(source, filename)
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
new file mode 100644
index 0000000..9ec5c27
--- /dev/null
+++ b/jinja2/exceptions.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.exceptions
+ ~~~~~~~~~~~~~~~~
+
+ Jinja exceptions.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+class TemplateError(RuntimeError):
+ pass
+
+
+class TemplateNotFound(IOError, LookupError, TemplateError):
+ """
+ Raised if a template does not exist.
+ """
+
+ def __init__(self, name):
+ IOError.__init__(self, name)
+ self.name = name
+
+
+class TemplateSyntaxError(SyntaxError, TemplateError):
+ """
+ Raised to tell the user that there is a problem with the template.
+ """
+
+ def __init__(self, message, lineno, filename):
+ SyntaxError.__init__(self, message)
+ self.lineno = lineno
+ self.filename = filename
+
+
+class TemplateRuntimeError(TemplateError):
+ """
+ Raised by the template engine if a tag encountered an error when
+ rendering.
+ """
diff --git a/jinja2/filters.py b/jinja2/filters.py
new file mode 100644
index 0000000..6098b6e
--- /dev/null
+++ b/jinja2/filters.py
@@ -0,0 +1,985 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.filters
+ ~~~~~~~~~~~~~
+
+ Bundled jinja filters.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+from random import choice
+try:
+ from operator import itemgetter
+except ImportError:
+ itemgetter = lambda a: lambda b: b[a]
+from urllib import urlencode, quote
+
+
+_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
+
+
+def stringfilter(f):
+ """
+ Decorator for filters that just work on unicode objects.
+ """
+ def decorator(*args):
+ def wrapped(env, context, value):
+ nargs = list(args)
+ for idx, var in enumerate(nargs):
+ if isinstance(var, str):
+ nargs[idx] = env.to_unicode(var)
+ return f(env.to_unicode(value), *nargs)
+ return wrapped
+ try:
+ decorator.__doc__ = f.__doc__
+ decorator.__name__ = f.__name__
+ except:
+ pass
+ return decorator
+
+
+def simplefilter(f):
+ """
+ Decorator for simplifying filters. Filter arguments are passed
+ to the decorated function without environment and context. The
+ source value is the first argument. (like stringfilter but
+ without unicode conversion)
+ """
+ def decorator(*args):
+ def wrapped(env, context, value):
+ return f(value, *args)
+ return wrapped
+ try:
+ decorator.__doc__ = f.__doc__
+ decorator.__name__ = f.__name__
+ except:
+ pass
+ return decorator
+
+
+def do_replace(s, old, new, count=None):
+ """
+ Return a copy of the value with all occurrences of a substring
+ replaced with a new one. The first argument is the substring
+ that should be replaced, the second is the replacement string.
+ If the optional third argument ``count`` is given, only the first
+ ``count`` occurrences are replaced:
+
+ .. sourcecode:: jinja
+
+ {{ "Hello World"|replace("Hello", "Goodbye") }}
+ -> Goodbye World
+
+ {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
+ -> d'oh, d'oh, aaargh
+ """
+ if not isinstance(old, basestring) or \
+ not isinstance(new, basestring):
+ raise FilterArgumentError('the replace filter requires '
+ 'string replacement arguments')
+ if count is None:
+ return s.replace(old, new)
+ if not isinstance(count, (int, long)):
+ raise FilterArgumentError('the count parameter of the '
+ 'replace filter requires '
+ 'an integer')
+ return s.replace(old, new, count)
+do_replace = stringfilter(do_replace)
+
+
+def do_upper(s):
+ """
+ Convert a value to uppercase.
+ """
+ return s.upper()
+do_upper = stringfilter(do_upper)
+
+
+def do_lower(s):
+ """
+ Convert a value to lowercase.
+ """
+ return s.lower()
+do_lower = stringfilter(do_lower)
+
+
+def do_escape(attribute=False):
+ """
+ XML escape ``&``, ``<``, and ``>`` in a string of data. If the
+ optional parameter is `true` this filter will also convert
+ ``"`` to ``"``. This filter is just used if the environment
+ was configured with disabled `auto_escape`.
+
+ This method will have no effect it the value is already escaped.
+ """
+ #: because filters are cached we can make a local alias to
+ #: speed things up a bit
+ e = escape
+ def wrapped(env, context, s):
+ if isinstance(s, TemplateData):
+ return s
+ elif hasattr(s, '__html__'):
+ return s.__html__()
+ #: small speedup, do not convert to unicode if we already
+ #: have an unicode object.
+ if s.__class__ is not unicode:
+ s = env.to_unicode(s)
+ return e(s, attribute)
+ return wrapped
+
+
+def do_xmlattr(autospace=False):
+ """
+ Create an SGML/XML attribute string based on the items in a dict.
+ All values that are neither `none` nor `undefined` are automatically
+ escaped:
+
+ .. sourcecode:: html+jinja
+
+ <ul{{ {'class': 'my_list', 'missing': None,
+ 'id': 'list-%d'|format(variable)}|xmlattr }}>
+ ...
+ </ul>
+
+ Results in something like this:
+
+ .. sourcecode:: html
+
+ <ul class="my_list" id="list-42">
+ ...
+ </ul>
+
+ As you can see it automatically prepends a space in front of the item
+ if the filter returned something. You can disable this by passing
+ `false` as only argument to the filter.
+
+ *New in Jinja 1.1*
+ """
+ e = escape
+ def wrapped(env, context, d):
+ if not hasattr(d, 'iteritems'):
+ raise TypeError('a dict is required')
+ result = []
+ for key, value in d.iteritems():
+ if value not in (None, env.undefined_singleton):
+ result.append(u'%s="%s"' % (
+ e(env.to_unicode(key)),
+ e(env.to_unicode(value), True)
+ ))
+ rv = u' '.join(result)
+ if autospace:
+ rv = ' ' + rv
+ return rv
+ return wrapped
+
+
+def do_capitalize(s):
+ """
+ Capitalize a value. The first character will be uppercase, all others
+ lowercase.
+ """
+ return s.capitalize()
+do_capitalize = stringfilter(do_capitalize)
+
+
+def do_title(s):
+ """
+ Return a titlecased version of the value. I.e. words will start with
+ uppercase letters, all remaining characters are lowercase.
+ """
+ return s.title()
+do_title = stringfilter(do_title)
+
+
+def do_dictsort(case_sensitive=False, by='key'):
+ """
+ Sort a dict and yield (key, value) pairs. Because python dicts are
+ unsorted you may want to use this function to order them by either
+ key or value:
+
+ .. sourcecode:: jinja
+
+ {% for item in mydict|dictsort %}
+ sort the dict by key, case insensitive
+
+ {% for item in mydict|dicsort(true) %}
+ sort the dict by key, case sensitive
+
+ {% for item in mydict|dictsort(false, 'value') %}
+ sort the dict by key, case insensitive, sorted
+ normally and ordered by value.
+ """
+ if by == 'key':
+ pos = 0
+ elif by == 'value':
+ pos = 1
+ else:
+ raise FilterArgumentError('You can only sort by either '
+ '"key" or "value"')
+ def sort_func(value, env):
+ if isinstance(value, basestring):
+ value = env.to_unicode(value)
+ if not case_sensitive:
+ value = value.lower()
+ return value
+
+ def wrapped(env, context, value):
+ items = value.items()
+ items.sort(lambda a, b: cmp(sort_func(a[pos], env),
+ sort_func(b[pos], env)))
+ return items
+ return wrapped
+
+
+def do_default(default_value=u'', boolean=False):
+ """
+ If the value is undefined it will return the passed default value,
+ otherwise the value of the variable:
+
+ .. sourcecode:: jinja
+
+ {{ my_variable|default('my_variable is not defined') }}
+
+ This will output the value of ``my_variable`` if the variable was
+ defined, otherwise ``'my_variable is not defined'``. If you want
+ to use default with variables that evaluate to false you have to
+ set the second parameter to `true`:
+
+ .. sourcecode:: jinja
+
+ {{ ''|default('the string was empty', true) }}
+ """
+ def wrapped(env, context, value):
+ if (boolean and not value) or value in (env.undefined_singleton, None):
+ return default_value
+ return value
+ return wrapped
+
+
+def do_join(d=u''):
+ """
+ Return a string which is the concatenation of the strings in the
+ sequence. The separator between elements is an empty string per
+ default, you can define ith with the optional parameter:
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|join('|') }}
+ -> 1|2|3
+
+ {{ [1, 2, 3]|join }}
+ -> 123
+ """
+ def wrapped(env, context, value):
+ return env.to_unicode(d).join([env.to_unicode(x) for x in value])
+ return wrapped
+
+
+def do_count():
+ """
+ Return the length of the value. In case if getting an integer or float
+ it will convert it into a string an return the length of the new
+ string. If the object has no length it will of corse return 0.
+ """
+ def wrapped(env, context, value):
+ try:
+ if type(value) in (int, float, long):
+ return len(str(value))
+ return len(value)
+ except TypeError:
+ return 0
+ return wrapped
+
+
+def do_reverse():
+ """
+ Return a reversed list of the sequence filtered. You can use this
+ for example for reverse iteration:
+
+ .. sourcecode:: jinja
+
+ {% for item in seq|reverse %}
+ {{ item|e }}
+ {% endfor %}
+ """
+ def wrapped(env, context, value):
+ try:
+ return value[::-1]
+ except:
+ l = list(value)
+ l.reverse()
+ return l
+ return wrapped
+
+
+def do_center(value, width=80):
+ """
+ Centers the value in a field of a given width.
+ """
+ return value.center(width)
+do_center = stringfilter(do_center)
+
+
+def do_first():
+ """
+ Return the frist item of a sequence.
+ """
+ def wrapped(env, context, seq):
+ try:
+ return iter(seq).next()
+ except StopIteration:
+ return env.undefined_singleton
+ return wrapped
+
+
+def do_last():
+ """
+ Return the last item of a sequence.
+ """
+ def wrapped(env, context, seq):
+ try:
+ return iter(reversed(seq)).next()
+ except StopIteration:
+ return env.undefined_singleton
+ return wrapped
+
+
+def do_random():
+ """
+ Return a random item from the sequence.
+ """
+ def wrapped(env, context, seq):
+ try:
+ return choice(seq)
+ except IndexError:
+ return env.undefined_singleton
+ return wrapped
+
+
+def do_urlencode():
+ """
+ urlencode a string or directory.
+
+ .. sourcecode:: jinja
+
+ {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
+ -> foo=bar&blub=blah
+
+ {{ 'Hello World' }}
+ -> Hello%20World
+ """
+ def wrapped(env, context, value):
+ if isinstance(value, dict):
+ tmp = {}
+ for key, value in value.iteritems():
+ key = env.to_unicode(key).encode(env.charset)
+ value = env.to_unicode(value).encode(env.charset)
+ tmp[key] = value
+ return urlencode(tmp)
+ else:
+ return quote(env.to_unicode(value).encode(env.charset))
+ return wrapped
+
+
+def do_jsonencode():
+ """
+ JSON dump a variable. just works if simplejson is installed.
+
+ .. sourcecode:: jinja
+
+ {{ 'Hello World'|jsonencode }}
+ -> "Hello World"
+ """
+ global simplejson
+ try:
+ simplejson
+ except NameError:
+ import simplejson
+ return lambda e, c, v: simplejson.dumps(v)
+
+
+def do_filesizeformat():
+ """
+ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
+ bytes, etc).
+ """
+ def wrapped(env, context, value):
+ # fail silently
+ try:
+ bytes = float(value)
+ except TypeError:
+ bytes = 0
+
+ if bytes < 1024:
+ return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
+ elif bytes < 1024 * 1024:
+ return "%.1f KB" % (bytes / 1024)
+ elif bytes < 1024 * 1024 * 1024:
+ return "%.1f MB" % (bytes / (1024 * 1024))
+ return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+ return wrapped
+
+
+def do_pprint(verbose=False):
+ """
+ Pretty print a variable. Useful for debugging.
+
+ With Jinja 1.2 onwards you can pass it a parameter. If this parameter
+ is truthy the output will be more verbose (this requires `pretty`)
+ """
+ def wrapped(env, context, value):
+ return pformat(value, verbose=verbose)
+ return wrapped
+
+
+def do_urlize(value, trim_url_limit=None, nofollow=False):
+ """
+ Converts URLs in plain text into clickable links.
+
+ If you pass the filter an additional integer it will shorten the urls
+ to that number. Also a third argument exists that makes the urls
+ "nofollow":
+
+ .. sourcecode:: jinja
+
+ {{ mytext|urlize(40, True) }}
+ links are shortened to 40 chars and defined with rel="nofollow"
+ """
+ return urlize(value, trim_url_limit, nofollow)
+do_urlize = stringfilter(do_urlize)
+
+
+def do_indent(s, width=4, indentfirst=False):
+ """
+ {{ s|indent[ width[ indentfirst[ usetab]]] }}
+
+ Return a copy of the passed string, each line indented by
+ 4 spaces. The first line is not indented. If you want to
+ change the number of spaces or indent the first line too
+ you can pass additional parameters to the filter:
+
+ .. sourcecode:: jinja
+
+ {{ mytext|indent(2, True) }}
+ indent by two spaces and indent the first line too.
+ """
+ indention = ' ' * width
+ if indentfirst:
+ return u'\n'.join([indention + line for line in s.splitlines()])
+ return s.replace('\n', '\n' + indention)
+do_indent = stringfilter(do_indent)
+
+
+def do_truncate(s, length=255, killwords=False, end='...'):
+ """
+ Return a truncated copy of the string. The length is specified
+ with the first parameter which defaults to ``255``. If the second
+ parameter is ``true`` the filter will cut the text at length. Otherwise
+ it will try to save the last word. If the text was in fact
+ truncated it will append an ellipsis sign (``"..."``). If you want a
+ different ellipsis sign than ``"..."`` you can specify it using the
+ third parameter.
+
+ .. sourcecode jinja::
+
+ {{ mytext|truncate(300, false, '»') }}
+ truncate mytext to 300 chars, don't split up words, use a
+ right pointing double arrow as ellipsis sign.
+ """
+ if len(s) <= length:
+ return s
+ elif killwords:
+ return s[:length] + end
+ words = s.split(' ')
+ result = []
+ m = 0
+ for word in words:
+ m += len(word) + 1
+ if m > length:
+ break
+ result.append(word)
+ result.append(end)
+ return u' '.join(result)
+do_truncate = stringfilter(do_truncate)
+
+
+def do_wordwrap(s, pos=79, hard=False):
+ """
+ Return a copy of the string passed to the filter wrapped after
+ ``79`` characters. You can override this default using the first
+ parameter. If you set the second parameter to `true` Jinja will
+ also split words apart (usually a bad idea because it makes
+ reading hard).
+ """
+ if len(s) < pos:
+ return s
+ if hard:
+ return u'\n'.join([s[idx:idx + pos] for idx in
+ xrange(0, len(s), pos)])
+ # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
+ return reduce(lambda line, word, pos=pos: u'%s%s%s' %
+ (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
+ len(word.split('\n', 1)[0]) >= pos)],
+ word), s.split(' '))
+do_wordwrap = stringfilter(do_wordwrap)
+
+
+def do_wordcount(s):
+ """
+ Count the words in that string.
+ """
+ return len([x for x in s.split() if x])
+do_wordcount = stringfilter(do_wordcount)
+
+
+def do_textile(s):
+ """
+ Prase the string using textile.
+
+ requires the `PyTextile`_ library.
+
+ .. _PyTextile: http://dealmeida.net/projects/textile/
+ """
+ from textile import textile
+ return textile(s.encode('utf-8')).decode('utf-8')
+do_textile = stringfilter(do_textile)
+
+
+def do_markdown(s):
+ """
+ Parse the string using markdown.
+
+ requires the `Python-markdown`_ library.
+
+ .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/
+ """
+ from markdown import markdown
+ return markdown(s.encode('utf-8')).decode('utf-8')
+do_markdown = stringfilter(do_markdown)
+
+
+def do_rst(s):
+ """
+ Parse the string using the reStructuredText parser from the
+ docutils package.
+
+ requires `docutils`_.
+
+ .. _docutils: http://docutils.sourceforge.net/
+ """
+ from docutils.core import publish_parts
+ parts = publish_parts(source=s, writer_name='html4css1')
+ return parts['fragment']
+do_rst = stringfilter(do_rst)
+
+
+def do_int(default=0):
+ """
+ Convert the value into an integer. If the
+ conversion doesn't work it will return ``0``. You can
+ override this default using the first parameter.
+ """
+ def wrapped(env, context, value):
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ try:
+ return int(float(value))
+ except (TypeError, ValueError):
+ return default
+ return wrapped
+
+
+def do_float(default=0.0):
+ """
+ Convert the value into a floating point number. If the
+ conversion doesn't work it will return ``0.0``. You can
+ override this default using the first parameter.
+ """
+ def wrapped(env, context, value):
+ try:
+ return float(value)
+ except (TypeError, ValueError):
+ return default
+ return wrapped
+
+
+def do_string():
+ """
+ Convert the value into an string.
+ """
+ return lambda e, c, v: e.to_unicode(v)
+
+
+def do_format(*args):
+ """
+ Apply python string formatting on an object:
+
+ .. sourcecode:: jinja
+
+ {{ "%s - %s"|format("Hello?", "Foo!") }}
+ -> Hello? - Foo!
+
+ Note that you cannot use the mapping syntax (``%(name)s``)
+ like in python. Use `|dformat` for that.
+ """
+ def wrapped(env, context, value):
+ return env.to_unicode(value) % args
+ return wrapped
+
+
+def do_dformat(d):
+ """
+ Apply python mapping string formatting on an object:
+
+ .. sourcecode:: jinja
+
+ {{ "Hello %(username)s!"|dformat({'username': 'John Doe'}) }}
+ -> Hello John Doe!
+
+ This is useful when adding variables to translateable
+ string expressions.
+
+ *New in Jinja 1.1*
+ """
+ if not isinstance(d, dict):
+ raise FilterArgumentError('dict required')
+ def wrapped(env, context, value):
+ return env.to_unicode(value) % d
+ return wrapped
+
+
+def do_trim(value):
+ """
+ Strip leading and trailing whitespace.
+ """
+ return value.strip()
+do_trim = stringfilter(do_trim)
+
+
+def do_capture(name='captured', clean=False):
+ """
+ Store the value in a variable called ``captured`` or a variable
+ with the name provided. Useful for filter blocks:
+
+ .. sourcecode:: jinja
+
+ {% filter capture('foo') %}
+ ...
+ {% endfilter %}
+ {{ foo }}
+
+ This will output "..." two times. One time from the filter block
+ and one time from the variable. If you don't want the filter to
+ output something you can use it in `clean` mode:
+
+ .. sourcecode:: jinja
+
+ {% filter capture('foo', True) %}
+ ...
+ {% endfilter %}
+ {{ foo }}
+ """
+ if not isinstance(name, basestring):
+ raise FilterArgumentError('You can only capture into variables')
+ def wrapped(env, context, value):
+ context[name] = value
+ if clean:
+ return TemplateData()
+ return value
+ return wrapped
+
+
+def do_striptags(value):
+ """
+ Strip SGML/XML tags and replace adjacent whitespace by one space.
+
+ *new in Jinja 1.1*
+ """
+ return ' '.join(_striptags_re.sub('', value).split())
+do_striptags = stringfilter(do_striptags)
+
+
+def do_slice(slices, fill_with=None):
+ """
+ Slice an iterator and return a list of lists containing
+ those items. Useful if you want to create a div containing
+ three div tags that represent columns:
+
+ .. sourcecode:: html+jinja
+
+ <div class="columwrapper">
+ {%- for column in items|slice(3) %}
+ <ul class="column-{{ loop.index }}">
+ {%- for item in column %}
+ <li>{{ item }}</li>
+ {%- endfor %}
+ </ul>
+ {%- endfor %}
+ </div>
+
+ If you pass it a second argument it's used to fill missing
+ values on the last iteration.
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ result = []
+ seq = list(value)
+ length = len(seq)
+ items_per_slice = length // slices
+ slices_with_extra = length % slices
+ offset = 0
+ for slice_number in xrange(slices):
+ start = offset + slice_number * items_per_slice
+ if slice_number < slices_with_extra:
+ offset += 1
+ end = offset + (slice_number + 1) * items_per_slice
+ tmp = seq[start:end]
+ if fill_with is not None and slice_number >= slices_with_extra:
+ tmp.append(fill_with)
+ result.append(tmp)
+ return result
+ return wrapped
+
+
+def do_batch(linecount, fill_with=None):
+ """
+ A filter that batches items. It works pretty much like `slice`
+ just the other way round. It returns a list of lists with the
+ given number of items. If you provide a second parameter this
+ is used to fill missing items. See this example:
+
+ .. sourcecode:: html+jinja
+
+ <table>
+ {%- for row in items|batch(3, ' ') %}
+ <tr>
+ {%- for column in row %}
+ <tr>{{ column }}</td>
+ {%- endfor %}
+ </tr>
+ {%- endfor %}
+ </table>
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ result = []
+ tmp = []
+ for item in value:
+ if len(tmp) == linecount:
+ result.append(tmp)
+ tmp = []
+ tmp.append(item)
+ if tmp:
+ if fill_with is not None and len(tmp) < linecount:
+ tmp += [fill_with] * (linecount - len(tmp))
+ result.append(tmp)
+ return result
+ return wrapped
+
+
+def do_sum():
+ """
+ Sum up the given sequence of numbers.
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ return sum(value)
+ return wrapped
+
+
+def do_abs():
+ """
+ Return the absolute value of a number.
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ return abs(value)
+ return wrapped
+
+
+def do_round(precision=0, method='common'):
+ """
+ Round the number to a given precision. The first
+ parameter specifies the precision (default is ``0``), the
+ second the rounding method:
+
+ - ``'common'`` rounds either up or down
+ - ``'ceil'`` always rounds up
+ - ``'floor'`` always rounds down
+
+ If you don't specify a method ``'common'`` is used.
+
+ .. sourcecode:: jinja
+
+ {{ 42.55|round }}
+ -> 43
+ {{ 42.55|round(1, 'floor') }}
+ -> 42.5
+
+ *new in Jinja 1.1*
+ """
+ if not method in ('common', 'ceil', 'floor'):
+ raise FilterArgumentError('method must be common, ceil or floor')
+ if precision < 0:
+ raise FilterArgumentError('precision must be a postive integer '
+ 'or zero.')
+ def wrapped(env, context, value):
+ if method == 'common':
+ return round(value, precision)
+ import math
+ func = getattr(math, method)
+ if precision:
+ return func(value * 10 * precision) / (10 * precision)
+ else:
+ return func(value)
+ return wrapped
+
+
+def do_sort(reverse=False):
+ """
+ Sort a sequence. Per default it sorts ascending, if you pass it
+ `True` as first argument it will reverse the sorting.
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ return sorted(value, reverse=reverse)
+ return wrapped
+
+
+def do_groupby(attribute):
+ """
+ Group a sequence of objects by a common attribute.
+
+ If you for example have a list of dicts or objects that represent persons
+ with `gender`, `first_name` and `last_name` attributes and you want to
+ group all users by genders you can do something like the following
+ snippet:
+
+ .. sourcecode:: html+jinja
+
+ <ul>
+ {% for group in persons|groupby('gender') %}
+ <li>{{ group.grouper }}<ul>
+ {% for person in group.list %}
+ <li>{{ person.first_name }} {{ person.last_name }}</li>
+ {% endfor %}</ul></li>
+ {% endfor %}
+ </ul>
+
+ As you can see the item we're grouping by is stored in the `grouper`
+ attribute and the `list` contains all the objects that have this grouper
+ in common.
+
+ *New in Jinja 1.2*
+ """
+ def wrapped(env, context, value):
+ expr = lambda x: env.get_attribute(x, attribute)
+ return sorted([{
+ 'grouper': a,
+ 'list': list(b)
+ } for a, b in groupby(sorted(value, key=expr), expr)],
+ key=itemgetter('grouper'))
+ return wrapped
+
+
+def do_getattribute(attribute):
+ """
+ Get one attribute from an object. Normally you don't have to use this
+ filter because the attribute and subscript expressions try to either
+ get an attribute of an object or an item. In some situations it could
+ be that there is an item *and* an attribute with the same name. In that
+ situation only the item is returned, never the attribute.
+
+ .. sourcecode:: jinja
+
+ {{ foo.bar }} -> {{ foo|getattribute('bar') }}
+
+ *New in Jinja 1.2*
+ """
+ def wrapped(env, context, value):
+ try:
+ return get_attribute(value, attribute)
+ except (SecurityException, AttributeError):
+ return env.undefined_singleton
+ return wrapped
+
+
+def do_getitem(key):
+ """
+ This filter basically works like the normal subscript expression but
+ it doesn't fall back to attribute lookup. If an item does not exist for
+ an object undefined is returned.
+
+ .. sourcecode:: jinja
+
+ {{ foo.bar }} -> {{ foo|getitem('bar') }}
+
+ *New in Jinja 1.2*
+ """
+ def wrapped(env, context, value):
+ try:
+ return value[key]
+ except (TypeError, KeyError, IndexError, AttributeError):
+ return env.undefined_singleton
+ return wrapped
+
+
+FILTERS = {
+ 'replace': do_replace,
+ 'upper': do_upper,
+ 'lower': do_lower,
+ 'escape': do_escape,
+ 'e': do_escape,
+ 'xmlattr': do_xmlattr,
+ 'capitalize': do_capitalize,
+ 'title': do_title,
+ 'default': do_default,
+ 'join': do_join,
+ 'count': do_count,
+ 'dictsort': do_dictsort,
+ 'length': do_count,
+ 'reverse': do_reverse,
+ 'center': do_center,
+ 'title': do_title,
+ 'capitalize': do_capitalize,
+ 'first': do_first,
+ 'last': do_last,
+ 'random': do_random,
+ 'urlencode': do_urlencode,
+ 'jsonencode': do_jsonencode,
+ 'filesizeformat': do_filesizeformat,
+ 'pprint': do_pprint,
+ 'indent': do_indent,
+ 'truncate': do_truncate,
+ 'wordwrap': do_wordwrap,
+ 'wordcount': do_wordcount,
+ 'textile': do_textile,
+ 'markdown': do_markdown,
+ 'rst': do_rst,
+ 'int': do_int,
+ 'float': do_float,
+ 'string': do_string,
+ 'urlize': do_urlize,
+ 'format': do_format,
+ 'dformat': do_dformat,
+ 'capture': do_capture,
+ 'trim': do_trim,
+ 'striptags': do_striptags,
+ 'slice': do_slice,
+ 'batch': do_batch,
+ 'sum': do_sum,
+ 'abs': do_abs,
+ 'round': do_round,
+ 'sort': do_sort,
+ 'groupby': do_groupby,
+ 'getattribute': do_getattribute,
+ 'getitem': do_getitem
+}
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
new file mode 100644
index 0000000..7752bee
--- /dev/null
+++ b/jinja2/lexer.py
@@ -0,0 +1,503 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.lexer
+ ~~~~~~~~~~~~
+
+ This module implements a Jinja / Python combination lexer. The
+ `Lexer` class provided by this module is used to do some preprocessing
+ for Jinja.
+
+ On the one hand it filters out invalid operators like the bitshift
+ operators we don't allow in templates. On the other hand it separates
+ template code and python code in expressions.
+
+ :copyright: 2007-2008 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import unicodedata
+from jinja.datastructure import TokenStream, Token
+from jinja.exceptions import TemplateSyntaxError
+from weakref import WeakValueDictionary
+
+
+__all__ = ['Lexer', 'Failure', 'keywords']
+
+
+# cache for the lexers. Exists in order to be able to have multiple
+# environments with the same lexer
+_lexer_cache = WeakValueDictionary()
+
+
+# static regular expressions
+whitespace_re = re.compile(r'\s+(?um)')
+string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)')
+integer_re = re.compile(r'\d+')
+name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
+float_re = re.compile(r'\d+\.\d+')
+
+
+# set of used keywords
+keywords = set(['and', 'block', 'elif', 'else', 'endblock',
+ 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw',
+ 'endtrans', 'extends', 'filter', 'for', 'if', 'in',
+ 'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw',
+ 'recursive', 'set', 'trans', 'call', 'endcall',
+ 'true', 'false', 'none'])
+
+# bind operators to token types
+operators = {
+ '+': 'add',
+ '-': 'sub',
+ '/': 'div',
+ '//': 'floordiv',
+ '*': 'mul',
+ '%': 'mod',
+ '**': 'pow',
+ '~': 'tilde',
+ '[': 'lbracket',
+ ']': 'rbracket',
+ '(': 'lparen',
+ ')': 'rparen',
+ '{': 'lbrace',
+ '}': 'rbrace',
+ '==': 'eq',
+ '!=': 'ne',
+ '>': 'gt',
+ '>=': 'gteq',
+ '<': 'lt',
+ '<=': 'lteq',
+ '=': 'assign',
+ '.': 'dot',
+ ':': 'colon',
+ '|': 'pipe',
+ ',': 'comma',
+ ';': 'semicolon'
+}
+
+reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
+assert len(operators) == len(reverse_operators), 'operators dropped'
+operator_re = re.compile('(%s)' % '|'.join([re.escape(x) for x in
+ sorted(operators, key=lambda x: -len(x))]))
+
+simple_escapes = {
+ 'a': '\a',
+ 'n': '\n',
+ 'r': '\r',
+ 'f': '\f',
+ 't': '\t',
+ 'v': '\v',
+ '\\': '\\',
+ '"': '"',
+ "'": "'",
+ '0': '\x00'
+}
+unicode_escapes = {
+ 'x': 2,
+ 'u': 4,
+ 'U': 8
+}
+
+
+def unescape_string(lineno, filename, s):
+ r"""
+ Unescape a string. Supported escapes:
+ \a, \n, \r\, \f, \v, \\, \", \', \0
+
+ \x00, \u0000, \U00000000, \N{...}
+
+ Not supported are \101 because imho redundant.
+ """
+ result = []
+ write = result.append
+ chariter = iter(s)
+ next_char = chariter.next
+
+ # faster lookup
+ sescapes = simple_escapes
+ uescapes = unicode_escapes
+
+ try:
+ for char in chariter:
+ if char == '\\':
+ char = next_char()
+ if char in sescapes:
+ write(sescapes[char])
+ elif char in uescapes:
+ seq = [next_char() for x in xrange(uescapes[char])]
+ try:
+ write(unichr(int(''.join(seq), 16)))
+ except ValueError:
+ raise TemplateSyntaxError('invalid unicode codepoint',
+ lineno, filename)
+ elif char == 'N':
+ if next_char() != '{':
+ raise TemplateSyntaxError('no name for codepoint',
+ lineno, filename)
+ seq = []
+ while 1:
+ char = next_char()
+ if char == '}':
+ break
+ seq.append(char)
+ try:
+ write(unicodedata.lookup(u''.join(seq)))
+ except KeyError:
+ raise TemplateSyntaxError('unknown character name',
+ lineno, filename)
+ else:
+ write('\\' + char)
+ else:
+ write(char)
+ except StopIteration:
+ raise TemplateSyntaxError('invalid string escape', lineno, filename)
+ return u''.join(result)
+
+
+def unescape_regex(s):
+ """
+ Unescape rules for regular expressions.
+ """
+ buffer = []
+ write = buffer.append
+ in_escape = False
+ for char in s:
+ if in_escape:
+ in_escape = False
+ if char not in safe_chars:
+ write('\\' + char)
+ continue
+ write(char)
+ return u''.join(buffer)
+
+
+class Failure(object):
+ """
+ Class that raises a `TemplateSyntaxError` if called.
+ Used by the `Lexer` to specify known errors.
+ """
+
+ def __init__(self, message, cls=TemplateSyntaxError):
+ self.message = message
+ self.error_class = cls
+
+ def __call__(self, lineno, filename):
+ raise self.error_class(self.message, lineno, filename)
+
+
+class LexerMeta(type):
+ """
+ Metaclass for the lexer that caches instances for
+ the same configuration in a weak value dictionary.
+ """
+
+ def __call__(cls, environment):
+ key = hash((environment.block_start_string,
+ environment.block_end_string,
+ environment.variable_start_string,
+ environment.variable_end_string,
+ environment.comment_start_string,
+ environment.comment_end_string,
+ environment.trim_blocks))
+
+ # use the cached lexer if possible
+ if key in _lexer_cache:
+ return _lexer_cache[key]
+
+ # create a new lexer and cache it
+ lexer = type.__call__(cls, environment)
+ _lexer_cache[key] = lexer
+ return lexer
+
+
+class Lexer(object):
+ """
+ Class that implements a lexer for a given environment. Automatically
+ created by the environment class, usually you don't have to do that.
+
+ Note that the lexer is not automatically bound to an environment.
+ Multiple environments can share the same lexer.
+ """
+
+ __metaclass__ = LexerMeta
+
+ def __init__(self, environment):
+ # shortcuts
+ c = lambda x: re.compile(x, re.M | re.S)
+ e = re.escape
+
+ # lexing rules for tags
+ tag_rules = [
+ (whitespace_re, None, None),
+ (float_re, 'float', None),
+ (integer_re, 'integer', None),
+ ('%s' % '|'.join(sorted(keywords, key=lambda x: -len(x))),
+ 'keyword', None),
+ (name_re, 'name', None),
+ (string_re, 'string', None),
+ (operator_re, 'operator', None)
+ ]
+
+ #: if variables and blocks have the same delimiters we won't
+ #: receive any variable blocks in the parser. This variable is `True`
+ #: if we need that.
+ self.no_variable_block = (
+ (environment.variable_start_string is
+ environment.variable_end_string is None) or
+ (environment.variable_start_string ==
+ environment.block_start_string and
+ environment.variable_end_string ==
+ environment.block_end_string)
+ )
+
+ # assamble the root lexing rule. because "|" is ungreedy
+ # we have to sort by length so that the lexer continues working
+ # as expected when we have parsing rules like <% for block and
+ # <%= for variables. (if someone wants asp like syntax)
+ # variables are just part of the rules if variable processing
+ # is required.
+ root_tag_rules = [
+ ('comment', environment.comment_start_string),
+ ('block', environment.block_start_string)
+ ]
+ if not self.no_variable_block:
+ root_tag_rules.append(('variable',
+ environment.variable_start_string))
+ root_tag_rules.sort(lambda a, b: cmp(len(b[1]), len(a[1])))
+
+ # block suffix if trimming is enabled
+ block_suffix_re = environment.trim_blocks and '\\n?' or ''
+
+ # global lexing rules
+ self.rules = {
+ 'root': [
+ # directives
+ (c('(.*?)(?:%s)' % '|'.join(
+ ['(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*%s)' % (
+ e(environment.block_start_string),
+ e(environment.block_start_string),
+ e(environment.block_end_string)
+ )] + [
+ '(?P<%s_begin>\s*%s\-|%s)' % (n, e(r), e(r))
+ for n, r in root_tag_rules
+ ])), ('data', '#bygroup'), '#bygroup'),
+ # data
+ (c('.+'), 'data', None)
+ ],
+ # comments
+ 'comment_begin': [
+ (c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
+ e(environment.comment_end_string),
+ e(environment.comment_end_string),
+ block_suffix_re
+ )), ('comment', 'comment_end'), '#pop'),
+ (c('(.)'), (Failure('Missing end of comment tag'),), None)
+ ],
+ # blocks
+ 'block_begin': [
+ (c('(?:\-%s\s*|%s)%s' % (
+ e(environment.block_end_string),
+ e(environment.block_end_string),
+ block_suffix_re
+ )), 'block_end', '#pop'),
+ ] + tag_rules,
+ # raw block
+ 'raw_begin': [
+ (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
+ e(environment.block_start_string),
+ e(environment.block_start_string),
+ e(environment.block_end_string),
+ e(environment.block_end_string),
+ block_suffix_re
+ )), ('data', 'raw_end'), '#pop'),
+ (c('(.)'), (Failure('Missing end of raw directive'),), None)
+ ]
+ }
+
+ # only add the variable rules to the list if we process variables
+ # the variable_end_string variable could be None and break things.
+ if not self.no_variable_block:
+ self.rules['variable_begin'] = [
+ (c('\-%s\s*|%s' % (
+ e(environment.variable_end_string),
+ e(environment.variable_end_string)
+ )), 'variable_end', '#pop')
+ ] + tag_rules
+
+ def tokenize(self, source, filename=None):
+ """
+ Works like `tokeniter` but returns a tokenstream of tokens and not a
+ generator or token tuples. Additionally all token values are already
+ converted into types and postprocessed. For example keywords are
+ already keyword tokens, not named tokens, comments are removed,
+ integers and floats converted, strings unescaped etc.
+ """
+ def generate():
+ for lineno, token, value in self.tokeniter(source, filename):
+ if token in ('comment_begin', 'comment', 'comment_end'):
+ continue
+ elif token == 'data':
+ try:
+ value = str(value)
+ except UnicodeError:
+ pass
+ elif token == 'keyword':
+ token = str(value)
+ elif token == 'name':
+ value = str(value)
+ elif token == 'string':
+ value = unescape_string(lineno, filename, value[1:-1])
+ try:
+ value = str(value)
+ except UnicodeError:
+ pass
+ elif token == 'integer':
+ value = int(value)
+ elif token == 'float':
+ value = float(value)
+ elif token == 'operator':
+ token = operators[value]
+ value = ''
+ yield Token(lineno, token, value)
+ return TokenStream(generate(), filename)
+
+ def tokeniter(self, source, filename=None):
+ """
+ This method tokenizes the text and returns the tokens in a generator.
+ Use this method if you just want to tokenize a template. The output
+ you get is not compatible with the input the jinja parser wants. The
+ parser uses the `tokenize` function with returns a `TokenStream` and
+ keywords instead of just names.
+ """
+ source = '\n'.join(source.splitlines())
+ pos = 0
+ lineno = 1
+ stack = ['root']
+ statetokens = self.rules['root']
+ source_length = len(source)
+
+ balancing_stack = []
+
+ while True:
+ # tokenizer loop
+ for regex, tokens, new_state in statetokens:
+ m = regex.match(source, pos)
+ # if no match we try again with the next rule
+ if not m:
+ continue
+
+ # we only match blocks and variables if brances / parentheses
+ # are balanced. continue parsing with the lower rule which
+ # is the operator rule. do this only if the end tags look
+ # like operators
+ if balancing_stack and \
+ tokens in ('variable_end', 'block_end'):
+ continue
+
+ # tuples support more options
+ if isinstance(tokens, tuple):
+ for idx, token in enumerate(tokens):
+ # hidden group
+ if token is None:
+ g = m.group(idx)
+ if g:
+ lineno += g.count('\n')
+ continue
+ # failure group
+ elif token.__class__ is Failure:
+ raise token(lineno, filename)
+ # bygroup is a bit more complex, in that case we
+ # yield for the current token the first named
+ # group that matched
+ elif token == '#bygroup':
+ for key, value in m.groupdict().iteritems():
+ if value is not None:
+ yield lineno, key, value
+ lineno += value.count('\n')
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve '
+ 'the token dynamically'
+ ' but no group matched'
+ % regex)
+ # normal group
+ else:
+ data = m.group(idx + 1)
+ if data:
+ yield lineno, token, data
+ lineno += data.count('\n')
+
+ # strings as token just are yielded as it, but just
+ # if the data is not empty
+ else:
+ data = m.group()
+ # update brace/parentheses balance
+ if tokens == 'operator':
+ if data == '{':
+ balancing_stack.append('}')
+ elif data == '(':
+ balancing_stack.append(')')
+ elif data == '[':
+ balancing_stack.append(']')
+ elif data in ('}', ')', ']'):
+ if not balancing_stack:
+ raise TemplateSyntaxError('unexpected "%s"' %
+ data, lineno,
+ filename)
+ expected_op = balancing_stack.pop()
+ if expected_op != data:
+ raise TemplateSyntaxError('unexpected "%s", '
+ 'expected "%s"' %
+ (data, expected_op),
+ lineno, filename)
+ # yield items
+ if tokens is not None:
+ if data:
+ yield lineno, tokens, data
+ lineno += data.count('\n')
+
+ # fetch new position into new variable so that we can check
+ # if there is a internal parsing error which would result
+ # in an infinite loop
+ pos2 = m.end()
+
+ # handle state changes
+ if new_state is not None:
+ # remove the uppermost state
+ if new_state == '#pop':
+ stack.pop()
+ # resolve the new state by group checking
+ elif new_state == '#bygroup':
+ for key, value in m.groupdict().iteritems():
+ if value is not None:
+ stack.append(key)
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve the '
+ 'new state dynamically but'
+ ' no group matched' %
+ regex)
+ # direct state name given
+ else:
+ stack.append(new_state)
+ statetokens = self.rules[stack[-1]]
+ # we are still at the same position and no stack change.
+ # this means a loop without break condition, avoid that and
+ # raise error
+ elif pos2 == pos:
+ raise RuntimeError('%r yielded empty string without '
+ 'stack change' % regex)
+ # publish new function and start again
+ pos = pos2
+ break
+ # if loop terminated without break we havn't found a single match
+ # either we are at the end of the file or we have a problem
+ else:
+ # end of text
+ if pos >= source_length:
+ return
+ # something went wrong
+ raise TemplateSyntaxError('unexpected char %r at %d' %
+ (source[pos], pos), lineno,
+ filename)
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
new file mode 100644
index 0000000..40bc1d7
--- /dev/null
+++ b/jinja2/loaders.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.loaders
+ ~~~~~~~~~~~~~
+
+ Jinja loader classes.
+
+ :copyright: 2007 by Armin Ronacher, Bryan McLemore.
+ :license: BSD, see LICENSE for more details.
+"""
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
new file mode 100644
index 0000000..1e20096
--- /dev/null
+++ b/jinja2/nodes.py
@@ -0,0 +1,494 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.nodes
+ ~~~~~~~~~~~~
+
+ This module implements additional nodes derived from the ast base node.
+
+ It also provides some node tree helper functions like `in_lineno` and
+ `get_nodes` used by the parser and translator in order to normalize
+ python and jinja nodes.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import operator
+from itertools import chain, izip
+from copy import copy
+
+
+_binop_to_func = {
+ '*': operator.mul,
+ '/': operator.truediv,
+ '//': operator.floordiv,
+ '**': operator.pow,
+ '%': operator.mod,
+ '+': operator.add,
+ '-': operator.sub
+}
+
+_uaop_to_func = {
+ 'not': operator.not_,
+ '+': operator.pos,
+ '-': operator.neg
+}
+
+
+class Impossible(Exception):
+ """
+ Raised if the node could not perform a requested action.
+ """
+
+
+class NodeType(type):
+
+ def __new__(cls, name, bases, d):
+ for attr in '_fields', '_attributes':
+ storage = []
+ for base in bases:
+ storage.extend(getattr(base, attr, ()))
+ storage.extend(d.get(attr, ()))
+ assert len(storage) == len(set(storage))
+ d[attr] = tuple(storage)
+ return type.__new__(cls, name, bases, d)
+
+
+class Node(object):
+ """
+ Base jinja node.
+ """
+ __metaclass__ = NodeType
+ _fields = ()
+ _attributes = ('lineno',)
+
+ def __init__(self, *args, **kw):
+ if args:
+ if len(args) != len(self._fields):
+ if not self._fields:
+ raise TypeError('%r takes 0 arguments' %
+ self.__class__.__name__)
+ raise TypeError('%r takes 0 or %d argument%s' % (
+ self.__class__.__name__,
+ len(self._fields),
+ len(self._fields) != 1 and 's' or ''
+ ))
+ for name, arg in izip(self._fields, args):
+ setattr(self, name, arg)
+ for attr in self._attributes:
+ setattr(self, attr, kw.pop(attr, None))
+ if kw:
+ raise TypeError('unknown keyword argument %r' %
+ iter(kw).next())
+
+ def iter_fields(self):
+ for name in self._fields:
+ try:
+ yield name, getattr(self, name)
+ except AttributeError:
+ pass
+
+ def iter_child_nodes(self):
+ for field, item in self.iter_fields():
+ if isinstance(item, list):
+ for n in item:
+ if isinstance(n, Node):
+ yield n
+ elif isinstance(item, Node):
+ yield item
+
+ def __repr__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__,
+ ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
+ arg in self._fields)
+ )
+
+
+class Stmt(Node):
+ """
+ Base node for all statements.
+ """
+
+
+class Helper(Node):
+ """
+ Nodes that exist in a specific context only.
+ """
+
+
+class Template(Node):
+ """
+ Node that represents a template.
+ """
+ _fields = ('extends', 'body')
+
+
+class Output(Stmt):
+ """
+ A node that holds multiple expressions which are then printed out. This
+ is used both for the `print` statement and the regular template data.
+ """
+ _fields = ('nodes',)
+
+
+class Extends(Stmt):
+ """
+ Represents an extends statement.
+ """
+ _fields = ('extends',)
+
+
+class For(Stmt):
+ """
+ A node that represents a for loop
+ """
+ _fields = ('item', 'seq', 'body', 'else_', 'recursive')
+
+
+class If(Stmt):
+ """
+ A node that represents an if condition.
+ """
+ _fields = ('test', 'body', 'else_')
+
+
+class Macro(Stmt):
+ """
+ A node that represents a macro.
+ """
+ _fields = ('name', 'arguments', 'body')
+
+
+class CallBlock(Stmt):
+ """
+ A node that represents am extended macro call.
+ """
+ _fields = ('expr', 'body')
+
+
+class Set(Stmt):
+ """
+ Allows defining own variables.
+ """
+ _fields = ('name', 'expr')
+
+
+class FilterBlock(Stmt):
+ """
+ Node for filter sections.
+ """
+ _fields = ('body', 'filters')
+
+
+class Block(Stmt):
+ """
+ A node that represents a block.
+ """
+ _fields = ('name', 'body')
+
+
+class Include(Stmt):
+ """
+ A node that represents the include tag.
+ """
+ _fields = ('template',)
+
+
+class Trans(Stmt):
+ """
+ A node for translatable sections.
+ """
+ _fields = ('singular', 'plural', 'indicator', 'replacements')
+
+
+class ExprStmt(Stmt):
+ """
+ A statement that evaluates an expression to None.
+ """
+ _fields = ('node',)
+
+
+class Expr(Node):
+ """
+ Baseclass for all expressions.
+ """
+
+ def as_const(self):
+ """
+ Return the value of the expression as constant or raise `Impossible`
+ if this was not possible.
+ """
+ raise Impossible()
+
+ def can_assign(self):
+ """
+ Check if it's possible to assign something to this node.
+ """
+ return False
+
+
+class BinExpr(Expr):
+ """
+ Baseclass for all binary expressions.
+ """
+ _fields = ('left', 'right')
+ operator = None
+
+ def as_const(self):
+ f = _binop_to_func[self.operator]
+ try:
+ return f(self.left.as_const(), self.right.as_const())
+ except:
+ print self.left, f, self.right
+ raise Impossible()
+
+
+class UnaryExpr(Expr):
+ """
+ Baseclass for all unary expressions.
+ """
+ _fields = ('node',)
+ operator = None
+
+ def as_const(self):
+ f = _uaop_to_func[self.operator]
+ try:
+ return f(self.node.as_const())
+ except:
+ raise Impossible()
+
+
+class Name(Expr):
+ """
+ any name such as {{ foo }}
+ """
+ _fields = ('name',)
+
+ def can_assign(self):
+ return True
+
+
+class Literal(Expr):
+ """
+ Baseclass for literals.
+ """
+
+
+class Const(Literal):
+ """
+ any constat such as {{ "foo" }}
+ """
+ _fields = ('value',)
+
+ def as_const(self):
+ return self.value
+
+
+class Tuple(Literal):
+ """
+ For loop unpacking and some other things like multiple arguments
+ for subscripts.
+ """
+ _fields = ('items',)
+
+ def as_const(self):
+ return tuple(x.as_const() for x in self.items)
+
+ def can_assign(self):
+ for item in self.items:
+ if not item.can_assign():
+ return False
+ return True
+
+
+class List(Literal):
+ """
+ any list literal such as {{ [1, 2, 3] }}
+ """
+ _fields = ('items',)
+
+ def as_const(self):
+ return [x.as_const() for x in self.items]
+
+
+class Dict(Literal):
+ """
+ any dict literal such as {{ {1: 2, 3: 4} }}
+ """
+ _fields = ('items',)
+
+ def as_const(self):
+ return dict(x.as_const() for x in self.items)
+
+
+class Pair(Helper):
+ """
+ A key, value pair for dicts.
+ """
+ _fields = ('key', 'value')
+
+ def as_const(self):
+ return self.key.as_const(), self.value.as_const()
+
+
+class CondExpr(Expr):
+ """
+ {{ foo if bar else baz }}
+ """
+ _fields = ('test', 'expr1', 'expr2')
+
+ def as_const(self):
+ if self.test.as_const():
+ return self.expr1.as_const()
+ return self.expr2.as_const()
+
+
+class Filter(Expr):
+ """
+ {{ foo|bar|baz }}
+ """
+ _fields = ('node', 'filters')
+
+
+class Test(Expr):
+ """
+ {{ foo is lower }}
+ """
+ _fields = ('node', 'name', 'args')
+
+
+class Call(Expr):
+ """
+ {{ foo(bar) }}
+ """
+ _fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+
+
+class Subscript(Expr):
+ """
+ {{ foo.bar }} and {{ foo['bar'] }} etc.
+ """
+ _fields = ('node', 'arg')
+
+ def as_const(self):
+ try:
+ return self.node.as_const()[self.node.as_const()]
+ except:
+ raise Impossible()
+
+ def can_assign(self):
+ return True
+
+
+class Slice(Expr):
+ """
+ 1:2:3 etc.
+ """
+ _fields = ('start', 'stop', 'step')
+
+
+class Concat(Expr):
+ """
+ For {{ foo ~ bar }}. Concatenates strings.
+ """
+ _fields = ('nodes',)
+
+ def as_const(self):
+ return ''.join(unicode(x.as_const()) for x in self.nodes)
+
+
+class Compare(Expr):
+ """
+ {{ foo == bar }}, {{ foo >= bar }} etc.
+ """
+ _fields = ('expr', 'ops')
+
+
+class Mul(BinExpr):
+ """
+ {{ foo * bar }}
+ """
+ operator = '*'
+
+
+class Div(BinExpr):
+ """
+ {{ foo / bar }}
+ """
+ operator = '/'
+
+
+class FloorDiv(BinExpr):
+ """
+ {{ foo // bar }}
+ """
+ operator = '//'
+
+
+class Add(BinExpr):
+ """
+ {{ foo + bar }}
+ """
+ operator = '+'
+
+
+class Sub(BinExpr):
+ """
+ {{ foo - bar }}
+ """
+ operator = '-'
+
+
+class Mod(BinExpr):
+ """
+ {{ foo % bar }}
+ """
+ operator = '%'
+
+
+class Pow(BinExpr):
+ """
+ {{ foo ** bar }}
+ """
+ operator = '**'
+
+
+class And(BinExpr):
+ """
+ {{ foo and bar }}
+ """
+ operator = 'and'
+
+ def as_const(self):
+ return self.left.as_const() and self.right.as_const()
+
+
+class Or(BinExpr):
+ """
+ {{ foo or bar }}
+ """
+ operator = 'or'
+
+ def as_const(self):
+ return self.left.as_const() or self.right.as_const()
+
+
+class Not(UnaryExpr):
+ """
+ {{ not foo }}
+ """
+ operator = 'not'
+
+
+class NegExpr(UnaryExpr):
+ """
+ {{ -foo }}
+ """
+ operator = '-'
+
+
+class PosExpr(UnaryExpr):
+ """
+ {{ +foo }}
+ """
+ operator = '+'
diff --git a/jinja2/parser.py b/jinja2/parser.py
new file mode 100644
index 0000000..d981c75
--- /dev/null
+++ b/jinja2/parser.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.parser
+ ~~~~~~~~~~~~~
+
+ Implements the template parser.
+
+ :copyright: 2008 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja import nodes
+from jinja.exceptions import TemplateSyntaxError
+
+
+__all__ = ['Parser']
+
+
+class Parser(object):
+ """
+ The template parser class.
+
+ Transforms sourcecode into an abstract syntax tree.
+ """
+
+ def __init__(self, environment, source, filename=None):
+ self.environment = environment
+ if isinstance(source, str):
+ source = source.decode(environment.template_charset, 'ignore')
+ if isinstance(filename, unicode):
+ filename = filename.encode('utf-8')
+ self.source = source
+ self.filename = filename
+ self.closed = False
+ self.blocks = set()
+ self.no_variable_block = self.environment.lexer.no_variable_block
+ self.stream = environment.lexer.tokenize(source, filename)
+
+ def parse(self):
+ pass
diff --git a/jinja2/tests.py b/jinja2/tests.py
new file mode 100644
index 0000000..25f7992
--- /dev/null
+++ b/jinja2/tests.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.tests
+ ~~~~~~~~~~~
+
+ Jinja test functions. Used with the "is" operator.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+
+
+number_re = re.compile(r'^-?\d+(\.\d+)?$')
+regex_type = type(number_re)
+
+
+def test_odd():
+ """
+ Return true if the variable is odd.
+ """
+ return lambda e, c, v: v % 2 == 1
+
+
+def test_even():
+ """
+ Return true of the variable is even.
+ """
+ return lambda e, c, v: v % 2 == 0
+
+
+def test_defined():
+ """
+ Return true if the variable is defined:
+
+ .. sourcecode:: jinja
+
+ {% if variable is defined %}
+ value of variable: {{ variable }}
+ {% else %}
+ variable is not defined
+ {% endif %}
+
+ See also the ``default`` filter.
+ """
+ return lambda e, c, v: v is not e.undefined_singleton
+
+
+def test_lower():
+ """
+ Return true if the variable is lowercase.
+ """
+ return lambda e, c, v: isinstance(v, basestring) and v.islower()
+
+
+def test_upper():
+ """
+ Return true if the variable is uppercase.
+ """
+ return lambda e, c, v: isinstance(v, basestring) and v.isupper()
+
+
+def test_numeric():
+ """
+ Return true if the variable is numeric.
+ """
+ return lambda e, c, v: isinstance(v, (int, long, float)) or (
+ isinstance(v, basestring) and
+ number_re.match(v) is not None)
+
+
+def test_sequence():
+ """
+ Return true if the variable is a sequence. Sequences are variables
+ that are iterable.
+ """
+ def wrapped(environment, context, value):
+ try:
+ len(value)
+ value.__getitem__
+ except:
+ return False
+ return True
+ return wrapped
+
+
+def test_matching(regex):
+ r"""
+ Test if the variable matches the regular expression given. Note that
+ you have to escape special chars using *two* backslashes, these are
+ *not* raw strings.
+
+ .. sourcecode:: jinja
+
+ {% if var is matching @/^\d+$/ %}
+ var looks like a number
+ {% else %}
+ var doesn't really look like a number
+ {% endif %}
+ """
+ def wrapped(environment, context, value):
+ if type(regex) is regex_type:
+ regex_ = regex
+ else:
+ if environment.disable_regexps:
+ raise RuntimeError('regular expressions disabled.')
+ if isinstance(regex, unicode):
+ regex_ = re.compile(regex, re.U)
+ elif isinstance(regex, str):
+ regex_ = re.compile(regex)
+ else:
+ return False
+ return regex_.search(value) is not None
+ return wrapped
+
+
+def test_sameas(other):
+ """
+ Check if an object points to the same memory address than another
+ object:
+
+ .. sourcecode:: jinja
+
+ {% if foo.attribute is sameas(false) %}
+ the foo attribute really is the `False` singleton
+ {% endif %}
+
+ *New in Jinja 1.2*
+ """
+ return lambda e, c, v: v is other
+
+
+TESTS = {
+ 'odd': test_odd,
+ 'even': test_even,
+ 'defined': test_defined,
+ 'lower': test_lower,
+ 'upper': test_upper,
+ 'numeric': test_numeric,
+ 'sequence': test_sequence,
+ 'matching': test_matching,
+ 'sameas': test_sameas
+}
diff --git a/jinja2/translators/__init__.py b/jinja2/translators/__init__.py
new file mode 100644
index 0000000..ba55148
--- /dev/null
+++ b/jinja2/translators/__init__.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.translators
+ ~~~~~~~~~~~~~~~~~
+
+ The submodules of this module provide translators for the jinja ast.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+class Translator(object):
+ """
+ Base class of all translators.
+ """
+
+ def process(environment, tree, source=None):
+ """
+ Process the given ast with the rules defined in
+ environment and return a translated version of it.
+ The translated object can be anything. The python
+ translator for example outputs Template instances,
+ a javascript translator would probably output strings.
+
+ This is a static function.
+ """
+ pass
+ process = staticmethod(process)
diff --git a/jinja2/translators/python.py b/jinja2/translators/python.py
new file mode 100644
index 0000000..3e7b942
--- /dev/null
+++ b/jinja2/translators/python.py
@@ -0,0 +1,1115 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja.translators.python
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This module translates a jinja ast into python code.
+
+ This translator tries hard to keep Jinja sandboxed. All security
+ relevant calls are wrapped by methods defined in the environment.
+ This affects:
+
+ - method calls
+ - attribute access
+ - name resolution
+
+ It also adds debug symbols used by the traceback toolkit implemented
+ in `jinja.utils`.
+
+ Implementation Details
+ ======================
+
+ It might sound strange but the translator tries to keep the generated
+ code readable as much as possible. This simplifies debugging the Jinja
+ core a lot. The additional processing overhead is just relevant for
+ the translation process, the additional comments and whitespace won't
+ appear in the saved bytecode.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import sys
+from jinja import nodes
+from jinja.nodes import get_nodes
+from jinja.parser import Parser
+from jinja.exceptions import TemplateSyntaxError
+from jinja.translators import Translator
+from jinja.datastructure import TemplateStream
+from jinja.utils import set, capture_generator
+
+
+#: regular expression for the debug symbols
+_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P<filename>.*?), '
+ r'lineno=(?P<lineno>\d+)\)$')
+
+# For Python versions without generator exit exceptions
+try:
+ GeneratorExit = GeneratorExit
+except NameError:
+ class GeneratorExit(Exception):
+ pass
+
+# For Pythons without conditional expressions
+try:
+ exec '0 if 0 else 0'
+ have_conditional_expr = True
+except SyntaxError:
+ have_conditional_expr = False
+
+
+class Template(object):
+ """
+ Represents a finished template.
+ """
+
+ def __init__(self, environment, code):
+ self.environment = environment
+ self.code = code
+ self.generate_func = None
+
+ def dump(self, stream=None):
+ """Dump the template into python bytecode."""
+ if stream is not None:
+ from marshal import dump
+ dump(self.code, stream)
+ else:
+ from marshal import dumps
+ return dumps(self.code)
+
+ def load(environment, data):
+ """Load the template from python bytecode."""
+ if isinstance(data, basestring):
+ from marshal import loads
+ code = loads(data)
+ else:
+ from marshal import load
+ code = load(data)
+ return Template(environment, code)
+ load = staticmethod(load)
+
+ def render(self, *args, **kwargs):
+ """Render a template."""
+ __traceback_hide__ = True
+ ctx = self._prepare(*args, **kwargs)
+ try:
+ return capture_generator(self.generate_func(ctx))
+ except:
+ self._debug(ctx, *sys.exc_info())
+
+ def stream(self, *args, **kwargs):
+ """Render a template as stream."""
+ def proxy(ctx):
+ try:
+ for item in self.generate_func(ctx):
+ yield item
+ except GeneratorExit:
+ return
+ except:
+ self._debug(ctx, *sys.exc_info())
+ return TemplateStream(proxy(self._prepare(*args, **kwargs)))
+
+ def _prepare(self, *args, **kwargs):
+ """Prepare the template execution."""
+ # if there is no generation function we execute the code
+ # in a new namespace and save the generation function and
+ # debug information.
+ env = self.environment
+ if self.generate_func is None:
+ ns = {'environment': env}
+ exec self.code in ns
+ self.generate_func = ns['generate']
+ return env.context_class(env, *args, **kwargs)
+
+ def _debug(self, ctx, exc_type, exc_value, traceback):
+ """Debugging Helper"""
+ # just modify traceback if we have that feature enabled
+ from traceback import print_exception
+ print_exception(exc_type, exc_value, traceback)
+
+ if self.environment.friendly_traceback:
+ # hook the debugger in
+ from jinja.debugger import translate_exception
+ exc_type, exc_value, traceback = translate_exception(
+ self, ctx, exc_type, exc_value, traceback)
+ print_exception(exc_type, exc_value, traceback)
+
+ raise exc_type, exc_value, traceback
+
+
+class TranslationOperator(object):
+ """
+ A translation operator has a single string representing the operation
+ """
+ def __init__(self, operator, translator):
+ self.operator = operator
+ self.translator = translator
+
+class UnaryOperator(TranslationOperator):
+ """
+ A unary operator has one side to the operation.
+ """
+ def __call__(self, node):
+ """
+ Apply operator.
+ """
+ return '( %s %s)' % (
+ self.operator,
+ self.translator.handle_node(node.node),
+ )
+
+class BinaryOperator(TranslationOperator):
+ """
+ A binary operator has two sides to the operation.
+ """
+ def __call__(self, node):
+ """
+ Apply operator.
+ """
+ return '(%s %s %s)' % (
+ self.translator.handle_node(node.left),
+ self.operator,
+ self.translator.handle_node(node.right),
+ )
+
+class PythonTranslator(Translator):
+ """
+ Pass this translator a ast tree to get valid python code.
+ """
+
+ def __init__(self, environment, node, source):
+ self.environment = environment
+ self.loader = environment.loader.get_controlled_loader()
+ self.node = node
+ self.source = source
+ self.closed = False
+
+ #: current level of indention
+ self.indention = 0
+ #: each {% cycle %} tag has a unique ID which increments
+ #: automatically for each tag.
+ self.last_cycle_id = 0
+ #: set of used shortcuts jinja has to make local automatically
+ self.used_shortcuts = set(['undefined_singleton'])
+ #: set of used datastructures jinja has to import
+ self.used_data_structures = set()
+ #: set of used utils jinja has to import
+ self.used_utils = set()
+ #: flags for runtime error
+ self.require_runtime_error = False
+ #: do wee need a "set" object?
+ self.need_set_import = False
+ #: flag for regular expressions
+ self.compiled_regular_expressions = {}
+
+ #: bind the nodes to the callback functions. There are
+ #: some missing! A few are specified in the `unhandled`
+ #: mapping in order to disallow their usage, some of them
+ #: will not appear in the jinja parser output because
+ #: they are filtered out.
+ self.handlers = {
+ # block nodes
+ nodes.Template: self.handle_template,
+ nodes.Text: self.handle_template_text,
+ nodes.NodeList: self.handle_node_list,
+ nodes.ForLoop: self.handle_for_loop,
+ nodes.IfCondition: self.handle_if_condition,
+ nodes.Cycle: self.handle_cycle,
+ nodes.Print: self.handle_print,
+ nodes.Macro: self.handle_macro,
+ nodes.Call: self.handle_call,
+ nodes.Set: self.handle_set,
+ nodes.Filter: self.handle_filter,
+ nodes.Block: self.handle_block,
+ nodes.Include: self.handle_include,
+ nodes.Trans: self.handle_trans,
+
+ # expression nodes
+ nodes.NameExpression: self.handle_name,
+ nodes.CompareExpression: self.handle_compare,
+ nodes.TestExpression: self.handle_test,
+ nodes.ConstantExpression: self.handle_const,
+ nodes.RegexExpression: self.handle_regex,
+ nodes.SubscriptExpression: self.handle_subscript,
+ nodes.FilterExpression: self.handle_filter_expr,
+ nodes.CallExpression: self.handle_call_expr,
+ nodes.AddExpression: BinaryOperator('+', self),
+ nodes.SubExpression: BinaryOperator('-', self),
+ nodes.ConcatExpression: self.handle_concat,
+ nodes.DivExpression: BinaryOperator('/', self),
+ nodes.FloorDivExpression: BinaryOperator('//', self),
+ nodes.MulExpression: BinaryOperator('*', self),
+ nodes.ModExpression: BinaryOperator('%', self),
+ nodes.PosExpression: UnaryOperator('+', self),
+ nodes.NegExpression: UnaryOperator('-', self),
+ nodes.PowExpression: BinaryOperator('**', self),
+ nodes.DictExpression: self.handle_dict,
+ nodes.SetExpression: self.handle_set_expr,
+ nodes.ListExpression: self.handle_list,
+ nodes.TupleExpression: self.handle_tuple,
+ nodes.UndefinedExpression: self.handle_undefined,
+ nodes.AndExpression: BinaryOperator(' and ', self),
+ nodes.OrExpression: BinaryOperator(' or ', self),
+ nodes.NotExpression: UnaryOperator(' not ', self),
+ nodes.SliceExpression: self.handle_slice,
+ nodes.ConditionalExpression: self.handle_conditional_expr
+ }
+
+ # -- public methods
+
+ def process(environment, node, source=None):
+ """
+ The only public method. Creates a translator instance,
+ translates the code and returns it in form of an
+ `Template` instance.
+ """
+ translator = PythonTranslator(environment, node, source)
+ filename = node.filename or '<template>'
+ source = translator.translate()
+ return Template(environment, compile(source, filename, 'exec'))
+ process = staticmethod(process)
+
+ # -- private helper methods
+
+ def indent(self, text):
+ """
+ Indent the current text. This does only indent the
+ first line.
+ """
+ return (' ' * (self.indention * 4)) + text
+
+ def to_tuple(self, args):
+ """
+ Return a tuple repr without nested repr.
+ """
+ return '(%s%s)' % (
+ ', '.join(args),
+ len(args) == 1 and ',' or ''
+ )
+
+ def nodeinfo(self, node, force=False):
+ """
+ Return a comment that helds the node informations or None
+ if there is no need to add a debug comment.
+ """
+ return '# DEBUG(filename=%s, lineno=%s)' % (
+ node.filename or '',
+ node.lineno
+ )
+
+ def handle_node(self, node):
+ """
+ Handle one node. Resolves the correct callback functions defined
+ in the callback mapping.
+ """
+ if self.closed:
+ raise RuntimeError('translator is closed')
+ if node.__class__ in self.handlers:
+ return self.handlers[node.__class__](node)
+ else:
+ raise AssertionError('unhandled node %r' % node.__class__)
+
+ def close(self):
+ """
+ Clean up stuff.
+ """
+ self.closed = True
+ self.handlers = self.node = self.environment = self.loader = None
+
+ def translate(self):
+ """
+ Translate the node defined in the constructor.
+ """
+ try:
+ return self.handle_node(self.node)
+ finally:
+ self.close()
+
+ # -- jinja nodes
+
+ def handle_template(self, node):
+ """
+ Handle the overall template node. This node is the first node and
+ ensures that we get the bootstrapping code. It also knows about
+ inheritance information. It only occours as outer node, never in
+ the tree itself.
+ """
+ self.indention = 1
+
+ # if there is a parent template we parse the parent template and
+ # update the blocks there. Once this is done we drop the current
+ # template in favor of the new one. Do that until we found the
+ # root template.
+ parent = None
+ overwrites = {}
+ blocks = {}
+ requirements = []
+ outer_filename = node.filename or '<template>'
+
+ # this set is required in order to not add blocks to the block
+ # dict a second time if they were not overridden in one template
+ # in the template chain.
+ already_registered_block = set()
+
+ while node.extends is not None:
+ # the direct child nodes in a template that are not blocks
+ # are processed as template globals, thus executed *before*
+ # the master layout template is loaded. This can be used
+ # for further processing. The output of those nodes does
+ # not appear in the final template.
+ requirements += [child for child in node.body.get_child_nodes()
+ if child.__class__ not in (nodes.Text,
+ nodes.Block)]
+
+ # load the template we inherit from and add not known blocks.
+ # this also marks the templates on the controlled loader but
+ # are never removed. that's no problem because we don't allow
+ # parents we extend from as includes and the controlled loader
+ # is only used for this templated
+ parent = self.loader.parse(node.extends,
+ node.filename)
+
+ # look up all block nodes in the current template and
+ # add them to the override dict.
+ for n in get_nodes(nodes.Block, node):
+ overwrites[n.name] = n
+ # handle direct overrides
+ for n in get_nodes(nodes.Block, parent):
+ # an overwritten block for the parent template. handle that
+ # override in the template and register it in the deferred
+ # block dict.
+ if n.name in overwrites and n not in already_registered_block:
+ blocks.setdefault(n.name, []).append(n.clone())
+ n.replace(overwrites[n.name])
+ already_registered_block.add(n)
+ # make the parent node the new node
+ node = parent
+
+ # handle requirements code
+ if requirements:
+ requirement_lines = ['def bootstrap(context):']
+ for n in requirements:
+ requirement_lines.append(self.handle_node(n))
+ requirement_lines.append(' if 0: yield None\n')
+
+ # handle body in order to get the used shortcuts
+ body_code = self.handle_node(node.body)
+
+ # same for blocks in callables
+ block_lines = []
+ block_items = blocks.items()
+ block_items.sort()
+ dict_lines = []
+ for name, items in block_items:
+ tmp = []
+ for idx, item in enumerate(items):
+ # ensure that the indention is correct
+ self.indention = 1
+ func_name = 'block_%s_%s' % (name, idx)
+ data = self.handle_block(item, idx + 1)
+ # blocks with data
+ if data:
+ block_lines.extend([
+ 'def %s(context):' % func_name,
+ self.indent(self.nodeinfo(item, True)),
+ data,
+ ' if 0: yield None\n'
+ ])
+ tmp.append('buffereater(%s)' % func_name)
+ self.used_utils.add('buffereater')
+ # blocks without data, can default to something
+ # from utils
+ else:
+ tmp.append('empty_block')
+ self.used_utils.add('empty_block')
+ dict_lines.append(' %r: %s' % (
+ str(name),
+ self.to_tuple(tmp)
+ ))
+
+ # bootstrapping code
+ lines = ['# Essential imports', 'from __future__ import division']
+ if self.used_utils:
+ lines.append('from jinja.utils import %s' % \
+ ', '.join(tuple(self.used_utils)))
+ if self.require_runtime_error:
+ lines.append('from jinja.exceptions import TemplateRuntimeError')
+ if self.used_data_structures:
+ lines.append('from jinja.datastructure import %s' % ', '.
+ join(self.used_data_structures))
+ if self.need_set_import:
+ lines.append('from jinja.utils import set')
+
+ # compile regular expressions
+ if self.compiled_regular_expressions:
+ lines.append('import re')
+ lines.append('\n# Compile used regular expressions')
+ for regex, name in self.compiled_regular_expressions.iteritems():
+ lines.append('%s = re.compile(%r)' % (name, regex))
+
+ lines.append(
+ '\n# Aliases for some speedup\n'
+ '%s\n\n'
+ '# Marker for Jinja templates\n'
+ '__jinja_template__ = True\n\n'
+ '# Name for disabled debugging\n'
+ '__name__ = %r\n\n'
+ 'def generate(context):\n'
+ ' assert environment is context.environment' % (
+ '\n'.join([
+ '%s = environment.%s' % (item, item) for item in
+ self.used_shortcuts
+ ]),
+ outer_filename
+ )
+ )
+
+ # the template body
+ if requirements:
+ lines.append(' for item in bootstrap(context): pass')
+ lines.append(body_code)
+ lines.append(' if 0: yield None\n')
+
+ # now write the bootstrapping (requirements) core if there is one
+ if requirements:
+ lines.append('# Bootstrapping code')
+ lines.extend(requirement_lines)
+
+ # blocks must always be defined. even if it's empty. some
+ # features depend on it
+ if block_lines:
+ lines.append('# Superable blocks')
+ lines.extend(block_lines)
+ lines.append('# Block mapping')
+ if dict_lines:
+ lines.append('blocks = {\n%s\n}\n' % ',\n'.join(dict_lines))
+ else:
+ lines.append('blocks = {}\n')
+
+ # now get the real source lines and map the debugging symbols
+ debug_mapping = []
+ file_mapping = {}
+ last = None
+ offset = -1
+ sourcelines = ('\n'.join(lines)).splitlines()
+ result = []
+
+ for idx, line in enumerate(sourcelines):
+ m = _debug_re.search(line)
+ if m is not None:
+ d = m.groupdict()
+ filename = d['filename'] or None
+ if isinstance(filename, unicode):
+ filename = filename.encode('utf-8')
+ if filename in file_mapping:
+ file_id = file_mapping[filename]
+ else:
+ file_id = file_mapping[filename] = 'F%d' % \
+ len(file_mapping)
+ this = (file_id, int(d['lineno']))
+ # if it's the same as the line before we ignore it
+ if this != last:
+ debug_mapping.append('(%r, %s, %r)' % ((idx - offset,) + this))
+ last = this
+ # for each debug symbol the line number and so the offset
+ # changes by one.
+ offset += 1
+ else:
+ result.append(line)
+
+ # now print file mapping and debug info
+ # the debug info:
+ # debug_info binds template line numbers to generated
+ # source lines. this information is always
+ # present and part of the bytecode.
+ # template_source only available if loaded from string to
+ # get debug source code. Because this is
+ # dumped too it's a bad idea to dump templates
+ # loaded from a string.
+ result.append('\n# Debug Information')
+ file_mapping = file_mapping.items()
+ file_mapping.sort(lambda a, b: cmp(a[1], b[1]))
+ for filename, file_id in file_mapping:
+ result.append('%s = %r' % (file_id, filename))
+ result.append('debug_info = %s' % self.to_tuple(debug_mapping))
+ result.append('template_source = %r' % self.source)
+
+ return '\n'.join(result)
+
+ def handle_template_text(self, node):
+ """
+ Handle data around nodes.
+ """
+ # special case: no variables
+ if not node.variables:
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('yield %r' % node.text.replace('%%', '%'))
+
+ # special case: one variable, no text
+ self.used_shortcuts.add('finish_var')
+ if len(node.variables) == 1 and node.text == '%s':
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('yield finish_var(%s, context)' %
+ self.handle_node(node.variables[0]))
+
+ # all other cases
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+
+ write(self.nodeinfo(node))
+ write('yield %r %% (' % node.text)
+ self.indention += 1
+ for var in node.variables:
+ write(self.nodeinfo(var))
+ write('finish_var(%s, context)' % self.handle_node(var) + ',')
+ self.indention -= 1
+ write(')')
+
+ return '\n'.join(buf)
+
+ def handle_node_list(self, node):
+ """
+ In some situations we might have a node list. It's just
+ a collection of multiple statements.
+
+ If the nodelist was empty it will return an empty string
+ """
+ body = '\n'.join([self.handle_node(n) for n in node])
+ if body:
+ return self.indent(self.nodeinfo(node)) + '\n' + body
+ return ''
+
+ def handle_for_loop(self, node):
+ """
+ Handle a for loop. Pretty basic, just that we give the else
+ clause a different behavior.
+ """
+ self.used_data_structures.add('LoopContext')
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+ write(self.nodeinfo(node))
+ write('context.push()')
+
+ # recursive loops
+ if node.recursive:
+ write('def loop(seq):')
+ self.indention += 1
+ write('for %s in context[\'loop\'].push(seq):' %
+ self.handle_node(node.item),
+ )
+
+ # simple loops
+ else:
+ write('context[\'loop\'] = loop = LoopContext(%s, '
+ 'context[\'loop\'], None)' % self.handle_node(node.seq))
+ write('for %s in loop:' %
+ self.handle_node(node.item)
+ )
+
+ # handle real loop code
+ self.indention += 1
+ write(self.nodeinfo(node.body))
+ if node.body:
+ buf.append(self.handle_node(node.body))
+ else:
+ write('pass')
+ self.indention -= 1
+
+ # else part of loop
+ if node.else_:
+ write('if not context[\'loop\'].iterated:')
+ self.indention += 1
+ write(self.nodeinfo(node.else_))
+ buf.append(self.handle_node(node.else_) or self.indent('pass'))
+ self.indention -= 1
+
+ # call recursive for loop!
+ if node.recursive:
+ write('context[\'loop\'].pop()')
+ write('if 0: yield None')
+ self.indention -= 1
+ write('context[\'loop\'] = LoopContext(None, context[\'loop\'], '
+ 'buffereater(loop))')
+ self.used_utils.add('buffereater')
+ write('for item in loop(%s):' % self.handle_node(node.seq))
+ self.indention += 1
+ write('yield item')
+ self.indention -= 1
+
+ write('context.pop()')
+ return '\n'.join(buf)
+
+ def handle_if_condition(self, node):
+ """
+ Handle an if condition node.
+ """
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+ write(self.nodeinfo(node))
+ for idx, (test, body) in enumerate(node.tests):
+ write('%sif %s:' % (
+ idx and 'el' or '',
+ self.handle_node(test)
+ ))
+ self.indention += 1
+ write(self.nodeinfo(body))
+ buf.append(self.handle_node(body) or self.indent('pass'))
+ self.indention -= 1
+ if node.else_ is not None:
+ write('else:')
+ self.indention += 1
+ write(self.nodeinfo(node.else_))
+ buf.append(self.handle_node(node.else_) or self.indent('pass'))
+ self.indention -= 1
+ return '\n'.join(buf)
+
+ def handle_cycle(self, node):
+ """
+ Handle the cycle tag.
+ """
+ self.used_data_structures.add('CycleContext')
+ name = '::cycle_%x' % self.last_cycle_id
+ self.last_cycle_id += 1
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+
+ write('if %r not in context.current:' % name)
+ self.indention += 1
+ write(self.nodeinfo(node))
+ if node.seq.__class__ in (nodes.TupleExpression,
+ nodes.ListExpression):
+ write('context.current[%r] = CycleContext(%s)' % (
+ name,
+ self.to_tuple([self.handle_node(n) for n in node.seq.items])
+ ))
+ hardcoded = True
+ else:
+ write('context.current[%r] = CycleContext()' % name)
+ hardcoded = False
+ self.indention -= 1
+
+ self.used_shortcuts.add('finish_var')
+ if hardcoded:
+ write('yield finish_var(context.current[%r].cycle(), '
+ 'context)' % name)
+ else:
+ write('yield finish_var(context.current[%r].cycle(%s), '
+ 'context)' % (
+ name,
+ self.handle_node(node.seq)
+ ))
+
+ return '\n'.join(buf)
+
+ def handle_print(self, node):
+ """
+ Handle a print statement.
+ """
+ self.used_shortcuts.add('finish_var')
+ return self.indent(self.nodeinfo(node)) + '\n' +\
+ self.indent('yield finish_var(%s, context)' %
+ self.handle_node(node.expr))
+
+ def handle_macro(self, node):
+ """
+ Handle macro declarations.
+ """
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+
+ write('def macro(*args, **kw):')
+ self.indention += 1
+ write(self.nodeinfo(node))
+
+ # collect macro arguments
+ arg_items = []
+ caller_overridden = False
+
+ # if we have conditional expressions available in that python
+ # build (for example cpython > 2.4) we can use them, they
+ # will perform slightly better.
+ if have_conditional_expr:
+ arg_tmpl = '\'%(name)s\': args[%(pos)d] if argcount > %(pos)d ' \
+ 'else %(default)s'
+ # otherwise go with the and/or tuple hack:
+ else:
+ arg_tmpl = '\'%(name)s\': (argcount > %(pos)d and '\
+ '(args[%(pos)d],) or (%(default)s,))[0]'
+
+ if node.arguments:
+ varargs_init = '\'varargs\': args[%d:]' % len(node.arguments)
+ write('argcount = len(args)')
+ for idx, (name, n) in enumerate(node.arguments):
+ arg_items.append(arg_tmpl % {
+ 'name': name,
+ 'pos': idx,
+ 'default': n is None and 'undefined_singleton' or
+ self.handle_node(n)
+ })
+ if name == 'caller':
+ caller_overridden = True
+ elif name == 'varargs':
+ varargs_init = None
+ else:
+ varargs_init = '\'varargs\': args'
+
+ if caller_overridden:
+ write('kw.pop(\'caller\', None)')
+ else:
+ arg_items.append('\'caller\': kw.pop(\'caller\', undefined_singleton)')
+ if varargs_init:
+ arg_items.append(varargs_init)
+
+ write('context.push({%s})' % ',\n '.join([
+ idx and self.indent(item) or item for idx, item
+ in enumerate(arg_items)
+ ]))
+
+ # disallow any keyword arguments
+ write('if kw:')
+ self.indention += 1
+ write('raise TemplateRuntimeError(\'%s got an unexpected keyword '
+ 'argument %%r\' %% iter(kw).next())' % node.name)
+ self.require_runtime_error = True
+ self.indention -= 1
+
+ write(self.nodeinfo(node.body))
+ data = self.handle_node(node.body)
+ if data:
+ buf.append(data)
+ write('context.pop()')
+ write('if 0: yield None')
+ self.indention -= 1
+ buf.append(self.indent('context[%r] = buffereater(macro, True)' %
+ node.name))
+ self.used_utils.add('buffereater')
+
+ return '\n'.join(buf)
+
+ def handle_call(self, node):
+ """
+ Handle extended macro calls.
+ """
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+
+ write('def call(**kwargs):')
+ self.indention += 1
+ write('context.push(kwargs)')
+ data = self.handle_node(node.body)
+ if data:
+ buf.append(data)
+ write('context.pop()')
+ write('if 0: yield None')
+ self.indention -= 1
+ write('yield ' + self.handle_call_expr(node.expr,
+ {'caller': 'buffereater(call)'}))
+ self.used_utils.add('buffereater')
+
+ return '\n'.join(buf)
+
+ def handle_set(self, node):
+ """
+ Handle variable assignments.
+ """
+ if node.scope_local:
+ tmpl = 'context[%r] = %s'
+ else:
+ tmpl = 'context.set_nonlocal(%r, %s)'
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent(tmpl % (
+ node.name,
+ self.handle_node(node.expr)
+ ))
+
+ def handle_filter(self, node):
+ """
+ Handle filter sections.
+ """
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+ write('def filtered():')
+ self.indention += 1
+ write('context.push()')
+ write(self.nodeinfo(node.body))
+ data = self.handle_node(node.body)
+ if data:
+ buf.append(data)
+ write('context.pop()')
+ write('if 0: yield None')
+ self.indention -= 1
+ self.used_shortcuts.add('apply_filters')
+ write('yield apply_filters(buffereater(filtered)(), context, %s)' %
+ self.to_tuple(['(%r, %s)' % (
+ name,
+ self.to_tuple(map(self.handle_node, args))
+ ) for name, args in node.filters])
+ )
+ self.used_utils.add('buffereater')
+ return '\n'.join(buf)
+
+ def handle_block(self, node, level=0):
+ """
+ Handle blocks in the sourcecode. We only use them to
+ call the current block implementation that is stored somewhere
+ else.
+ """
+ rv = self.handle_node(node.body)
+ if not rv:
+ return ''
+
+ self.used_data_structures.add('SuperBlock')
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+
+ write(self.nodeinfo(node))
+ write('context.push({\'super\': SuperBlock(%r, blocks, %r, context)})' % (
+ str(node.name),
+ level
+ ))
+ write(self.nodeinfo(node.body))
+ buf.append(rv)
+ write('context.pop()')
+ return '\n'.join(buf)
+
+ def handle_include(self, node):
+ """
+ Include another template at the current position.
+ """
+ tmpl = self.loader.parse(node.template,
+ node.filename)
+ try:
+ return self.handle_node(tmpl.body)
+ finally:
+ self.loader.mark_as_processed()
+
+ def handle_trans(self, node):
+ """
+ Handle translations.
+ """
+ if node.replacements:
+ replacements = []
+ for name, n in node.replacements.iteritems():
+ replacements.append('%r: %s' % (
+ name,
+ self.handle_node(n)
+ ))
+ replacements = '{%s}' % ', '.join(replacements)
+ else:
+ replacements = 'None'
+ return self.indent(self.nodeinfo(node)) + '\n' +\
+ self.indent('yield context.translate_func(%r, %r, %r, %s)' % (
+ node.singular,
+ node.plural,
+ node.indicator,
+ replacements
+ ))
+
+ # -- python nodes
+
+ def handle_name(self, node):
+ """
+ Handle name assignments and name retreivement.
+ """
+ if node.name == '_':
+ return 'context.translate_func'
+ return 'context[%r]' % node.name
+
+ def handle_compare(self, node):
+ """
+ Any sort of comparison
+ """
+ ops = {
+ 'eq': '==',
+ 'ne': '!=',
+ 'lt': '<',
+ 'lteq': '<=',
+ 'gt': '>',
+ 'gteq': '>=',
+ 'in': 'in',
+ 'not in': 'not in'
+ }
+ buf = []
+ buf.append(self.handle_node(node.expr))
+ for op, n in node.ops:
+ buf.append(ops[op])
+ buf.append(self.handle_node(n))
+ return ' '.join(buf)
+
+ def handle_test(self, node):
+ """
+ Handle test calls.
+ """
+ self.used_shortcuts.add('perform_test')
+ return 'perform_test(context, %r, %s, %s)' % (
+ node.name,
+ self.to_tuple([self.handle_node(n) for n in node.args]),
+ self.handle_node(node.node)
+ )
+
+ def handle_const(self, node):
+ """
+ Constant values in expressions.
+ """
+ return repr(node.value)
+
+ def handle_regex(self, node):
+ """
+ Regular expression literals.
+ """
+ if self.environment.disable_regexps:
+ raise TemplateSyntaxError('regular expressions disabled.')
+ if node.value in self.compiled_regular_expressions:
+ return self.compiled_regular_expressions[node.value]
+ name = 'regex_%d' % len(self.compiled_regular_expressions)
+ self.compiled_regular_expressions[node.value] = name
+ return name
+
+ def handle_subscript(self, node):
+ """
+ Handle variable based attribute access foo['bar'].
+ """
+ self.used_shortcuts.add('get_attribute')
+ if node.arg.__class__ is nodes.SliceExpression:
+ rv = self.handle_slice(node.arg, getslice_test=True)
+ if rv is not None:
+ return self.handle_node(node.node) + rv
+ return 'get_attribute(%s, %s)' % (
+ self.handle_node(node.node),
+ self.handle_node(node.arg)
+ )
+
+ def handle_tuple(self, node):
+ """
+ Tuple unpacking loops.
+ """
+ return self.to_tuple([self.handle_node(n) for n in node.items])
+
+ def handle_filter_expr(self, node):
+ """
+ We use the pipe operator for filtering.
+ """
+ self.used_shortcuts.add('apply_filters')
+ return 'apply_filters(%s, context, %s)' % (
+ self.handle_node(node.node),
+ self.to_tuple(['(%r, %s)' % (
+ name,
+ self.to_tuple(map(self.handle_node, args))
+ ) for name, args in node.filters])
+ )
+
+ def handle_call_expr(self, node, extra_kwargs=None):
+ """
+ Handle function calls.
+ """
+ args = []
+ kwargs = {}
+ dyn_args = dyn_kwargs = None
+ if node.dyn_args is not None:
+ dyn_args = self.handle_node(node.dyn_args)
+ if node.dyn_kwargs is not None:
+ dyn_kwargs = self.handle_node(node.dyn_kwargs)
+ for arg in node.args:
+ args.append(self.handle_node(arg))
+ for name, arg in node.kwargs:
+ kwargs[name] = self.handle_node(arg)
+ if extra_kwargs:
+ kwargs.update(extra_kwargs)
+ if not (args or kwargs or dyn_args or dyn_kwargs):
+ self.used_shortcuts.add('call_function_simple')
+ return 'call_function_simple(%s, context)' % \
+ self.handle_node(node.node)
+ self.used_shortcuts.add('call_function')
+ return 'call_function(%s, context, %s, {%s}, %s, %s)' % (
+ self.handle_node(node.node),
+ self.to_tuple(args),
+ ', '.join(['%r: %s' % i for i in kwargs.iteritems()]),
+ dyn_args,
+ dyn_kwargs
+ )
+
+ def handle_concat(self, node):
+ """
+ Convert some objects to unicode and concatenate them.
+ """
+ self.used_shortcuts.add('to_unicode')
+ return "u''.join(%s)" % self.to_tuple([
+ 'to_unicode(%s)' % self.handle_node(arg)
+ for arg in node.args
+ ])
+
+
+ def handle_dict(self, node):
+ """
+ Dict constructor syntax.
+ """
+ return '{%s}' % ', '.join([
+ '%s: %s' % (
+ self.handle_node(key),
+ self.handle_node(value)
+ ) for key, value in node.items
+ ])
+
+ def handle_set_expr(self, node):
+ """
+ Set constructor syntax.
+ """
+ self.need_set_import = True
+ return 'set([%s])' % ', '.join([self.handle_node(n)
+ for n in node.items])
+
+ def handle_list(self, node):
+ """
+ We don't know tuples, tuples are lists for jinja.
+ """
+ return '[%s]' % ', '.join([
+ self.handle_node(n) for n in node.items
+ ])
+
+ def handle_undefined(self, node):
+ """
+ Return the current undefined literal.
+ """
+ return 'undefined_singleton'
+
+
+ def handle_slice(self, node, getslice_test=False):
+ """
+ Slice access. Because of backwards compatibilty to python's
+ `__getslice__` this function takes a second parameter that lets this
+ method return a regular slice bracket call. If a regular slice bracket
+ call that is compatible to __getslice__ is not possible the return
+ value will be `None` so that a regular `get_attribute` wrapping can
+ happen.
+ """
+ if node.start is None:
+ start = not getslice_test and 'None' or ''
+ else:
+ start = self.handle_node(node.start)
+ if node.stop is None:
+ stop = not getslice_test and 'None' or ''
+ else:
+ stop = self.handle_node(node.stop)
+ if node.step is None:
+ step = 'None'
+ else:
+ if getslice_test:
+ return
+ step = self.handle_node(node.step)
+ if getslice_test:
+ return '[%s:%s]' % (start, stop)
+ return 'slice(%s, %s, %s)' % (start, stop, step)
+
+ def handle_conditional_expr(self, node):
+ """
+ Handle conditional expressions.
+ """
+ if have_conditional_expr:
+ tmpl = '%(expr1)s if %(test)s else %(expr2)s'
+ else:
+ tmpl = '(%(test)s and (%(expr1)s,) or (%(expr2)s,))[0]'
+ return tmpl % {
+ 'test': self.handle_node(node.test),
+ 'expr1': self.handle_node(node.expr1),
+ 'expr2': self.handle_node(node.expr2)
+ }
diff --git a/jinja2/utils.py b/jinja2/utils.py
new file mode 100644
index 0000000..3f6395e
--- /dev/null
+++ b/jinja2/utils.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.utils
+ ~~~~~~~~~~~~
+
+ Utility functions.
+
+ :copyright: 2008 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""