# names that are declared by parameters
self.declared_parameter = set()
- # filters that are referenced
+ # filters/tests that are referenced
self.filters = set()
+ self.tests = set()
def add_special(self, name):
"""Register a special name like `loop`."""
def visit_Filter(self, node):
self.generic_visit(node)
- if node.name not in self.identifiers.filters:
- self.identifiers.filters.add(node.name)
+ self.identifiers.filters.add(node.name)
+
+ def visit_Test(self, node):
+ self.generic_visit(node)
+ self.identifiers.tests.add(node.name)
def visit_Macro(self, node):
"""Macros set local."""
self.identifiers.declared_locally.add(node.name)
+ def visit_Include(self, node):
+ """Some includes set local."""
+ self.generic_visit(node)
+ if node.target is not None:
+ self.identifiers.declared_locally.add(node.target)
+
def visit_Assign(self, node):
"""Visit assignments in the correct order."""
self.visit(node.node)
self.writeline('l_%s = context[%r]' % (name, name))
for name in frame.identifiers.filters:
self.writeline('f_%s = environment.filters[%r]' % (name, name))
+ for name in frame.identifiers.tests:
+ self.writeline('t_%s = environment.tests[%r]' % (name, name))
if not no_indent:
self.outdent()
self.blocks[block.name] = block
# generate the root render function.
- self.writeline('def root(globals, environment=environment):', extra=1)
+ self.writeline('def root(globals, environment=environment'
+ ', standalone=False):', extra=1)
self.indent()
- self.writeline('context = TemplateContext(globals, %r, blocks)' %
- self.filename)
+ self.writeline('context = TemplateContext(globals, %r, blocks'
+ ', standalone)' % self.filename)
if have_extends:
self.writeline('parent_root = None')
self.outdent()
frame.inspect(node.body)
frame.toplevel = frame.rootlevel = True
self.pull_locals(frame)
- self.blockvisit(node.body, frame, True)
+ self.indent()
+ self.writeline('yield context')
+ self.outdent()
+ self.blockvisit(node.body, frame)
# make sure that the parent root is called.
if have_extends:
self.indent()
self.writeline('if parent_root is not None:')
self.indent()
- self.writeline('for event in parent_root(context):')
+ self.writeline('stream = parent_root(context)')
+ self.writeline('stream.next()')
+ self.writeline('for event in stream:')
self.indent()
self.writeline('yield event')
self.outdent(1 + self.has_known_extends)
# and now we have one more
self.extends_so_far += 1
+ def visit_Include(self, node, frame):
+ """Handles includes."""
+ # simpled include is include into a variable. This kind of
+ # include works the same on every level, so we handle it first.
+ if node.target is not None:
+ self.writeline('l_%s = ' % node.target, node)
+ if frame.toplevel:
+ self.write('context[%r] = ' % node.target)
+ self.write('IncludedTemplate(environment, context, ')
+ self.visit(node.template, frame)
+ self.write(')')
+ return
+
+ self.writeline('included_stream = environment.get_template(', node)
+ self.visit(node.template, frame)
+ self.write(').root_render_func(context, standalone=True)')
+ self.writeline('included_context = included_stream.next()')
+ self.writeline('for event in included_stream:')
+ self.indent()
+ self.writeline('yield event')
+ self.outdent()
+
+ # if we have a toplevel include the exported variables are copied
+ # into the current context without exporting them. context.udpate
+ # does *not* mark the variables as exported
+ if frame.toplevel:
+ self.writeline('context.update(included_context.get_exported())')
+
def visit_For(self, node, frame):
loop_frame = frame.inner()
loop_frame.inspect(node.iter_child_nodes())
self.newline(node)
if self.environment.finalize is unicode:
finalizer = 'unicode'
+ have_finalizer = False
else:
finalizer = 'context.finalize'
+ have_finalizer = False
# if we are in the toplevel scope and there was already an extends
# statement we have to add a check that disables our yield(s) here
for idx, argument in enumerate(arguments):
if idx:
self.write(', ')
- if finalizer != 'unicode':
+ if have_finalizer:
self.write('(')
self.visit(argument, frame)
- if finalizer != 'unicode':
+ if have_finalizer:
self.write(')')
self.write(idx == 0 and ',)' or ')')
def visit_Filter(self, node, frame):
self.write('f_%s(' % node.name)
+ func = self.environment.filters.get(node.name)
+ if getattr(func, 'contextfilter', False):
+ self.write('context, ')
self.visit(node.node, frame)
self.signature(node, frame)
self.write(')')
def visit_Test(self, node, frame):
- self.write('environment.tests[%r](')
+ self.write('t_%s(' % node.name)
+ func = self.environment.tests.get(node.name)
+ if getattr(func, 'contexttest', False):
+ self.write('context, ')
self.visit(node.node, frame)
self.signature(node, frame)
self.write(')')
defaultdict = None
-__all__ = ['subscribe', 'LoopContext', 'StaticLoopContext',
- 'TemplateContext', 'Macro', 'Undefined']
+__all__ = ['subscribe', 'LoopContext', 'StaticLoopContext', 'TemplateContext',
+ 'Macro', 'IncludedTemplate', 'Undefined']
def subscribe(obj, argument):
the exported variables for example).
"""
- def __init__(self, globals, filename, blocks):
+ def __init__(self, globals, filename, blocks, standalone):
dict.__init__(self, globals)
self.exported = set()
self.filename = filename
self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
- if isinstance(globals, TemplateContext):
- for name, parent_blocks in globals.blocks.iteritems():
- self.blocks.setdefault(name, []).extend(parent_blocks)
+
+ # if the template is in standalone mode we don't copy the blocks over.
+ # this is used for includes for example but otherwise, if the globals
+ # are a template context, this template is participating in a template
+ # inheritance chain and we have to copy the blocks over.
+ if not standalone and isinstance(globals, TemplateContext):
+ for name, parent_blocks in globals.blocks.iteritems():
+ self.blocks.setdefault(name, []).extend(parent_blocks)
def __setitem__(self, key, value):
"""If we set items to the dict we track the variables set so
def __missing__(self, key):
return Undefined(key)
+ def __repr__(self):
+ return '<%s %s of %r>' % (
+ self.__class__.__name__,
+ dict.__repr__(self),
+ self.filename
+ )
+
+
+class IncludedTemplate(object):
+ """Represents an included template."""
+
+ def __init__(self, environment, context, template):
+ root = environment.get_template(template).root_render_func
+ gen = root(context, standalone=True)
+ self._context = gen.next().get_exported()
+ self._rendered_body = u''.join(gen)
+
+ def __getitem__(self, name):
+ return self._context[name]
+
+ def __unicode__(self):
+ return self._context
+
class LoopContextBase(object):
"""Helper for extended iteration."""
class LoopContext(LoopContextBase):
+ """A loop context for dynamic iteration."""
def __init__(self, iterable, parent=None, enforce_length=False):
self._iterable = iterable