]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
WIP for overlay scopes
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 10:10:55 +0000 (11:10 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 10:22:01 +0000 (11:22 +0100)
jinja2/compiler.py
jinja2/idtracking.py
jinja2/nodes.py
jinja2/runtime.py

index 9a6ac6a034f75df853a2602f1fb9b4278ce277c2..18cc2ec074aee06f80f254099b85ac20be59203f 100644 (file)
@@ -302,6 +302,9 @@ class CodeGenerator(NodeVisitor):
         # Tracks parameter definition blocks
         self._param_def_block = []
 
+        # Tracks the current context.
+        self._context_reference_stack = ['context']
+
     # -- Various compilation helpers
 
     def fail(self, msg, lineno):
@@ -473,8 +476,8 @@ class CodeGenerator(NodeVisitor):
             if action == VAR_LOAD_PARAMETER:
                 pass
             elif action == VAR_LOAD_RESOLVE:
-                self.writeline('%s = resolve(%r)' %
-                               (target, param))
+                self.writeline('%s = %s(%r)' %
+                               (target, self.get_resolve_func(), param))
             elif action == VAR_LOAD_ALIAS:
                 self.writeline('%s = %s' % (target, param))
             elif action == VAR_LOAD_UNDEFINED:
@@ -626,6 +629,27 @@ class CodeGenerator(NodeVisitor):
         if self._param_def_block:
             self._param_def_block[-1].discard(target)
 
+    def push_context_reference(self, target):
+        self._context_reference_stack.append(target)
+
+    def pop_context_reference(self):
+        self._context_reference_stack.pop()
+
+    def get_context_ref(self):
+        return self._context_reference_stack[-1]
+
+    def get_resolve_func(self):
+        target = self._context_reference_stack[-1]
+        if target == 'context':
+            return 'resolve'
+        return '%s.resolve' % target
+
+    def derive_context(self, frame):
+        return '%s.derived(%s)' % (
+            self.get_context_ref(),
+            self.dump_local_context(frame),
+        )
+
     def parameter_is_undeclared(self, target):
         """Checks if a given target is an undeclared parameter."""
         if not self._param_def_block:
@@ -793,8 +817,11 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('if parent_template is None:')
                 self.indent()
                 level += 1
-        context = node.scoped and (
-            'context.derived(%s)' % self.dump_local_context(frame)) or 'context'
+
+        if node.scoped:
+            context = self.derive_context(frame)
+        else:
+            context = self.get_context_ref()
 
         if supports_yield_from and not self.environment.is_async and \
            frame.buffer is None:
@@ -1619,6 +1646,20 @@ class CodeGenerator(NodeVisitor):
         self.blockvisit(node.body, scope_frame)
         self.leave_frame(scope_frame)
 
+    def visit_OverlayScope(self, node, frame):
+        ctx = self.temporary_identifier()
+        self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
+        self.writeline('%s.vars = ' % ctx)
+        self.visit(node.context, frame)
+        self.push_context_reference(ctx)
+
+        scope_frame = Frame(frame.eval_ctx)
+        scope_frame.symbols.analyze_node(node)
+        self.enter_frame(scope_frame)
+        self.blockvisit(node.body, scope_frame)
+        self.leave_frame(scope_frame)
+        self.pop_context_reference()
+
     def visit_EvalContextModifier(self, node, frame):
         for keyword in node.options:
             self.writeline('context.eval_ctx.%s = ' % keyword.key)
index b00dab8c1c604ce1568ffb9eca37724e96e7e2f7..697d4a20a73cc47eb0d45f7774039637c22c05d5 100644 (file)
@@ -167,6 +167,10 @@ class RootVisitor(NodeVisitor):
         for child in node.iter_child_nodes(exclude=('call',)):
             self.sym_visitor.visit(child)
 
+    def visit_OverlayScope(self, node, **kwargs):
+        for child in node.body:
+            self.sym_visitor.visit(child)
+
     def visit_For(self, node, for_branch='body', **kwargs):
         if node.test is not None:
             self.sym_visitor.visit(node.test)
@@ -258,3 +262,6 @@ class FrameSymbolVisitor(NodeVisitor):
 
     def visit_Block(self, node, **kwargs):
         """Stop visiting at blocks."""
+
+    def visit_OverlayScope(self, node, **kwargs):
+        """Do not visit into overlay scopes."""
index d1a4c381922991a630d7af92c84db45df315b152..a7641a4895360b3b357cc45e850426c97da00336 100644 (file)
@@ -905,6 +905,14 @@ class Scope(Stmt):
     fields = ('body',)
 
 
+class OverlayScope(Stmt):
+    """An overlay scope for extensions.  This is a largely unoptimized scope
+    that however can be used to introduce completely arbitrar variables into
+    a sub scope from a dictionary or dictionary like object.
+    """
+    fields = ('context', 'body')
+
+
 class EvalContextModifier(Stmt):
     """Modifies the eval context.  For each option that should be modified,
     a :class:`Keyword` has to be added to the :attr:`options` list.
index 5c2089b8c0a6206a8ed0465cd418e3008b32c748..deb1c89794c756cc5c2ff9a2225e3d2207d48adf 100644 (file)
@@ -24,7 +24,7 @@ from jinja2._compat import imap, text_type, iteritems, \
 __all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
            'TemplateRuntimeError', 'missing', 'concat', 'escape',
            'markup_join', 'unicode_join', 'to_string', 'identity',
-           'TemplateNotFound']
+           'make_overlay_resolve', 'TemplateNotFound']
 
 #: the name of the function that is used to convert something into
 #: a string.  We can just use the text type here.