implemented includes
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 11 Apr 2008 20:21:00 +0000 (22:21 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 11 Apr 2008 20:21:00 +0000 (22:21 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/environment.py
jinja2/parser.py
jinja2/runtime.py
test.py

index 74cd0a52774b27a1008b9379cd3cc6c86a30d0cb..63a140f6c200920f7fb5a40240b92023e3e3e5e3 100644 (file)
@@ -79,8 +79,9 @@ class Identifiers(object):
         # 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`."""
@@ -166,13 +167,22 @@ class FrameIdentifierVisitor(NodeVisitor):
 
     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)
@@ -276,6 +286,8 @@ class CodeGenerator(NodeVisitor):
             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()
 
@@ -299,10 +311,11 @@ class CodeGenerator(NodeVisitor):
             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()
@@ -312,7 +325,10 @@ class CodeGenerator(NodeVisitor):
         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:
@@ -320,7 +336,9 @@ class CodeGenerator(NodeVisitor):
                 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)
@@ -395,6 +413,34 @@ class CodeGenerator(NodeVisitor):
         # 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())
@@ -507,8 +553,10 @@ class CodeGenerator(NodeVisitor):
         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
@@ -559,10 +607,10 @@ class CodeGenerator(NodeVisitor):
             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 ')')
 
@@ -706,12 +754,18 @@ class CodeGenerator(NodeVisitor):
 
     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(')')
index 417a03539e09b83a0c59f5da895a888474d300a8..8d5e9d458a9c1239d754f5bcd8872d0b1ba10f65 100644 (file)
@@ -145,4 +145,7 @@ class Template(object):
         return u''.join(self.stream(*args, **kwargs))
 
     def stream(self, *args, **kwargs):
-        return self.root_render_func(dict(*args, **kwargs))
+        gen = self.root_render_func(dict(*args, **kwargs))
+        # skip the first item which is a reference to the stream
+        gen.next()
+        return gen
index 8dc5d5837dd727dbbf3440b1d65ced0bf19aba55..f1195c4424c997802d18ac82b4b57f33939da91a 100644 (file)
@@ -161,12 +161,12 @@ class Parser(object):
         expr = self.parse_expression()
         if self.stream.current.type is 'assign':
             self.stream.next()
-            if not expr.can_assign():
+            node.target = self.stream.expect('name')
+            # make sure that assignments to that name are allowed
+            if not nodes.Name(node.target, 'store').can_assign():
                 raise TemplateSyntaxError('can\'t assign imported template '
-                                          'to this expression.', expr.lineno,
+                                          'to %r' % node.target, expr.lineno,
                                           self.filename)
-            expr.set_ctx('store')
-            node.target = expr
             node.template = self.parse_expression()
         else:
             node.target = None
@@ -186,6 +186,11 @@ class Parser(object):
     def parse_macro(self):
         node = nodes.Macro(lineno=self.stream.expect('macro').lineno)
         node.name = self.stream.expect('name').value
+        # make sure that assignments to that name are allowed
+        if not nodes.Name(node.name, 'store').can_assign():
+            raise TemplateSyntaxError('can\'t assign macro to %r' %
+                                      node.target, node.lineno,
+                                      self.filename)
         self.stream.expect('lparen')
         node.args = args = []
         node.defaults = defaults = []
index 27dab9f9777d78da77f6d5960d7503ba40aa9040..fcd537d9c4dfb338380ae4e3532996872bee044a 100644 (file)
@@ -14,8 +14,8 @@ except ImportError:
     defaultdict = None
 
 
-__all__ = ['subscribe', 'LoopContext', 'StaticLoopContext',
-           'TemplateContext', 'Macro', 'Undefined']
+__all__ = ['subscribe', 'LoopContext', 'StaticLoopContext', 'TemplateContext',
+           'Macro', 'IncludedTemplate', 'Undefined']
 
 
 def subscribe(obj, argument):
@@ -45,14 +45,19 @@ class TemplateContext(dict):
     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
@@ -79,6 +84,29 @@ class TemplateContext(dict):
         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."""
@@ -98,6 +126,7 @@ class LoopContextBase(object):
 
 
 class LoopContext(LoopContextBase):
+    """A loop context for dynamic iteration."""
 
     def __init__(self, iterable, parent=None, enforce_length=False):
         self._iterable = iterable
diff --git a/test.py b/test.py
index 549efe8fa7d907e3976e48fe329988b92bd5d10d..45a5e6522893748f85613a31f5d32d072d94dc8b 100644 (file)
--- a/test.py
+++ b/test.py
@@ -4,15 +4,20 @@ from jinja2.loaders import DictLoader
 env = Environment(loader=DictLoader({
 'child.html': u'''\
 {% extends master_layout or 'master.html' %}
+{% include 'helpers.html' %}
 {% macro get_the_answer() %}42{% endmacro %}
 {% block body %}
     {{ get_the_answer() }}
+    {{ conspirate() }}
 {% endblock %}
 ''',
 'master.html': u'''\
 <!doctype html>
 <title>Foo</title>
 {% block body %}{% endblock %}
+''',
+'helpers.html': u'''\
+{% macro conspirate() %}23{% endmacro %}
 '''
 }))