Skip to content

Commit

Permalink
added a function to parse assign targes and documented it for the ext…
Browse files Browse the repository at this point in the history
…ension interface

--HG--
branch : trunk
  • Loading branch information
mitsuhiko committed May 10, 2008
1 parent fb2e38a commit 09c002e
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 71 deletions.
6 changes: 3 additions & 3 deletions docs/extensions.rst
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions docs/switching.rst
Expand Up @@ -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
~~~~~~~~~
Expand Down
23 changes: 8 additions & 15 deletions examples/profile.py
Expand Up @@ -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 = """\
<!doctype html>
<html>
<head>
Expand All @@ -24,15 +20,6 @@
<div class="header">
<h1>${page_title|e}</h1>
</div>
<ul class="navigation">
% for href, caption in [
('index.html', 'Index'),
('downloads.html', 'Downloads'),
('products.html', 'Products')
]
<li><a href="${href|e}">${caption|e}</a></li>
% endfor
</ul>
<div class="table">
<table>
% for row in table
Expand All @@ -46,7 +33,13 @@
</div>
</body>
</html>\
""")
"""
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()
Expand Down
96 changes: 45 additions & 51 deletions jinja2/parser.py
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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):
Expand All @@ -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':
Expand All @@ -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())
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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'):
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_security.py
Expand Up @@ -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)
'''

0 comments on commit 09c002e

Please sign in to comment.