This has to be implemented before the release:
-Drop special casing template globals
-------------------------------------
-
-The idea some time ago was to partially evaluate templates at compile time.
-For that we decided to not allow globals being overridden by locals as the
-static compiler could have changed semantics. Turns out that the static
-compiler blows up the complexity to something nobody wants to support which
-means that this restriction is kinda pointless.
-
-That should go for good.
-
Pull Attributes Onces
---------------------
.. attribute:: globals
A dict of global variables. These variables are always available
- in a template and (if the optimizer is enabled) may not be
- overridden by templates. As long as no template was loaded it's safe
+ in a template. As long as no template was loaded it's safe
to modify this dict. For more details see :ref:`global-namespace`.
For valid object names have a look at :ref:`identifier-naming`.
The Global Namespace
--------------------
-Variables stored in the :attr:`Environment.globals` or :attr:`Template.globals`
-dicts are special as they are available for imported templates too and will be
-used by the optimizer in future releases to evaluates parts of the template at
-compile time. This is the place where you can put variables and functions
-that should be available all the time.
+Variables stored in the :attr:`Environment.globals` dict are special as they
+are available for imported templates too, even if they are imported without
+context. This is the place where you can put variables and functions
+that should be available all the time. Additionally :attr:`Template.globals`
+exist that are variables available to a specific template that are available
+to all :meth:`~Template.render` calls.
"""
return self.lexer.tokeniter(source, filename)
- def compile(self, source, name=None, filename=None, globals=None,
- raw=False):
+ def compile(self, source, name=None, filename=None, raw=False):
"""Compile a node or template source code. The `name` parameter is
the load name of the template after it was joined using
:meth:`join_path` if necessary, not the filename on the file system.
the `filename` parameter is the estimated filename of the template on
the file system. If the template came from a database or memory this
- can be omitted. The `globals` parameter can be used to provide extra
- variables at compile time for the template. In the future the
- optimizer will be able to evaluate parts of the template at compile
- time based on those variables.
+ can be omitted.
The return value of this method is a python code object. If the `raw`
parameter is `True` the return value will be a string with python
if isinstance(source, basestring):
source = self.parse(source, filename)
if self.optimized:
- node = optimize(source, self, globals or {})
+ node = optimize(source, self)
source = generate(node, self, name, filename)
if raw:
return source
If the `parent` parameter is not `None`, :meth:`join_path` is called
to get the real template name before loading.
- The `globals` parameter can be used to provide compile-time globals.
- In the future this will allow the optimizer to render parts of the
- templates at compile-time.
+ The `globals` parameter can be used to provide temlate wide globals.
+ These variables are available in the context at render time.
If the template does not exist a :exc:`TemplateNotFound` exception is
raised.
"""
globals = self.make_globals(globals)
cls = template_class or self.template_class
- return cls.from_code(self, self.compile(source, globals=globals),
- globals, None)
+ return cls.from_code(self, self.compile(source), globals, None)
def make_globals(self, d):
"""Return a dict for the globals."""
raise exc_type, exc_value, tb
def _generate(self, *args, **kwargs):
- # assemble the context
- context = dict(*args, **kwargs)
-
- # if the environment is using the optimizer locals may never
- # override globals as optimizations might have happened
- # depending on values of certain globals. This assertion goes
- # away if the python interpreter is started with -O
- if __debug__ and self.environment.optimized:
- overrides = set(context) & set(self.globals)
- if overrides:
- plural = len(overrides) != 1 and 's' or ''
- raise AssertionError('the per template variable%s %s '
- 'override%s global variable%s. '
- 'With an enabled optimizer this '
- 'will lead to unexpected results.' %
- (plural, ', '.join(overrides), plural or ' a', plural))
-
- return self.root_render_func(self.new_context(context))
+ """Creates a new context and generates the template."""
+ return self.root_render_func(self.new_context(dict(*args, **kwargs)))
def new_context(self, vars=None, shared=False):
"""Create a new template context for this template. The vars
while 1:
try:
while c_size < size:
- push(next())
- c_size += 1
+ c = next()
+ push(c)
+ if c:
+ c_size += 1
except StopIteration:
if not c_size:
return
if globals is None:
globals = {}
source, filename, uptodate = self.get_source(environment, name)
- code = environment.compile(source, name, filename, globals)
+ code = environment.compile(source, name, filename)
return environment.template_class.from_code(environment, code,
globals, uptodate)
:license: GNU GPL.
"""
from jinja2 import nodes
-from jinja2.visitor import NodeVisitor, NodeTransformer
-from jinja2.runtime import LoopContext
-from jinja2.utils import concat
+from jinja2.visitor import NodeTransformer
-def optimize(node, environment, context_hint=None):
+def optimize(node, environment):
"""The context hint can be used to perform an static optimization
based on the context given."""
optimizer = Optimizer(environment)
- return optimizer.visit(node, ContextStack(context_hint))
-
-
-class ContextStack(object):
- """Simple compile time context implementation."""
- undefined = object()
-
- def __init__(self, initial=None):
- self.stack = [{}]
- if initial is not None:
- self.stack.insert(0, initial)
-
- def push(self):
- self.stack.append({})
-
- def pop(self):
- self.stack.pop()
-
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
-
- def undef(self, name):
- if name in self:
- self[name] = self.undefined
-
- def __contains__(self, key):
- try:
- self[key]
- except KeyError:
- return False
- return True
-
- def __getitem__(self, key):
- for level in reversed(self.stack):
- if key in level:
- rv = level[key]
- if rv is self.undefined:
- raise KeyError(key)
- return rv
- raise KeyError(key)
-
- def __setitem__(self, key, value):
- self.stack[-1][key] = value
-
- def blank(self):
- """Return a new context with nothing but the root scope."""
- return ContextStack(self.stack[0])
+ return optimizer.visit(node)
class Optimizer(NodeTransformer):
def __init__(self, environment):
self.environment = environment
- def visit_Block(self, node, context):
- block_context = context.blank()
- for name in 'super', 'self':
- block_context.undef(name)
- return self.generic_visit(node, block_context)
-
- def visit_For(self, node, context):
- context.push()
- context.undef('loop')
- try:
- return self.generic_visit(node, context)
- finally:
- context.pop()
-
- def visit_Macro(self, node, context):
- context.push()
- for name in 'varargs', 'kwargs', 'caller':
- context.undef(name)
- try:
- return self.generic_visit(node, context)
- finally:
- context.pop()
-
- def visit_CallBlock(self, node, context):
- context.push()
- for name in 'varargs', 'kwargs':
- context.undef(name)
- try:
- return self.generic_visit(node, context)
- finally:
- context.pop()
-
- def visit_FilterBlock(self, node, context):
- """Try to filter a block at compile time."""
- context.push()
- try:
- return self.generic_visit(node, context)
- finally:
- context.pop()
-
- def visit_If(self, node, context):
+ def visit_If(self, node):
+ """Eliminate dead code."""
try:
- val = self.visit(node.test, context).as_const()
+ val = self.visit(node.test).as_const()
except nodes.Impossible:
- return self.generic_visit(node, context)
+ return self.generic_visit(node)
if val:
body = node.body
else:
body = node.else_
result = []
for node in body:
- result.extend(self.visit_list(node, context))
+ result.extend(self.visit_list(node))
return result
- def visit_Name(self, node, context):
- if node.ctx != 'load':
- # something overwrote the variable, we can no longer use
- # the constant from the context
- context.undef(node.name)
- return node
- try:
- return nodes.Const.from_untrusted(context[node.name],
- lineno=node.lineno,
- environment=self.environment)
- except (KeyError, nodes.Impossible):
- return node
-
- def visit_Import(self, node, context):
- rv = self.generic_visit(node, context)
- context.undef(node.target)
- return rv
-
- def visit_FromImport(self, node, context):
- rv = self.generic_visit(node, context)
- for name in node.names:
- if isinstance(name, tuple):
- context.undef(name[1])
- else:
- context.undef(name)
- return rv
-
- def fold(self, node, context):
+ def fold(self, node):
"""Do constant folding."""
- node = self.generic_visit(node, context)
+ node = self.generic_visit(node)
try:
return nodes.Const.from_untrusted(node.as_const(),
lineno=node.lineno,