From e98c5f53dfcc74598995f0511f9a106125f7dd56 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 21 Apr 2007 09:39:06 +0200 Subject: [PATCH] [svn] updated documentation regarding "{% call %}" and documented speedup module. also fixed some minor bugs in the speedup module --HG-- branch : trunk --- CHANGES | 4 ++ docs/src/designerdoc.txt | 61 ++++++++++++++++++++++ jinja/_native.py | 10 ++-- jinja/_speedups.c | 103 ++++++++++++++++++++++++++++++++------ jinja/datastructure.py | 12 ++--- tests/runtime/bigbench.py | 88 ++++++++++++++++++++++++++++++++ tests/runtime/bigtable.py | 33 ++++++++++-- 7 files changed, 283 insertions(+), 28 deletions(-) create mode 100644 tests/runtime/bigbench.py diff --git a/CHANGES b/CHANGES index 9e5e64c..68a5383 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,10 @@ Version 1.1 - You can now use the environment to just tokenize a template. This can be useful for syntax highlighting or other purposes. +- added optional c-implementation of the context baseclass. + +- implemented `{% call %}` - unsure if this makes it into the final release. + Version 1.0 ----------- diff --git a/docs/src/designerdoc.txt b/docs/src/designerdoc.txt index 3040e14..1d6cf66 100644 --- a/docs/src/designerdoc.txt +++ b/docs/src/designerdoc.txt @@ -936,5 +936,66 @@ default: {% trans username %}Hello {{ username }}!{% endtrans %} +Bleeding Edge +============= + +Here are some features documented that are new in the SVN version and might +change. + +``{% call %}``: + + A new tag that allows to pass a macro a block with template data: + + .. sourcecode:: html+jinja + + {% macro dialog title %} +
+

{{ title }}

