From 09c002e6faaae853563bc1641cb829d91d17a700 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 10 May 2008 22:21:30 +0200 Subject: [PATCH] added a function to parse assign targes and documented it for the extension interface --HG-- branch : trunk --- docs/extensions.rst | 6 +-- docs/switching.rst | 4 ++ examples/profile.py | 23 ++++------ jinja2/parser.py | 96 ++++++++++++++++++++---------------------- tests/test_security.py | 4 +- 5 files changed, 62 insertions(+), 71 deletions(-) diff --git a/docs/extensions.rst b/docs/extensions.rst index 7be9f128d..8bd80f5ba 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -17,7 +17,7 @@ extension pass a list of extension classes or import paths to the `environment` parameter of the :class:`Environment` constructor. The following example creates a Jinja2 environment with the i18n extension loaded:: - jinja_env = Environment(extensions=['jinja.ext.i18n']) + jinja_env = Environment(extensions=['jinja2.ext.i18n']) .. _i18n-extension: @@ -147,8 +147,8 @@ expressions of different types. The following methods may be used by extensions: .. autoclass:: jinja2.parser.Parser - :members: parse_expression, parse_tuple, parse_statements, skip_colon, - skip_comma, free_identifier + :members: parse_expression, parse_tuple, parse_assign_target, + parse_statements, skip_colon, skip_comma, free_identifier .. attribute:: filename diff --git a/docs/switching.rst b/docs/switching.rst index 0ca2990c9..f88acbe92 100644 --- a/docs/switching.rst +++ b/docs/switching.rst @@ -62,6 +62,10 @@ Context modifications nor is it a singleton. As inheritance is dynamic now multiple context objects may exist during template evaluation. +Filters and Tests + Filters and tests are regular functions now. It's no longer necessary + and allowed to use factory functions. + Templates ~~~~~~~~~ diff --git a/examples/profile.py b/examples/profile.py index 843efb166..a154404c2 100644 --- a/examples/profile.py +++ b/examples/profile.py @@ -10,11 +10,7 @@ 'table': [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10) for x in range(1000)] } -jinja_template = JinjaEnvironment( - line_statement_prefix='%', - variable_start_string="${", - variable_end_string="}" -).from_string("""\ +source = """\ @@ -24,15 +20,6 @@

${page_title|e}

