[svn] implemented `{{ super() }}` for blocks. This checkin makes jinja much slower...
authorArmin Ronacher <armin.ronacher@active-4.com>
Thu, 29 Mar 2007 18:03:40 +0000 (20:03 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Thu, 29 Mar 2007 18:03:40 +0000 (20:03 +0200)
--HG--
branch : trunk

jdebug.py
jinja/datastructure.py
jinja/nodes.py
jinja/translators/python.py
tests/runtime/super.py [new file with mode: 0644]

index 5de88155f6ccd013be5850142c576ac24323be65..109c5c6c213fe17b54686d866fbbdf053974752d 100644 (file)
--- a/jdebug.py
+++ b/jdebug.py
@@ -8,6 +8,8 @@
     :copyright: 2006 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+import os
+import sys
 from jinja import Environment
 from jinja.parser import Parser
 from jinja.lexer import Lexer
@@ -19,6 +21,18 @@ __all__ = ['e', 't', 'p', 'l']
 e = Environment()
 t = e.from_string
 
+
+if os.environ.get('JDEBUG_SOURCEPRINT'):
+    original_translate = PythonTranslator.translate
+
+    def debug_translate(self):
+        rv = original_translate(self)
+        sys.stderr.write('## GENERATED SOURCE:\n%s\n' % rv)
+        return rv
+
+    PythonTranslator.translate = debug_translate
+
+
 def p(x):
     print PythonTranslator(e, Parser(e, x).parse()).translate()
 
index 3b8448e8a2f71533e7e5cef0f82f7f8dc04d5cab..e64f687c4ce04a60841b0ecf01c44b0f21f46134 100644 (file)
@@ -316,6 +316,26 @@ class CycleContext(object):
         return seq[self.lineno]
 
 
+class BlockContext(object):
+    """
+    Helper class for ``{{ super() }}``.
+    """
+    jinja_allowed_attributes = ['name']
+
+    def __init__(self, name, stack, level, context):
+        self.name = name
+        self.context = context
+        if len(stack) > level:
+            self.block = stack[level]
+        else:
+            self.block = None
+
+    def __call__(self):
+        if self.block is None:
+            raise TemplateRuntimeError('no super block for %r' % self.name)
+        return self.block(self.context)
+
+
 class TokenStream(object):
     """
     A token stream works like a normal generator just that
index b19994e211f63f6053ca76f29528d991b4ee6153..b9d30519c5dd1b3f1d01a24596c5801dcac56c29 100644 (file)
@@ -272,6 +272,14 @@ class Block(Node):
         assert node.__class__ is Block
         self.__dict__.update(node.__dict__)
 
+    def clone(self):
+        """
+        Create an independent clone of this node.
+        """
+        rv = Block(None, None, None)
+        rv.__dict__.update(self.__dict__)
+        return rv
+
     def get_items(self):
         return [self.name, self.body]
 
index 96082100a4deb540694fdf1756e9f75d57a4be4d..e8e9a59a8ec074ad4158d4af52c6b7f2d567cffb 100644 (file)
@@ -168,7 +168,7 @@ class PythonTranslator(Translator):
         """
         return (' ' * (self.indention * 4)) + text
 
-    def nodeinfo(self, node):
+    def nodeinfo(self, node, force=False):
         """
         Return a comment that helds the node informations or None
         if there is no need to add a debug comment.
@@ -179,7 +179,7 @@ class PythonTranslator(Translator):
             node.filename,
             node.lineno
         )
-        if rv != self.last_debug_comment:
+        if force or rv != self.last_debug_comment:
             self.last_debug_comment = rv
             return rv
 
@@ -260,6 +260,7 @@ class PythonTranslator(Translator):
         requirements_todo = []
         parent = None
         overwrites = {}
+        blocks = {}
 
         while node.extends is not None:
             # handle all requirements but not those from the
@@ -271,11 +272,17 @@ class PythonTranslator(Translator):
             # load the template we inherit from and add not known blocks
             parent = self.environment.loader.parse(node.extends.template,
                                                    node.filename)
-            # look up all block nodes and let them override each other
+            # look up all block nodes in the current template and
+            # add them to the override dict
             for n in get_nodes(nodes.Block, node):
                 overwrites[n.name] = n
+            # handle direct overrides
             for n in get_nodes(nodes.Block, parent):
+                # an overwritten block for the parent template. handle that
+                # override in the template and register it in the deferred
+                # block dict.
                 if n.name in overwrites:
+                    blocks.setdefault(n.name, []).append(n.clone())
                     n.replace(overwrites[n.name])
             # make the parent node the new node
             node = parent
@@ -287,22 +294,26 @@ class PythonTranslator(Translator):
                 if n.__class__ in (nodes.Set, nodes.Macro, nodes.Include):
                     requirements.append(n)
 
+        # aliases boilerplate
+        aliases = ['%s = environment.%s' % (item, item) for item in
+                   ['get_attribute', 'perform_test', 'apply_filters',
+                    'call_function', 'call_function_simple', 'finish_var']]
+
         # bootstrapping code
         lines = [
             'from __future__ import division\n'
-            'from jinja.datastructure import Undefined, LoopContext, CycleContext\n'
+            'from jinja.datastructure import Undefined, LoopContext, '
+            'CycleContext, BlockContext\n'
             'from jinja.utils import buffereater\n'
+            '%s\n'
             '__name__ = %r\n\n'
             'def generate(context):\n'
             '    assert environment is context.environment\n'
-            '    get_attribute = environment.get_attribute\n'
-            '    perform_test = environment.perform_test\n'
-            '    apply_filters = environment.apply_filters\n'
-            '    call_function = environment.call_function\n'
-            '    call_function_simple = environment.call_function_simple\n'
-            '    finish_var = environment.finish_var\n'
             '    ctx_push = context.push\n'
-            '    ctx_pop = context.pop\n' % node.filename
+            '    ctx_pop = context.pop' % (
+                '\n'.join(aliases),
+                node.filename
+            )
         ]
 
         # we have requirements? add them here.
@@ -323,9 +334,40 @@ class PythonTranslator(Translator):
                 '            return translator.gettext(s) % (r or {})\n'
                 '        return translator.ngettext(s, p, r[n]) % (r or {})'
             )
+
+        # add body lines and "generator hook"
         lines.extend(body_lines)
         lines.append('    if False:\n        yield None')
 
+        # add the missing blocks
+        if blocks:
+            block_items = blocks.items()
+            block_items.sort()
+            dict_lines = []
+            for name, items in block_items:
+                tmp = []
+                for idx, item in enumerate(items):
+                    # ensure that the indention is correct
+                    self.indention = 1
+                    func_name = 'block_%s_%s' % (name, idx)
+                    lines.extend([
+                        '\ndef %s(context):' % func_name,
+                        '    ctx_push = context.push',
+                        '    ctx_pop = context.pop',
+                        '    if False:',
+                        '        yield None'
+                    ])
+                    nodeinfo = self.nodeinfo(item, True)
+                    if nodeinfo:
+                        lines.append(self.indent(nodeinfo))
+                    lines.append(self.handle_block(item, idx + 1))
+                    tmp.append('buffereater(%s)' % func_name)
+                dict_lines.append('    %r: %s' % (
+                    str(name),
+                    _to_tuple(tmp)
+                ))
+            lines.append('\nblocks = {\n%s\n}' % ',\n'.join(dict_lines))
+
         return '\n'.join(lines)
 
     def handle_template_text(self, node):
@@ -346,7 +388,7 @@ class PythonTranslator(Translator):
         if buf:
             nodeinfo = self.nodeinfo(node)
             if nodeinfo:
-                buf = [self.indent(nodeinfo)] + buf
+                buf.insert(0, self.indent(nodeinfo))
         return '\n'.join(buf)
 
     def handle_for_loop(self, node):
@@ -559,7 +601,7 @@ class PythonTranslator(Translator):
         write('yield %s' % self.filter('u\'\'.join(filtered())', node.filters))
         return '\n'.join(buf)
 
-    def handle_block(self, node):
+    def handle_block(self, node, level=0):
         """
         Handle blocks in the sourcecode. We only use them to
         call the current block implementation that is stored somewhere
@@ -572,7 +614,11 @@ class PythonTranslator(Translator):
         buf = []
         write = lambda x: buf.append(self.indent(x))
 
-        write('ctx_push()')
+        write('ctx_push({\'super\': BlockContext(%r, blocks[%r], %r, context)})' % (
+            str(node.name),
+            str(node.name),
+            level
+        ))
         nodeinfo = self.nodeinfo(node.body)
         if nodeinfo:
             write(nodeinfo)
diff --git a/tests/runtime/super.py b/tests/runtime/super.py
new file mode 100644 (file)
index 0000000..8954afe
--- /dev/null
@@ -0,0 +1,12 @@
+# test file for block super support
+import jdebug
+from jinja import Environment, DictLoader
+
+env = Environment(loader=DictLoader({
+    'a': '{% block intro %}INTRO{% endblock %}|BEFORE|{% block data %}INNER{% endblock %}|AFTER',
+    'b': '{% extends "a" %}{% block data %}({{ super() }}){% endblock %}',
+    'c': '{% extends "b" %}{% block intro %}--{{ super() }}--{% endblock %}\n{% block data %}[{{ super() }}]{% endblock %}'
+}))
+
+tmpl = env.get_template('c')
+print tmpl.render()