+
+ {{ caller() }} +
+
+ {% endmacro %} + + Called the normal way `caller` will be undefined and thus return nothing + in silent mode or raise an exception in non silent. But if you call it + with the new `{% call %}` tag you can pass it some data: + + .. sourcecode:: html+jinja + + {% call dialog('Hello World') %} + This is an example dialog + {% endcall %} + + If you pass `caller()` some keyword arguments those are added to the + namespace of the wrapped template data: + + .. sourcecode:: html+jinja + + {% macro makelist items %} + + {%- endmacro %} + + {% call makelist([1, 2, 3, 4, 5, 6]) -%} + [[{{ item }}]] + {% endcall %} + + This will then produce this output: + + .. sourcecode:: html + + + .. _slicing chapter: http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice .. _range function: http://docs.python.org/tut/node6.html#SECTION006300000000000000000 diff --git a/jinja/_native.py b/jinja/_native.py index c5f5786..4abbb5c 100644 --- a/jinja/_native.py +++ b/jinja/_native.py @@ -6,6 +6,10 @@ 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. """ @@ -15,7 +19,7 @@ from jinja.datastructure import Deferred, Undefined class BaseContext(object): def __init__(self, silent, globals, initial): - self.silent = silent + self._silent = silent self.current = current = {} self.stack = [globals, initial, current] self.globals = globals @@ -31,7 +35,7 @@ class BaseContext(object): def push(self, data=None): """ - Push a new dict or empty layer to the stack and return that layer + Push one layer to the stack. Layer must be a dict or omitted. """ data = data or {} self.stack.append(data) @@ -58,7 +62,7 @@ class BaseContext(object): else: d[name] = rv return rv - if self.silent: + if self._silent: return Undefined raise TemplateRuntimeError('%r is not defined' % name) diff --git a/jinja/_speedups.c b/jinja/_speedups.c index 2f654d7..e57ac3d 100644 --- a/jinja/_speedups.c +++ b/jinja/_speedups.c @@ -6,6 +6,13 @@ * 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. + * + * TODO: + * - implement a cgi.escape replacement in this module + * * :copyright: 2007 by Armin Ronacher. * :license: BSD, see LICENSE for more details. */ @@ -13,14 +20,22 @@ #include #include -static PyObject *Undefined, *TemplateRuntimeError; +/* Set by init_constants to real values */ +static PyObject *Undefined, *TemplateRuntimeError, *FilterNotFound; static PyTypeObject *DeferredType; +/** + * 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 */ @@ -30,6 +45,10 @@ typedef struct { int silent; /* boolean value for silent failure */ } 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) { @@ -45,11 +64,17 @@ init_constants(void) PyObject *deferred = PyObject_GetAttrString(datastructure, "Deferred"); DeferredType = deferred->ob_type; TemplateRuntimeError = PyObject_GetAttrString(exceptions, "TemplateRuntimeError"); + FilterNotFound = PyObject_GetAttrString(exceptions, "FilterNotFound"); Py_DECREF(datastructure); Py_DECREF(exceptions); return 1; } +/** + * Deallocator for BaseContext. + * + * Frees the memory for the stack layers before freeing the object. + */ static void BaseContext_dealloc(BaseContext *self) { @@ -64,6 +89,15 @@ BaseContext_dealloc(BaseContext *self) self->ob_type->tp_free((PyObject*)self); } +/** + * Initializes the BaseContext. + * + * Like the native python class it takes a flag telling the context + * to either fail silently with Undefined or raising a TemplateRuntimeError. + * 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) { @@ -94,13 +128,15 @@ BaseContext_init(BaseContext *self, PyObject *args, PyObject *kwds) self->current->dict = PyDict_New(); if (!self->current->dict) return -1; - Py_INCREF(self->current->dict); self->current->prev = self->initial; self->stacksize = 3; return 0; } +/** + * Pop the highest layer from the stack and return it + */ static PyObject* BaseContext_pop(BaseContext *self) { @@ -116,6 +152,11 @@ BaseContext_pop(BaseContext *self) 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) { @@ -133,7 +174,7 @@ BaseContext_push(BaseContext *self, PyObject *args) } else Py_INCREF(value); - struct StackLayer *new = malloc(sizeof(struct StackLayer)); + struct StackLayer *new = PyMem_Malloc(sizeof(struct StackLayer)); new->dict = value; new->prev = self->current; self->current = new; @@ -142,6 +183,10 @@ BaseContext_push(BaseContext *self, PyObject *args) 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) { @@ -151,14 +196,17 @@ BaseContext_getstack(BaseContext *self, void *closure) struct StackLayer *current = self->current; int idx = 0; while (current) { - PyList_SetItem(result, idx++, current->dict); Py_INCREF(current->dict); + PyList_SetItem(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) { @@ -166,6 +214,9 @@ BaseContext_getcurrent(BaseContext *self, void *closure) return self->current->dict; } +/** + * Getter that returns a reference to the initial layer in the context. + */ static PyObject* BaseContext_getinitial(BaseContext *self, void *closure) { @@ -173,6 +224,9 @@ BaseContext_getinitial(BaseContext *self, void *closure) return self->initial->dict; } +/** + * Getter that returns a reference to the global layer in the context. + */ static PyObject* BaseContext_getglobals(BaseContext *self, void *closure) { @@ -180,6 +234,10 @@ BaseContext_getglobals(BaseContext *self, void *closure) return self->globals->dict; } +/** + * Generic setter that just raises an exception to notify the user + * that the attribute is read-only. + */ static int BaseContext_readonly(BaseContext *self, PyObject *value, void *closure) { @@ -187,20 +245,23 @@ BaseContext_readonly(BaseContext *self, PyObject *value, void *closure) return -1; } +/** + * 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) { - if (!PyString_Check(item)) { - Py_INCREF(Py_False); - return Py_False; - } + if (!PyString_Check(item)) + goto missing; /* disallow access to internal jinja values */ char *name = PyString_AS_STRING(item); - if (strlen(name) >= 2 && name[0] == ':' && name[1] == ':') { - Py_INCREF(Py_False); - return Py_False; - } + if (name[0] == ':' && name[1] == ':') + goto missing; PyObject *result; struct StackLayer *current = self->current; @@ -235,6 +296,7 @@ BaseContext_getitem(BaseContext *self, PyObject *item) return result; } +missing: if (self->silent) { Py_INCREF(Undefined); return Undefined; @@ -243,6 +305,9 @@ BaseContext_getitem(BaseContext *self, PyObject *item) return NULL; } +/** + * Check if the context contains a given value. + */ static int BaseContext_contains(BaseContext *self, PyObject *item) { @@ -265,6 +330,9 @@ BaseContext_contains(BaseContext *self, PyObject *item) return 0; } +/** + * Set an value in the highest layer or delete one. + */ static int BaseContext_setitem(BaseContext *self, PyObject *item, PyObject *value) { @@ -274,12 +342,16 @@ BaseContext_setitem(BaseContext *self, PyObject *item, PyObject *value) return PyDict_SetItemString(self->current->dict, name, value); } +/** + * Size of the stack. + */ static PyObject* BaseContext_length(BaseContext *self) { return PyInt_FromLong(self->stacksize); } + static PyGetSetDef BaseContext_getsetters[] = { {"stack", (getter)BaseContext_getstack, (setter)BaseContext_readonly, "a read only copy of the internal stack", NULL}, @@ -298,9 +370,12 @@ static PyMemberDef BaseContext_members[] = { static PyMethodDef BaseContext_methods[] = { {"pop", (PyCFunction)BaseContext_pop, METH_NOARGS, - "Pop the highest layer from the stack"}, + "ctx.pop() -> dict\n\n" + "Pop the last layer from the stack and return it."}, {"push", (PyCFunction)BaseContext_push, METH_VARARGS, - "Push one layer to the stack"}, + "ctx.push([layer]) -> layer\n\n" + "Push one layer to the stack. Layer must be a dict " + "or omitted."}, {NULL} /* Sentinel */ }; diff --git a/jinja/datastructure.py b/jinja/datastructure.py index bf7325d..e6dedf4 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -195,14 +195,14 @@ class Context(BaseContext): Dict like object containing the variables for the template. """ - def __init__(self, _environment_, *args, **kwargs): - super(Context, self).__init__(_environment_.silent, - _environment_.globals, - dict(*args, **kwargs)) - + def __init__(self, *args, **kwargs): + environment = args[0] + super(Context, self).__init__(environment.silent, + environment.globals, + dict(*args[1:], **kwargs)) self._translate_func = None self.cache = {} - self.environment = _environment_ + self.environment = environment def to_dict(self): """ diff --git a/tests/runtime/bigbench.py b/tests/runtime/bigbench.py new file mode 100644 index 0000000..2ac975a --- /dev/null +++ b/tests/runtime/bigbench.py @@ -0,0 +1,88 @@ +import jdebug +from time import time +from jinja import Environment +tmpl = Environment().from_string(''' +