-
% for row in table @@ -46,7 +33,13 @@ \ -""") +""" +jinja_template = JinjaEnvironment( + line_statement_prefix='%', + variable_start_string="${", + variable_end_string="}" +).from_string(source) +print jinja_template.environment.compile(source, raw=True) p = Profile() diff --git a/jinja2/parser.py b/jinja2/parser.py index 427cb058d..dae1a6b69 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -121,14 +121,9 @@ def parse_statements(self, end_tokens, drop_needle=False): def parse_for(self): """Parse a for loop.""" lineno = self.stream.expect('name:for').lineno - target = self.parse_tuple(simplified=True) - if not target.can_assign(): - raise TemplateSyntaxError("can't assign to '%s'" % - target.__class__.__name__.lower(), - target.lineno, self.filename) - target.set_ctx('store') + target = self.parse_assign_target() self.stream.expect('name:in') - iter = self.parse_tuple(no_condexpr=True) + iter = self.parse_tuple(with_condexpr=False) test = None if self.stream.current.test('name:if'): self.stream.next() @@ -144,7 +139,7 @@ def parse_if(self): """Parse an if construct.""" node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) while 1: - node.test = self.parse_tuple(no_condexpr=True) + node.test = self.parse_tuple(with_condexpr=False) node.body = self.parse_statements(('name:elif', 'name:else', 'name:endif')) token = self.stream.next() @@ -190,11 +185,7 @@ def parse_import(self): node = nodes.Import(lineno=self.stream.next().lineno) node.template = self.parse_expression() self.stream.expect('name:as') - node.target = self.stream.expect('name').value - if not nodes.Name(node.target, 'store').can_assign(): - raise TemplateSyntaxError('can\'t assign imported template ' - 'to %r' % node.target, node.lineno, - self.filename) + node.target = self.parse_assign_target(name_only=True).name return self.parse_import_context(node, False) def parse_from(self): @@ -217,25 +208,16 @@ def parse_context(): if self.stream.current.type is 'name': if parse_context(): break - target = nodes.Name(self.stream.current.value, 'store') - if not target.can_assign(): - raise TemplateSyntaxError('can\'t import object named %r' - % target.name, target.lineno, - self.filename) - elif target.name.startswith('__'): + target = self.parse_assign_target(name_only=True) + if target.name.startswith('__'): raise TemplateAssertionError('names starting with two ' 'underscores can not be ' 'imported', target.lineno, self.filename) - self.stream.next() if self.stream.current.test('name:as'): self.stream.next() - alias = self.stream.expect('name') - if not nodes.Name(alias.value, 'store').can_assign(): - raise TemplateSyntaxError('can\'t name imported ' - 'object %r.' % alias.value, - alias.lineno, self.filename) - node.names.append((target.name, alias.value)) + alias = self.parse_assign_target(name_only=True) + node.names.append((target.name, alias.name)) else: node.names.append(target.name) if parse_context() or self.stream.current.type is not 'comma': @@ -255,12 +237,7 @@ def parse_signature(self, node): while self.stream.current.type is not 'rparen': if args: self.stream.expect('comma') - token = self.stream.expect('name') - arg = nodes.Name(token.value, 'param', lineno=token.lineno) - if not arg.can_assign(): - raise TemplateSyntaxError("can't assign to '%s'" % - arg.name, arg.lineno, - self.filename) + arg = self.parse_assign_target(name_only=True) if self.stream.current.type is 'assign': self.stream.next() defaults.append(self.parse_expression()) @@ -291,12 +268,7 @@ def parse_filter_block(self): def parse_macro(self): node = nodes.Macro(lineno=self.stream.next().lineno) - node.name = self.stream.expect('name').value - # make sure that assignments to that name are allowed - if not nodes.Name(node.name, 'store').can_assign(): - raise TemplateSyntaxError('can\'t assign macro to %r' % - node.target, node.lineno, - self.filename) + node.name = self.parse_assign_target(name_only=True).name self.parse_signature(node) node.body = self.parse_statements(('name:endmacro',), drop_needle=True) @@ -311,14 +283,36 @@ def parse_print(self): node.nodes.append(self.parse_expression()) return node - def parse_expression(self, no_condexpr=False): + def parse_assign_target(self, with_tuple=True, name_only=False): + """Parse an assignment target. As Jinja2 allows assignments to + tuples, this function can parse all allowed assignment targets. Per + default assignments to tuples are parsed, that can be disable however + by setting `with_tuple` to `False`. If only assignments to names are + wanted `name_only` can be set to `True`. + """ + if name_only: + token = self.stream.expect('name') + target = nodes.Name(token.value, 'store', lineno=token.lineno) + else: + if with_tuple: + target = self.parse_tuple(simplified=True) + else: + target = self.parse_primary(with_postfix=False) + target.set_ctx('store') + if not target.can_assign(): + raise TemplateSyntaxError('can\'t assign to %r' % + target.__class__.__name__.lower(), + target.lineno, self.filename) + return target + + def parse_expression(self, with_condexpr=True): """Parse an expression. Per default all expressions are parsed, if - the optional `no_condexpr` parameter is set to `True` conditional + the optional `with_condexpr` parameter is set to `False` conditional expressions are not parsed. """ - if no_condexpr: - return self.parse_or() - return self.parse_condexpr() + if with_condexpr: + return self.parse_condexpr() + return self.parse_or() def parse_condexpr(self): lineno = self.stream.current.lineno @@ -472,7 +466,7 @@ def parse_unary(self): return nodes.Pos(node, lineno=lineno) return self.parse_primary() - def parse_primary(self, parse_postfix=True): + def parse_primary(self, with_postfix=True): token = self.stream.current if token.type is 'name': if token.value in ('true', 'false'): @@ -497,11 +491,11 @@ def parse_primary(self, parse_postfix=True): raise TemplateSyntaxError("unexpected token '%s'" % (token,), token.lineno, self.filename) - if parse_postfix: + if with_postfix: node = self.parse_postfix(node) return node - def parse_tuple(self, simplified=False, no_condexpr=False): + def parse_tuple(self, simplified=False, with_condexpr=True): """Works like `parse_expression` but if multiple expressions are delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. This method could also return a regular expression instead of a tuple @@ -513,11 +507,11 @@ def parse_tuple(self, simplified=False, no_condexpr=False): """ lineno = self.stream.current.lineno if simplified: - parse = self.parse_primary - elif no_condexpr: - parse = lambda: self.parse_expression(no_condexpr=True) - else: + parse = lambda: self.parse_primary(with_postfix=False) + elif with_condexpr: parse = self.parse_expression + else: + parse = lambda: self.parse_expression(with_condexpr=False) args = [] is_tuple = False while 1: @@ -741,7 +735,7 @@ def flush_data(): self.stream.next() elif token.type is 'variable_begin': self.stream.next() - add_data(self.parse_tuple()) + add_data(self.parse_tuple(with_condexpr=True)) self.stream.expect('variable_end') elif token.type is 'block_begin': flush_data() diff --git a/tests/test_security.py b/tests/test_security.py index 803c5e7f4..6813656fd 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -62,9 +62,9 @@ def __repr__(self): >>> env.from_string("{% for item.attribute in seq %}...{% endfor %}") Traceback (most recent call last): ... -TemplateSyntaxError: can't assign to 'subscript' (line 1) +TemplateSyntaxError: expected token 'in', got '.' (line 1) >>> env.from_string("{% for foo, bar.baz in seq %}...{% endfor %}") Traceback (most recent call last): ... -TemplateSyntaxError: can't assign to 'tuple' (line 1) +TemplateSyntaxError: expected token 'in', got '.' (line 1) '''