]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
add a node for derived context references 861/head
authorDaniel Watkins <daniel.watkins@canonical.com>
Fri, 1 Jun 2018 21:16:29 +0000 (17:16 -0400)
committerDavid Lord <davidism@gmail.com>
Mon, 2 Dec 2019 22:03:12 +0000 (14:03 -0800)
This allows extensions to access locals in the scope from which they
were called.

CHANGES.rst
jinja2/compiler.py
jinja2/nodes.py
tests/test_ext.py

index 037dfaa49b2ca3b39d9fb66fc2dffd3cff3dc766..f5f2e7cf18b3913301627d16455daa143e12aeca 100644 (file)
@@ -85,6 +85,9 @@ Unreleased
     and source for Python >= 3.7. :issue:`1104`
 -   Tracebacks for template syntax errors in Python 3 no longer show
     internal compiler frames. :issue:`763`
+-   Add a ``DerivedContextReference`` node that can be used by
+    extensions to get the current context and local variables such as
+    ``loop``. :issue:`860`
 
 
 Version 2.10.3
index 3c69df88be0b82e2b6b2c1a8ea3609361c309c39..addf71ea25db83aa313668c2fa4beb0303d7182a 100644 (file)
@@ -1720,6 +1720,9 @@ class CodeGenerator(NodeVisitor):
     def visit_ContextReference(self, node, frame):
         self.write('context')
 
+    def visit_DerivedContextReference(self, node, frame):
+        self.write(self.derive_context(frame))
+
     def visit_Continue(self, node, frame):
         self.writeline('continue', node)
 
index e46aa916778fbd1f29bb2fe16283aac5a10a3ce4..00e03295c61e46fdc99c9d352d768b77f0bf60b7 100644 (file)
@@ -952,6 +952,15 @@ class ContextReference(Expr):
     """
 
 
+class DerivedContextReference(Expr):
+    """Return the current template context including locals. Behaves
+    exactly like :class:`ContextReference`, but includes local
+    variables, such as from a ``for`` loop.
+
+    .. versionadded:: 2.11
+    """
+
+
 class Continue(Stmt):
     """Continue a loop."""
 
index 1c349bd13a11bf740063716d405c8f522ae73dfc..797527129ea7f448fb9a6e16aaca96915f678758 100644 (file)
@@ -109,24 +109,30 @@ newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
 class ExampleExtension(Extension):
     tags = set(['test'])
     ext_attr = 42
+    context_reference_node_cls = nodes.ContextReference
 
     def parse(self, parser):
         return nodes.Output([self.call_method('_dump', [
             nodes.EnvironmentAttribute('sandboxed'),
             self.attr('ext_attr'),
             nodes.ImportedName(__name__ + '.importable_object'),
-            nodes.ContextReference()
+            self.context_reference_node_cls()
         ])]).set_lineno(next(parser.stream).lineno)
 
     def _dump(self, sandboxed, ext_attr, imported_object, context):
-        return '%s|%s|%s|%s' % (
+        return '%s|%s|%s|%s|%s' % (
             sandboxed,
             ext_attr,
             imported_object,
-            context.blocks
+            context.blocks,
+            context.get('test_var')
         )
 
 
+class DerivedExampleExtension(ExampleExtension):
+    context_reference_node_cls = nodes.DerivedContextReference
+
+
 class PreprocessorExtension(Extension):
 
     def preprocess(self, source, name, filename=None):
@@ -205,7 +211,18 @@ class TestExtensions(object):
     def test_extension_nodes(self):
         env = Environment(extensions=[ExampleExtension])
         tmpl = env.from_string('{% test %}')
-        assert tmpl.render() == 'False|42|23|{}'
+        assert tmpl.render() == 'False|42|23|{}|None'
+
+    def test_contextreference_node_passes_context(self):
+        env = Environment(extensions=[ExampleExtension])
+        tmpl = env.from_string('{% set test_var="test_content" %}{% test %}')
+        assert tmpl.render() == 'False|42|23|{}|test_content'
+
+    def test_contextreference_node_can_pass_locals(self):
+        env = Environment(extensions=[DerivedExampleExtension])
+        tmpl = env.from_string(
+            '{% for test_var in ["test_content"] %}{% test %}{% endfor %}')
+        assert tmpl.render() == 'False|42|23|{}|test_content'
 
     def test_identifier(self):
         assert ExampleExtension.identifier == __name__ + '.ExampleExtension'