Bigtable

+ +{%- for row in table -%} + + {%- for col in row.values() %} + + {%- endfor %} + +{%- endfor %} +
{{ col }}
+ +

Unfilled

+
+ {%- for column in items|slice(3) %} +
+
    + {%- for item in column %} +
  • {{ item }}
  • + {%- endfor %} +
+
+ {%- endfor %} +
+ +

Filled

+
+ {%- for column in items|slice(3, 'missing') %} +
+
    + {%- for item in column %} +
  • {{ item }}
  • + {%- endfor %} +
+
+ {%- endfor %} +
+ +

Filled Table

+ + {%- for row in items|batch(3, ' ') %} + + {%- for column in row %} + + {%- endfor %} + + {%- endfor %} +
{{ column }}
+ +

Unfilled Table

+ + {%- for row in items|batch(3) %} + + {%- for column in row %} + + {%- endfor %} + {%- if row|length < 3 %} + + {%- endif %} + + {%- endfor %} +
{{ column }} 
+ +

Macros

+{% macro foo seq %} +
    + {%- for item in seq %} +
  • {{ caller(item=item) }}
  • + {%- endfor %} +
+{% endmacro %} + +{% call foo(items) -%} + [{{ item }}] +{%- endcall %} +''') + +start = time() +for _ in xrange(50): + tmpl.render( + items=range(200), + table=[dict(a='1',b='2',c='3',d='4',e='5',f='6',g='7',h='8',i='9',j='10') + for x in range(1000)] + ) +print time() - start diff --git a/tests/runtime/bigtable.py b/tests/runtime/bigtable.py index a7deaff..a6aae24 100644 --- a/tests/runtime/bigtable.py +++ b/tests/runtime/bigtable.py @@ -27,6 +27,12 @@ try: except ImportError: have_django = False +try: + from kid import Template as KidTemplate + have_kid = True +except ImportError: + have_kid = False + from Cheetah.Template import Template as CheetahTemplate try: @@ -46,11 +52,20 @@ genshi_tmpl = MarkupTemplate(""" """) +if have_kid: + kid_tmpl = KidTemplate(""" + + + +
+
+""") + if have_django: django_tmpl = DjangoTemplate(""" {% for row in table %} -{% for col in row.values %}{{ col }}{% endfor %} +{% for col in row.values %}{% endfor %} {% endfor %}
{{ col }}
""") @@ -58,7 +73,7 @@ if have_django: jinja_tmpl = Environment().from_string(''' {% for row in table -%} -{% for col in row.values() %}{{ col }}{% endfor %} +{% for col in row.values() %}{% endfor %} {% endfor %}
{{ col }}
''') @@ -68,7 +83,7 @@ cheetah_tmpl = CheetahTemplate(''' #for $row in $table #for $col in $row.values() -$col +$col #end for #end for @@ -81,7 +96,7 @@ if have_mako: % for row in table: % for col in row.values(): - ${col} + ${col} % endfor % endfor @@ -104,6 +119,13 @@ def test_genshi(): stream = genshi_tmpl.generate(table=table) stream.render('html', strip_whitespace=False) +def test_kid(): + """Kid Templates""" + if not have_kid: + return + kid_tmpl.table = table + kid_tmpl.serialize(output="html") + def test_cheetah(): """Cheetah Templates""" cheetah_tmpl.respond() @@ -116,7 +138,8 @@ def test_mako(): def run(which=None, number=10): - tests = ['test_django', 'test_jinja', 'test_genshi', 'test_cheetah', 'test_mako'] + tests = ['test_django', 'test_jinja', 'test_kid', 'test_genshi', + 'test_cheetah', 'test_mako'] if which: tests = filter(lambda n: n[5:] in which, tests) -- 2.26.2