[svn] updated documentation regarding "{% call %}" and documented speedup module...
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 21 Apr 2007 07:39:06 +0000 (09:39 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 21 Apr 2007 07:39:06 +0000 (09:39 +0200)
--HG--
branch : trunk

CHANGES
docs/src/designerdoc.txt
jinja/_native.py
jinja/_speedups.c
jinja/datastructure.py
tests/runtime/bigbench.py [new file with mode: 0644]
tests/runtime/bigtable.py

diff --git a/CHANGES b/CHANGES
index 9e5e64c51ca192fbac7d7e9264584cb1b171618c..68a53835e8b4f2844b72c222d6e049bd6ef99adb 100644 (file)
--- 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
 -----------
index 3040e14f6f7388b2cdfc732fe324c0b1d503d1ea..1d6cf665b9f0b06e33aa827b23aebd82dd43a30e 100644 (file)
@@ -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 %}
+          <div class="dialog">
+            <h3>{{ title }}</h3>
+            <div class="text">
+              {{ caller() }}
+            </div>
+          </div>
+        {% 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 %}
+          <ul>
+          {%- for item in items %}
+            <li>{{ caller(item=item) }}</li>
+          {%- endfor %}
+          </ul>
+        {%- endmacro %}
+
+        {% call makelist([1, 2, 3, 4, 5, 6]) -%}
+          [[{{ item }}]]
+        {% endcall %}
+
+    This will then produce this output:
+
+    .. sourcecode:: html
+
+        <ul>
+          <li>[[1]]</li>
+          <li>[[2]]</li>
+          <li>[[3]]</li>
+          <li>[[4]]</li>
+          <li>[[5]]</li>
+          <li>[[6]]</li>
+        </ul>
+
 .. _slicing chapter: http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
 .. _range function: http://docs.python.org/tut/node6.html#SECTION006300000000000000000
index c5f5786516521b4d81fadf01016bde46b7ba687e..4abbb5c1154c118abe0ea54b8acb403c487dddb8 100644 (file)
@@ -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)
 
index 2f654d7ea0a6e4b503debdf171f35122ef17f188..e57ac3d74cf0dd04f267aa3015e76e9c91bef901 100644 (file)
@@ -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.
  */
 #include <Python.h>
 #include <structmember.h>
 
-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 */
 };
 
index bf7325d99bad901e085ff2667b775fffbb134bfe..e6dedf4c7514daf395b502c402ff70aeb10e188d 100644 (file)
@@ -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 (file)
index 0000000..2ac975a
--- /dev/null
@@ -0,0 +1,88 @@
+import jdebug
+from time import time
+from jinja import Environment
+tmpl = Environment().from_string('''
+<h1>Bigtable</h1>
+<table>
+{%- for row in table -%}
+  <tr>
+  {%- for col in row.values() %}
+    <td>{{ col }}</td>
+  {%- endfor %}
+  </tr>
+{%- endfor %}
+</table>
+
+<h1>Unfilled</h1>
+<div class="index">
+  {%- for column in items|slice(3) %}
+  <div class="col-{{ loop.index }}">
+    <ul>
+    {%- for item in column %}
+      <li>{{ item }}</li>
+    {%- endfor %}
+    </ul>
+  </div>
+  {%- endfor %}
+</div>
+
+<h1>Filled</h1>
+<div class="index">
+  {%- for column in items|slice(3, 'missing') %}
+  <div class="col-{{ loop.index }}">
+    <ul>
+    {%- for item in column %}
+      <li>{{ item }}</li>
+    {%- endfor %}
+    </ul>
+  </div>
+  {%- endfor %}
+</div>
+
+<h1>Filled Table</h1>
+<table>
+  {%- for row in items|batch(3, '&nbsp;') %}
+  <tr>
+    {%- for column in row %}
+    <td>{{ column }}</td>
+    {%- endfor %}
+  </tr>
+  {%- endfor %}
+</table>
+
+<h1>Unfilled Table</h1>
+<table>
+  {%- for row in items|batch(3) %}
+  <tr>
+    {%- for column in row %}
+    <td>{{ column }}</td>
+    {%- endfor %}
+    {%- if row|length < 3 %}
+    <td colspan="{{ 3 - (row|length) }}">&nbsp;</td>
+    {%- endif %}
+  </tr>
+  {%- endfor %}
+</table>
+
+<h1>Macros</h1>
+{% macro foo seq %}
+  <ul>
+  {%- for item in seq %}
+    <li>{{ caller(item=item) }}</li>
+  {%- endfor %}
+  </ul>
+{% 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
index a7deaff279b0648fd17db9db5121e5df18e5f502..a6aae24a9a7b6da3b47b18f536673e97d2d4fbe2 100644 (file)
@@ -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("""
 </table>
 """)
 
+if have_kid:
+    kid_tmpl = KidTemplate("""
+<table xmlns:py="http://purl.org/kid/ns#">
+<tr py:for="row in table">
+<td py:for="c in row.values()" py:content="c"/>
+</tr>
+</table>
+""")
+
 if have_django:
     django_tmpl = DjangoTemplate("""
 <table>
 {% for row in table %}
-<tr>{% for col in row.values %}{{ col }}{% endfor %}</tr>
+<tr>{% for col in row.values %}<td>{{ col }}</td>{% endfor %}</tr>
 {% endfor %}
 </table>
 """)
@@ -58,7 +73,7 @@ if have_django:
 jinja_tmpl = Environment().from_string('''
 <table>
 {% for row in table -%}
-<tr>{% for col in row.values() %}{{ col }}{% endfor %}</tr>
+<tr>{% for col in row.values() %}<td>{{ col }}</td>{% endfor %}</tr>
 {% endfor %}
 </table>
 ''')
@@ -68,7 +83,7 @@ cheetah_tmpl = CheetahTemplate('''
 #for $row in $table
 <tr>
 #for $col in $row.values()
-$col
+<td>$col</td>
 #end for
 </tr>
 #end for
@@ -81,7 +96,7 @@ if have_mako:
 % for row in table:
 <tr>
 % for col in row.values():
-    ${col}
+    <td>${col}</td>
 % endfor
 </tr>
 % 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)