From a69014692ff21fc003e78f0c66a00b428e907647 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 29 Mar 2007 20:03:40 +0200 Subject: [PATCH] [svn] implemented `{{ super() }}` for blocks. This checkin makes jinja much slower. I'll improve that as soon as possible --HG-- branch : trunk --- jdebug.py | 14 +++++++ jinja/datastructure.py | 20 ++++++++++ jinja/nodes.py | 8 ++++ jinja/translators/python.py | 74 ++++++++++++++++++++++++++++++------- tests/runtime/super.py | 12 ++++++ 5 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 tests/runtime/super.py diff --git a/jdebug.py b/jdebug.py index 5de8815..109c5c6 100644 --- 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() diff --git a/jinja/datastructure.py b/jinja/datastructure.py index 3b8448e..e64f687 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -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 diff --git a/jinja/nodes.py b/jinja/nodes.py index b19994e..b9d3051 100644 --- a/jinja/nodes.py +++ b/jinja/nodes.py @@ -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] diff --git a/jinja/translators/python.py b/jinja/translators/python.py index 9608210..e8e9a59 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -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 index 0000000..8954afe --- /dev/null +++ b/tests/runtime/super.py @@ -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() -- 2.26.2