all calls are proxied by context.call now so that we can inject environment and context as first arguments. This slows calls down a bit but is a lot more user friendly. Added first draft of FAQ
--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index e43c362..83afc34 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -438,21 +438,13 @@
self._write_debug_info = node.lineno
self._last_line = node.lineno
- def signature(self, node, frame, have_comma=True, extra_kwargs=None):
+ def signature(self, node, frame, extra_kwargs=None):
"""Writes a function call to the stream for the current node.
- Per default it will write a leading comma but this can be
- disabled by setting have_comma to False. The extra keyword
+ A leading comma is added automatically. The extra keyword
arguments may not include python keywords otherwise a syntax
error could occour. The extra keyword arguments should be given
as python dict.
"""
- have_comma = have_comma and [True] or []
- def touch_comma():
- if have_comma:
- self.write(', ')
- else:
- have_comma.append(True)
-
# if any of the given keyword arguments is a python keyword
# we have to make sure that no invalid call is created.
kwarg_workaround = False
@@ -462,28 +454,25 @@
break
for arg in node.args:
- touch_comma()
+ self.write(', ')
self.visit(arg, frame)
if not kwarg_workaround:
for kwarg in node.kwargs:
- touch_comma()
+ self.write(', ')
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in extra_kwargs.iteritems():
- touch_comma()
- self.write('%s=%s' % (key, value))
+ self.write(', %s=%s' % (key, value))
if node.dyn_args:
- touch_comma()
- self.write('*')
+ self.write(', *')
self.visit(node.dyn_args, frame)
if kwarg_workaround:
- touch_comma()
if node.dyn_kwargs is not None:
- self.write('**dict({')
+ self.write(', **dict({')
else:
- self.write('**{')
+ self.write(', **{')
for kwarg in node.kwargs:
self.write('%r: ' % kwarg.key)
self.visit(kwarg.value, frame)
@@ -499,8 +488,7 @@
self.write('}')
elif node.dyn_kwargs is not None:
- touch_comma()
- self.write('**')
+ self.write(', **')
self.visit(node.dyn_kwargs, frame)
def pull_locals(self, frame):
@@ -1353,11 +1341,12 @@
def visit_Call(self, node, frame, forward_caller=False):
if self.environment.sandboxed:
- self.write('environment.call(')
+ self.write('environment.call(context, ')
+ else:
+ self.write('context.call(')
self.visit(node.node, frame)
- self.write(self.environment.sandboxed and ', ' or '(')
extra_kwargs = forward_caller and {'caller': 'caller'} or None
- self.signature(node, frame, False, extra_kwargs)
+ self.signature(node, frame, extra_kwargs)
self.write(')')
def visit_Keyword(self, node, frame):
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 568220f..9eb5460 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -14,7 +14,6 @@
"""
import operator
from copy import copy
-from types import FunctionType
from itertools import chain, izip
from collections import deque
from jinja2.utils import Markup
@@ -550,11 +549,10 @@
# don't evaluate context functions
args = [x.as_const() for x in self.args]
- if type(obj) is FunctionType:
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif obj.environmentfunction:
- args.insert(0, self.environment)
+ if getattr(obj, 'contextfunction', False):
+ raise Impossible()
+ elif getattr(obj, 'environmentfunction', False):
+ args.insert(0, self.environment)
kwargs = dict(x.as_const() for x in self.kwargs)
if self.dyn_args is not None:
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 86ee570..8ca1bd2 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -150,6 +150,7 @@
node = nodes.Block(lineno=self.stream.next().lineno)
node.name = self.stream.expect('name').value
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
+ self.stream.skip_if('name:' + node.name)
return node
def parse_extends(self):
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index fb72ed4..1325b17 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -66,14 +66,6 @@
self.exported_vars = set()
self.name = name
- # bind functions to the context of environment if required
- for name, obj in parent.iteritems():
- if type(obj) is FunctionType:
- if getattr(obj, 'contextfunction', 0):
- vars[name] = partial(obj, self)
- elif getattr(obj, 'environmentfunction', 0):
- vars[name] = partial(obj, environment)
-
# create the initial mapping of blocks. Whenever template inheritance
# takes place the runtime will update this mapping with the new blocks
# from the template.
@@ -122,6 +114,17 @@
"""
return dict(self.parent, **self.vars)
+ def call(__self, __obj, *args, **kwargs):
+ """Called by the template code to inject the current context
+ or environment as first arguments. Then forwards the call to
+ the object with the arguments and keyword arguments.
+ """
+ if getattr(__obj, 'contextfunction', 0):
+ args = (__self,) + args
+ elif getattr(__obj, 'environmentfunction', 0):
+ args = (__self.environment,) + args
+ return __obj(*args, **kwargs)
+
def _all(meth):
proxy = lambda self: getattr(self.get_all(), meth)()
proxy.__doc__ = getattr(dict, meth).__doc__
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index c558c73..b0de8e7 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -190,13 +190,13 @@
), name=argument, exc=SecurityError)
return self.undefined(obj=obj, name=argument)
- def call(__self, __obj, *args, **kwargs):
+ def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
raise SecurityError('%r is not safely callable' % (__obj,))
- return __obj(*args, **kwargs)
+ return __context.call(__obj, *args, **kwargs)
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 2a671d0..258961f 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -64,8 +64,8 @@
def contextfunction(f):
"""This decorator can be used to mark a callable as context callable. A
- context callable is passed the active context as first argument if it
- was directly stored in the context.
+ context callable is passed the active context as first argument when
+ called from the template.
"""
f.contextfunction = True
return f
@@ -74,7 +74,7 @@
def environmentfunction(f):
"""This decorator can be used to mark a callable as environment callable.
A environment callable is passed the current environment as first argument
- if it was directly stored in the context.
+ when called from the template.
"""
f.environmentfunction = True
return f