From fa865fb5f2908cde19697bb0905a3bdf0b0347d9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 12 Apr 2008 22:11:53 +0200 Subject: [PATCH] filter tag works now --HG-- branch : trunk --- jinja2/compiler.py | 100 +++++++++++++++++++++++------- jinja2/environment.py | 1 + jinja2/nodes.py | 4 +- jinja2/parser.py | 21 +++++-- test_filter_and_linestatements.py | 22 +++++++ 5 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 test_filter_and_linestatements.py diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 83e07e6..e291f1d 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -113,6 +113,7 @@ class Frame(object): # situations. self.rootlevel = False self.parent = parent + self.buffer = None self.block = parent and parent.block or None if parent is not None: self.identifiers.declared.update( @@ -120,11 +121,12 @@ class Frame(object): parent.identifiers.declared_locally | parent.identifiers.declared_parameter ) + self.buffer = parent.buffer def copy(self): """Create a copy of the current one.""" rv = copy(self) - rv.identifiers = copy(self) + rv.identifiers = copy(self.identifiers) return rv def inspect(self, nodes, hard_scope=False): @@ -229,7 +231,7 @@ class CodeGenerator(NodeVisitor): def blockvisit(self, nodes, frame, force_generator=False): self.indent() - if force_generator: + if force_generator and frame.buffer is None: self.writeline('if 0: yield None') try: for node in nodes: @@ -296,6 +298,16 @@ class CodeGenerator(NodeVisitor): if not no_indent: self.outdent() + def collect_shadowed(self, frame): + # make sure we "backup" overridden, local identifiers + # TODO: we should probably optimize this and check if the + # identifier is in use afterwards. + aliases = {} + for name in frame.identifiers.find_shadowed(): + aliases[name] = ident = self.temporary_identifier() + self.writeline('%s = l_%s' % (ident, name)) + return aliases + def function_scoping(self, node, frame): func_frame = frame.inner() func_frame.inspect(node.iter_child_nodes(), hard_scope=True) @@ -417,7 +429,10 @@ class CodeGenerator(NodeVisitor): level += 1 self.writeline('for event in context.blocks[%r][-1](context):' % node.name) self.indent() - self.writeline('yield event') + if frame.buffer is None: + self.writeline('yield event') + else: + self.writeline('%s.append(event)' % frame.buffer) self.outdent(level) def visit_Extends(self, node, frame): @@ -479,7 +494,10 @@ class CodeGenerator(NodeVisitor): self.writeline('included_context = included_stream.next()') self.writeline('for event in included_stream:') self.indent() - self.writeline('yield event') + if frame.buffer is None: + self.writeline('yield event') + else: + self.writeline('%s.append(event)' % frame.buffer) self.outdent() # if we have a toplevel include the exported variables are copied @@ -493,16 +511,10 @@ class CodeGenerator(NodeVisitor): loop_frame.inspect(node.iter_child_nodes()) extended_loop = bool(node.else_) or \ 'loop' in loop_frame.identifiers.undeclared - loop_frame.identifiers.add_special('loop') - - # make sure we "backup" overridden, local identifiers - # TODO: we should probably optimize this and check if the - # identifier is in use afterwards. - aliases = {} - for name in loop_frame.identifiers.find_shadowed(): - aliases[name] = ident = self.temporary_identifier() - self.writeline('%s = l_%s' % (ident, name)) + if extended_loop: + loop_frame.identifiers.add_special('loop') + aliases = self.collect_shadowed(loop_frame) self.pull_locals(loop_frame, True) self.newline(node) @@ -570,8 +582,33 @@ class CodeGenerator(NodeVisitor): self.visit(arg) self.write(', ') self.write('), %r, False)' % call_frame.accesses_arguments) - self.writeline('yield ', node) + if frame.buffer is None: + self.writeline('yield ', node) + else: + self.writeline('%s.append(' % frame.buffer, node) self.visit_Call(node.call, call_frame, extra_kwargs='caller=caller') + if frame.buffer is not None: + self.write(')') + + def visit_FilterBlock(self, node, frame): + filter_frame = frame.inner() + filter_frame.inspect(node.iter_child_nodes()) + + aliases = self.collect_shadowed(filter_frame) + self.pull_locals(filter_frame, True) + filter_frame.buffer = buf = self.temporary_identifier() + + self.writeline('%s = []' % buf, node) + for child in node.body: + self.visit(child, filter_frame) + + if frame.buffer is None: + self.writeline('yield ', node) + else: + self.writeline('%s.append(' % frame.buffer, node) + self.visit_Filter(node.filter, filter_frame, "u''.join(%s)" % buf) + if frame.buffer is not None: + self.write(')') def visit_ExprStmt(self, node, frame): self.newline(node) @@ -617,12 +654,20 @@ class CodeGenerator(NodeVisitor): if len(body) < 3: for item in body: if isinstance(item, list): - self.writeline('yield %s' % repr(u''.join(item))) + val = repr(u''.join(item)) + if frame.buffer is None: + self.writeline('yield ' + val) + else: + self.writeline('%s.append(%s)' % (frame.buffer, val)) else: self.newline(item) - self.write('yield %s(' % finalizer) + if frame.buffer is None: + self.write('yield ') + else: + self.write('%s.append(' % frame.buffer) + self.write(finalizer + '(') self.visit(item, frame) - self.write(')') + self.write(')' * (1 + frame.buffer is not None)) # otherwise we create a format string as this is faster in that case else: @@ -634,7 +679,11 @@ class CodeGenerator(NodeVisitor): else: format.append('%s') arguments.append(item) - self.writeline('yield %r %% (' % u''.join(format)) + if frame.buffer is None: + self.writeline('yield ') + else: + self.writeline('%s.append(' % frame.buffer) + self.write(repr(u''.join(format)) + ' % (') idx = -1 for idx, argument in enumerate(arguments): if idx: @@ -645,6 +694,8 @@ class CodeGenerator(NodeVisitor): if have_finalizer: self.write(')') self.write(idx == 0 and ',)' or ')') + if frame.buffer is not None: + self.write(')') if outdent_later: self.outdent() @@ -784,12 +835,15 @@ class CodeGenerator(NodeVisitor): self.write(':') self.visit(node.step, frame) - def visit_Filter(self, node, frame): + def visit_Filter(self, node, frame, initial=None): 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) + if initial is not None: + self.write(initial) + else: + 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(')') diff --git a/jinja2/environment.py b/jinja2/environment.py index 8458a23..fb8101b 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -107,6 +107,7 @@ class Environment(object): source = self.parse(source, filename) node = optimize(source, self) source = generate(node, self, filename) + print source if raw: return source if isinstance(filename, unicode): diff --git a/jinja2/nodes.py b/jinja2/nodes.py index b569a31..3b0ac3c 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -232,7 +232,7 @@ class Set(Stmt): class FilterBlock(Stmt): """Node for filter sections.""" - fields = ('body', 'filters') + fields = ('body', 'filter') class Block(Stmt): @@ -390,6 +390,8 @@ class Filter(Expr): fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') def as_const(self): + if self.node is None: + raise Impossible() filter = self.environment.filters.get(self.name) if filter is None or getattr(filter, 'contextfilter', False): raise nodes.Impossible() diff --git a/jinja2/parser.py b/jinja2/parser.py index 81c21ee..2d04211 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -52,6 +52,8 @@ class Parser(object): return getattr(self, 'parse_' + token_type)() elif token_type is 'call': return self.parse_call_block() + elif token_type is 'filter': + return self.parse_filter_block() lineno = self.stream.current expr = self.parse_tuple() if self.stream.current.type == 'assign': @@ -206,6 +208,12 @@ class Parser(object): node.body = self.parse_statements(('endcall',), drop_needle=True) return node + def parse_filter_block(self): + node = nodes.FilterBlock(lineno=self.stream.expect('filter').lineno) + node.filter = self.parse_filter(None, start_inline=True) + node.body = self.parse_statements(('endfilter',), drop_needle=True) + return node + def parse_macro(self): node = nodes.Macro(lineno=self.stream.expect('macro').lineno) node.name = self.stream.expect('name').value @@ -581,10 +589,11 @@ class Parser(object): return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) - def parse_filter(self, node): + def parse_filter(self, node, start_inline=False): lineno = self.stream.current.type - while self.stream.current.type == 'pipe': - self.stream.next() + while self.stream.current.type == 'pipe' or start_inline: + if not start_inline: + self.stream.next() token = self.stream.expect('name') if self.stream.current.type is 'lparen': args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) @@ -594,6 +603,7 @@ class Parser(object): dyn_args = dyn_kwargs = None node = nodes.Filter(node, token.value, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) + start_inline = False return node def parse_test(self, node): @@ -634,12 +644,13 @@ class Parser(object): while self.stream: token = self.stream.current if token.type is 'data': - add_data(nodes.Const(token.value, lineno=token.lineno)) + if token.value: + add_data(nodes.Const(token.value, lineno=token.lineno)) self.stream.next() elif token.type is 'variable_begin': self.stream.next() want_comma = False - while not self.stream.current.type in _statement_end_tokens: + while self.stream.current.type not in _statement_end_tokens: if want_comma: self.stream.expect('comma') add_data(self.parse_expression()) diff --git a/test_filter_and_linestatements.py b/test_filter_and_linestatements.py new file mode 100644 index 0000000..e5d94d0 --- /dev/null +++ b/test_filter_and_linestatements.py @@ -0,0 +1,22 @@ +from jinja2 import Environment + + +env = Environment(line_statement_prefix='%', variable_start_string="${", variable_end_string="}") +tmpl = env.from_string("""\ +% macro foo() + ${caller(42)} +% endmacro + +% call(var) foo() + [${var}] +% endcall +% filter escape + +% endfilter +""") + +print tmpl.render(seq=range(10)) -- 2.26.2