Index: mojo/public/third_party/jinja2/parser.py |
diff --git a/mojo/public/third_party/jinja2/parser.py b/mojo/public/third_party/jinja2/parser.py |
deleted file mode 100644 |
index f60cd018c51fff457dfaea5283b78267a4a0eefd..0000000000000000000000000000000000000000 |
--- a/mojo/public/third_party/jinja2/parser.py |
+++ /dev/null |
@@ -1,895 +0,0 @@ |
-# -*- coding: utf-8 -*- |
-""" |
- jinja2.parser |
- ~~~~~~~~~~~~~ |
- |
- Implements the template parser. |
- |
- :copyright: (c) 2010 by the Jinja Team. |
- :license: BSD, see LICENSE for more details. |
-""" |
-from jinja2 import nodes |
-from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError |
-from jinja2.lexer import describe_token, describe_token_expr |
-from jinja2._compat import next, imap |
- |
- |
-#: statements that callinto |
-_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', |
- 'macro', 'include', 'from', 'import', |
- 'set']) |
-_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) |
- |
- |
-class Parser(object): |
- """This is the central parsing class Jinja2 uses. It's passed to |
- extensions and can be used to parse expressions or statements. |
- """ |
- |
- def __init__(self, environment, source, name=None, filename=None, |
- state=None): |
- self.environment = environment |
- self.stream = environment._tokenize(source, name, filename, state) |
- self.name = name |
- self.filename = filename |
- self.closed = False |
- self.extensions = {} |
- for extension in environment.iter_extensions(): |
- for tag in extension.tags: |
- self.extensions[tag] = extension.parse |
- self._last_identifier = 0 |
- self._tag_stack = [] |
- self._end_token_stack = [] |
- |
- def fail(self, msg, lineno=None, exc=TemplateSyntaxError): |
- """Convenience method that raises `exc` with the message, passed |
- line number or last line number as well as the current name and |
- filename. |
- """ |
- if lineno is None: |
- lineno = self.stream.current.lineno |
- raise exc(msg, lineno, self.name, self.filename) |
- |
- def _fail_ut_eof(self, name, end_token_stack, lineno): |
- expected = [] |
- for exprs in end_token_stack: |
- expected.extend(imap(describe_token_expr, exprs)) |
- if end_token_stack: |
- currently_looking = ' or '.join( |
- "'%s'" % describe_token_expr(expr) |
- for expr in end_token_stack[-1]) |
- else: |
- currently_looking = None |
- |
- if name is None: |
- message = ['Unexpected end of template.'] |
- else: |
- message = ['Encountered unknown tag \'%s\'.' % name] |
- |
- if currently_looking: |
- if name is not None and name in expected: |
- message.append('You probably made a nesting mistake. Jinja ' |
- 'is expecting this tag, but currently looking ' |
- 'for %s.' % currently_looking) |
- else: |
- message.append('Jinja was looking for the following tags: ' |
- '%s.' % currently_looking) |
- |
- if self._tag_stack: |
- message.append('The innermost block that needs to be ' |
- 'closed is \'%s\'.' % self._tag_stack[-1]) |
- |
- self.fail(' '.join(message), lineno) |
- |
- def fail_unknown_tag(self, name, lineno=None): |
- """Called if the parser encounters an unknown tag. Tries to fail |
- with a human readable error message that could help to identify |
- the problem. |
- """ |
- return self._fail_ut_eof(name, self._end_token_stack, lineno) |
- |
- def fail_eof(self, end_tokens=None, lineno=None): |
- """Like fail_unknown_tag but for end of template situations.""" |
- stack = list(self._end_token_stack) |
- if end_tokens is not None: |
- stack.append(end_tokens) |
- return self._fail_ut_eof(None, stack, lineno) |
- |
- def is_tuple_end(self, extra_end_rules=None): |
- """Are we at the end of a tuple?""" |
- if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): |
- return True |
- elif extra_end_rules is not None: |
- return self.stream.current.test_any(extra_end_rules) |
- return False |
- |
- def free_identifier(self, lineno=None): |
- """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" |
- self._last_identifier += 1 |
- rv = object.__new__(nodes.InternalName) |
- nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) |
- return rv |
- |
- def parse_statement(self): |
- """Parse a single statement.""" |
- token = self.stream.current |
- if token.type != 'name': |
- self.fail('tag name expected', token.lineno) |
- self._tag_stack.append(token.value) |
- pop_tag = True |
- try: |
- if token.value in _statement_keywords: |
- return getattr(self, 'parse_' + self.stream.current.value)() |
- if token.value == 'call': |
- return self.parse_call_block() |
- if token.value == 'filter': |
- return self.parse_filter_block() |
- ext = self.extensions.get(token.value) |
- if ext is not None: |
- return ext(self) |
- |
- # did not work out, remove the token we pushed by accident |
- # from the stack so that the unknown tag fail function can |
- # produce a proper error message. |
- self._tag_stack.pop() |
- pop_tag = False |
- self.fail_unknown_tag(token.value, token.lineno) |
- finally: |
- if pop_tag: |
- self._tag_stack.pop() |
- |
- def parse_statements(self, end_tokens, drop_needle=False): |
- """Parse multiple statements into a list until one of the end tokens |
- is reached. This is used to parse the body of statements as it also |
- parses template data if appropriate. The parser checks first if the |
- current token is a colon and skips it if there is one. Then it checks |
- for the block end and parses until if one of the `end_tokens` is |
- reached. Per default the active token in the stream at the end of |
- the call is the matched end token. If this is not wanted `drop_needle` |
- can be set to `True` and the end token is removed. |
- """ |
- # the first token may be a colon for python compatibility |
- self.stream.skip_if('colon') |
- |
- # in the future it would be possible to add whole code sections |
- # by adding some sort of end of statement token and parsing those here. |
- self.stream.expect('block_end') |
- result = self.subparse(end_tokens) |
- |
- # we reached the end of the template too early, the subparser |
- # does not check for this, so we do that now |
- if self.stream.current.type == 'eof': |
- self.fail_eof(end_tokens) |
- |
- if drop_needle: |
- next(self.stream) |
- return result |
- |
- def parse_set(self): |
- """Parse an assign statement.""" |
- lineno = next(self.stream).lineno |
- target = self.parse_assign_target() |
- self.stream.expect('assign') |
- expr = self.parse_tuple() |
- return nodes.Assign(target, expr, lineno=lineno) |
- |
- def parse_for(self): |
- """Parse a for loop.""" |
- lineno = self.stream.expect('name:for').lineno |
- target = self.parse_assign_target(extra_end_rules=('name:in',)) |
- self.stream.expect('name:in') |
- iter = self.parse_tuple(with_condexpr=False, |
- extra_end_rules=('name:recursive',)) |
- test = None |
- if self.stream.skip_if('name:if'): |
- test = self.parse_expression() |
- recursive = self.stream.skip_if('name:recursive') |
- body = self.parse_statements(('name:endfor', 'name:else')) |
- if next(self.stream).value == 'endfor': |
- else_ = [] |
- else: |
- else_ = self.parse_statements(('name:endfor',), drop_needle=True) |
- return nodes.For(target, iter, body, else_, test, |
- recursive, lineno=lineno) |
- |
- 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(with_condexpr=False) |
- node.body = self.parse_statements(('name:elif', 'name:else', |
- 'name:endif')) |
- token = next(self.stream) |
- if token.test('name:elif'): |
- new_node = nodes.If(lineno=self.stream.current.lineno) |
- node.else_ = [new_node] |
- node = new_node |
- continue |
- elif token.test('name:else'): |
- node.else_ = self.parse_statements(('name:endif',), |
- drop_needle=True) |
- else: |
- node.else_ = [] |
- break |
- return result |
- |
- def parse_block(self): |
- node = nodes.Block(lineno=next(self.stream).lineno) |
- node.name = self.stream.expect('name').value |
- node.scoped = self.stream.skip_if('name:scoped') |
- |
- # common problem people encounter when switching from django |
- # to jinja. we do not support hyphens in block names, so let's |
- # raise a nicer error message in that case. |
- if self.stream.current.type == 'sub': |
- self.fail('Block names in Jinja have to be valid Python ' |
- 'identifiers and may not contain hyphens, use an ' |
- 'underscore instead.') |
- |
- node.body = self.parse_statements(('name:endblock',), drop_needle=True) |
- self.stream.skip_if('name:' + node.name) |
- return node |
- |
- def parse_extends(self): |
- node = nodes.Extends(lineno=next(self.stream).lineno) |
- node.template = self.parse_expression() |
- return node |
- |
- def parse_import_context(self, node, default): |
- if self.stream.current.test_any('name:with', 'name:without') and \ |
- self.stream.look().test('name:context'): |
- node.with_context = next(self.stream).value == 'with' |
- self.stream.skip() |
- else: |
- node.with_context = default |
- return node |
- |
- def parse_include(self): |
- node = nodes.Include(lineno=next(self.stream).lineno) |
- node.template = self.parse_expression() |
- if self.stream.current.test('name:ignore') and \ |
- self.stream.look().test('name:missing'): |
- node.ignore_missing = True |
- self.stream.skip(2) |
- else: |
- node.ignore_missing = False |
- return self.parse_import_context(node, True) |
- |
- def parse_import(self): |
- node = nodes.Import(lineno=next(self.stream).lineno) |
- node.template = self.parse_expression() |
- self.stream.expect('name:as') |
- node.target = self.parse_assign_target(name_only=True).name |
- return self.parse_import_context(node, False) |
- |
- def parse_from(self): |
- node = nodes.FromImport(lineno=next(self.stream).lineno) |
- node.template = self.parse_expression() |
- self.stream.expect('name:import') |
- node.names = [] |
- |
- def parse_context(): |
- if self.stream.current.value in ('with', 'without') and \ |
- self.stream.look().test('name:context'): |
- node.with_context = next(self.stream).value == 'with' |
- self.stream.skip() |
- return True |
- return False |
- |
- while 1: |
- if node.names: |
- self.stream.expect('comma') |
- if self.stream.current.type == 'name': |
- if parse_context(): |
- break |
- target = self.parse_assign_target(name_only=True) |
- if target.name.startswith('_'): |
- self.fail('names starting with an underline can not ' |
- 'be imported', target.lineno, |
- exc=TemplateAssertionError) |
- if self.stream.skip_if('name:as'): |
- 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 != 'comma': |
- break |
- else: |
- break |
- if not hasattr(node, 'with_context'): |
- node.with_context = False |
- self.stream.skip_if('comma') |
- return node |
- |
- def parse_signature(self, node): |
- node.args = args = [] |
- node.defaults = defaults = [] |
- self.stream.expect('lparen') |
- while self.stream.current.type != 'rparen': |
- if args: |
- self.stream.expect('comma') |
- arg = self.parse_assign_target(name_only=True) |
- arg.set_ctx('param') |
- if self.stream.skip_if('assign'): |
- defaults.append(self.parse_expression()) |
- args.append(arg) |
- self.stream.expect('rparen') |
- |
- def parse_call_block(self): |
- node = nodes.CallBlock(lineno=next(self.stream).lineno) |
- if self.stream.current.type == 'lparen': |
- self.parse_signature(node) |
- else: |
- node.args = [] |
- node.defaults = [] |
- |
- node.call = self.parse_expression() |
- if not isinstance(node.call, nodes.Call): |
- self.fail('expected call', node.lineno) |
- node.body = self.parse_statements(('name:endcall',), drop_needle=True) |
- return node |
- |
- def parse_filter_block(self): |
- node = nodes.FilterBlock(lineno=next(self.stream).lineno) |
- node.filter = self.parse_filter(None, start_inline=True) |
- node.body = self.parse_statements(('name:endfilter',), |
- drop_needle=True) |
- return node |
- |
- def parse_macro(self): |
- node = nodes.Macro(lineno=next(self.stream).lineno) |
- node.name = self.parse_assign_target(name_only=True).name |
- self.parse_signature(node) |
- node.body = self.parse_statements(('name:endmacro',), |
- drop_needle=True) |
- return node |
- |
- def parse_print(self): |
- node = nodes.Output(lineno=next(self.stream).lineno) |
- node.nodes = [] |
- while self.stream.current.type != 'block_end': |
- if node.nodes: |
- self.stream.expect('comma') |
- node.nodes.append(self.parse_expression()) |
- return node |
- |
- def parse_assign_target(self, with_tuple=True, name_only=False, |
- extra_end_rules=None): |
- """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`. The `extra_end_rules` |
- parameter is forwarded to the tuple parsing function. |
- """ |
- 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, |
- extra_end_rules=extra_end_rules) |
- else: |
- target = self.parse_primary() |
- target.set_ctx('store') |
- if not target.can_assign(): |
- self.fail('can\'t assign to %r' % target.__class__. |
- __name__.lower(), target.lineno) |
- return target |
- |
- def parse_expression(self, with_condexpr=True): |
- """Parse an expression. Per default all expressions are parsed, if |
- the optional `with_condexpr` parameter is set to `False` conditional |
- expressions are not parsed. |
- """ |
- if with_condexpr: |
- return self.parse_condexpr() |
- return self.parse_or() |
- |
- def parse_condexpr(self): |
- lineno = self.stream.current.lineno |
- expr1 = self.parse_or() |
- while self.stream.skip_if('name:if'): |
- expr2 = self.parse_or() |
- if self.stream.skip_if('name:else'): |
- expr3 = self.parse_condexpr() |
- else: |
- expr3 = None |
- expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return expr1 |
- |
- def parse_or(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_and() |
- while self.stream.skip_if('name:or'): |
- right = self.parse_and() |
- left = nodes.Or(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_and(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_not() |
- while self.stream.skip_if('name:and'): |
- right = self.parse_not() |
- left = nodes.And(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_not(self): |
- if self.stream.current.test('name:not'): |
- lineno = next(self.stream).lineno |
- return nodes.Not(self.parse_not(), lineno=lineno) |
- return self.parse_compare() |
- |
- def parse_compare(self): |
- lineno = self.stream.current.lineno |
- expr = self.parse_add() |
- ops = [] |
- while 1: |
- token_type = self.stream.current.type |
- if token_type in _compare_operators: |
- next(self.stream) |
- ops.append(nodes.Operand(token_type, self.parse_add())) |
- elif self.stream.skip_if('name:in'): |
- ops.append(nodes.Operand('in', self.parse_add())) |
- elif self.stream.current.test('name:not') and \ |
- self.stream.look().test('name:in'): |
- self.stream.skip(2) |
- ops.append(nodes.Operand('notin', self.parse_add())) |
- else: |
- break |
- lineno = self.stream.current.lineno |
- if not ops: |
- return expr |
- return nodes.Compare(expr, ops, lineno=lineno) |
- |
- def parse_add(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_sub() |
- while self.stream.current.type == 'add': |
- next(self.stream) |
- right = self.parse_sub() |
- left = nodes.Add(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_sub(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_concat() |
- while self.stream.current.type == 'sub': |
- next(self.stream) |
- right = self.parse_concat() |
- left = nodes.Sub(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_concat(self): |
- lineno = self.stream.current.lineno |
- args = [self.parse_mul()] |
- while self.stream.current.type == 'tilde': |
- next(self.stream) |
- args.append(self.parse_mul()) |
- if len(args) == 1: |
- return args[0] |
- return nodes.Concat(args, lineno=lineno) |
- |
- def parse_mul(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_div() |
- while self.stream.current.type == 'mul': |
- next(self.stream) |
- right = self.parse_div() |
- left = nodes.Mul(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_div(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_floordiv() |
- while self.stream.current.type == 'div': |
- next(self.stream) |
- right = self.parse_floordiv() |
- left = nodes.Div(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_floordiv(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_mod() |
- while self.stream.current.type == 'floordiv': |
- next(self.stream) |
- right = self.parse_mod() |
- left = nodes.FloorDiv(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_mod(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_pow() |
- while self.stream.current.type == 'mod': |
- next(self.stream) |
- right = self.parse_pow() |
- left = nodes.Mod(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_pow(self): |
- lineno = self.stream.current.lineno |
- left = self.parse_unary() |
- while self.stream.current.type == 'pow': |
- next(self.stream) |
- right = self.parse_unary() |
- left = nodes.Pow(left, right, lineno=lineno) |
- lineno = self.stream.current.lineno |
- return left |
- |
- def parse_unary(self, with_filter=True): |
- token_type = self.stream.current.type |
- lineno = self.stream.current.lineno |
- if token_type == 'sub': |
- next(self.stream) |
- node = nodes.Neg(self.parse_unary(False), lineno=lineno) |
- elif token_type == 'add': |
- next(self.stream) |
- node = nodes.Pos(self.parse_unary(False), lineno=lineno) |
- else: |
- node = self.parse_primary() |
- node = self.parse_postfix(node) |
- if with_filter: |
- node = self.parse_filter_expr(node) |
- return node |
- |
- def parse_primary(self): |
- token = self.stream.current |
- if token.type == 'name': |
- if token.value in ('true', 'false', 'True', 'False'): |
- node = nodes.Const(token.value in ('true', 'True'), |
- lineno=token.lineno) |
- elif token.value in ('none', 'None'): |
- node = nodes.Const(None, lineno=token.lineno) |
- else: |
- node = nodes.Name(token.value, 'load', lineno=token.lineno) |
- next(self.stream) |
- elif token.type == 'string': |
- next(self.stream) |
- buf = [token.value] |
- lineno = token.lineno |
- while self.stream.current.type == 'string': |
- buf.append(self.stream.current.value) |
- next(self.stream) |
- node = nodes.Const(''.join(buf), lineno=lineno) |
- elif token.type in ('integer', 'float'): |
- next(self.stream) |
- node = nodes.Const(token.value, lineno=token.lineno) |
- elif token.type == 'lparen': |
- next(self.stream) |
- node = self.parse_tuple(explicit_parentheses=True) |
- self.stream.expect('rparen') |
- elif token.type == 'lbracket': |
- node = self.parse_list() |
- elif token.type == 'lbrace': |
- node = self.parse_dict() |
- else: |
- self.fail("unexpected '%s'" % describe_token(token), token.lineno) |
- return node |
- |
- def parse_tuple(self, simplified=False, with_condexpr=True, |
- extra_end_rules=None, explicit_parentheses=False): |
- """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 |
- if no commas where found. |
- |
- The default parsing mode is a full tuple. If `simplified` is `True` |
- only names and literals are parsed. The `no_condexpr` parameter is |
- forwarded to :meth:`parse_expression`. |
- |
- Because tuples do not require delimiters and may end in a bogus comma |
- an extra hint is needed that marks the end of a tuple. For example |
- for loops support tuples between `for` and `in`. In that case the |
- `extra_end_rules` is set to ``['name:in']``. |
- |
- `explicit_parentheses` is true if the parsing was triggered by an |
- expression in parentheses. This is used to figure out if an empty |
- tuple is a valid expression or not. |
- """ |
- lineno = self.stream.current.lineno |
- if simplified: |
- parse = self.parse_primary |
- elif with_condexpr: |
- parse = self.parse_expression |
- else: |
- parse = lambda: self.parse_expression(with_condexpr=False) |
- args = [] |
- is_tuple = False |
- while 1: |
- if args: |
- self.stream.expect('comma') |
- if self.is_tuple_end(extra_end_rules): |
- break |
- args.append(parse()) |
- if self.stream.current.type == 'comma': |
- is_tuple = True |
- else: |
- break |
- lineno = self.stream.current.lineno |
- |
- if not is_tuple: |
- if args: |
- return args[0] |
- |
- # if we don't have explicit parentheses, an empty tuple is |
- # not a valid expression. This would mean nothing (literally |
- # nothing) in the spot of an expression would be an empty |
- # tuple. |
- if not explicit_parentheses: |
- self.fail('Expected an expression, got \'%s\'' % |
- describe_token(self.stream.current)) |
- |
- return nodes.Tuple(args, 'load', lineno=lineno) |
- |
- def parse_list(self): |
- token = self.stream.expect('lbracket') |
- items = [] |
- while self.stream.current.type != 'rbracket': |
- if items: |
- self.stream.expect('comma') |
- if self.stream.current.type == 'rbracket': |
- break |
- items.append(self.parse_expression()) |
- self.stream.expect('rbracket') |
- return nodes.List(items, lineno=token.lineno) |
- |
- def parse_dict(self): |
- token = self.stream.expect('lbrace') |
- items = [] |
- while self.stream.current.type != 'rbrace': |
- if items: |
- self.stream.expect('comma') |
- if self.stream.current.type == 'rbrace': |
- break |
- key = self.parse_expression() |
- self.stream.expect('colon') |
- value = self.parse_expression() |
- items.append(nodes.Pair(key, value, lineno=key.lineno)) |
- self.stream.expect('rbrace') |
- return nodes.Dict(items, lineno=token.lineno) |
- |
- def parse_postfix(self, node): |
- while 1: |
- token_type = self.stream.current.type |
- if token_type == 'dot' or token_type == 'lbracket': |
- node = self.parse_subscript(node) |
- # calls are valid both after postfix expressions (getattr |
- # and getitem) as well as filters and tests |
- elif token_type == 'lparen': |
- node = self.parse_call(node) |
- else: |
- break |
- return node |
- |
- def parse_filter_expr(self, node): |
- while 1: |
- token_type = self.stream.current.type |
- if token_type == 'pipe': |
- node = self.parse_filter(node) |
- elif token_type == 'name' and self.stream.current.value == 'is': |
- node = self.parse_test(node) |
- # calls are valid both after postfix expressions (getattr |
- # and getitem) as well as filters and tests |
- elif token_type == 'lparen': |
- node = self.parse_call(node) |
- else: |
- break |
- return node |
- |
- def parse_subscript(self, node): |
- token = next(self.stream) |
- if token.type == 'dot': |
- attr_token = self.stream.current |
- next(self.stream) |
- if attr_token.type == 'name': |
- return nodes.Getattr(node, attr_token.value, 'load', |
- lineno=token.lineno) |
- elif attr_token.type != 'integer': |
- self.fail('expected name or number', attr_token.lineno) |
- arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) |
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno) |
- if token.type == 'lbracket': |
- args = [] |
- while self.stream.current.type != 'rbracket': |
- if args: |
- self.stream.expect('comma') |
- args.append(self.parse_subscribed()) |
- self.stream.expect('rbracket') |
- if len(args) == 1: |
- arg = args[0] |
- else: |
- arg = nodes.Tuple(args, 'load', lineno=token.lineno) |
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno) |
- self.fail('expected subscript expression', self.lineno) |
- |
- def parse_subscribed(self): |
- lineno = self.stream.current.lineno |
- |
- if self.stream.current.type == 'colon': |
- next(self.stream) |
- args = [None] |
- else: |
- node = self.parse_expression() |
- if self.stream.current.type != 'colon': |
- return node |
- next(self.stream) |
- args = [node] |
- |
- if self.stream.current.type == 'colon': |
- args.append(None) |
- elif self.stream.current.type not in ('rbracket', 'comma'): |
- args.append(self.parse_expression()) |
- else: |
- args.append(None) |
- |
- if self.stream.current.type == 'colon': |
- next(self.stream) |
- if self.stream.current.type not in ('rbracket', 'comma'): |
- args.append(self.parse_expression()) |
- else: |
- args.append(None) |
- else: |
- args.append(None) |
- |
- return nodes.Slice(lineno=lineno, *args) |
- |
- def parse_call(self, node): |
- token = self.stream.expect('lparen') |
- args = [] |
- kwargs = [] |
- dyn_args = dyn_kwargs = None |
- require_comma = False |
- |
- def ensure(expr): |
- if not expr: |
- self.fail('invalid syntax for function call expression', |
- token.lineno) |
- |
- while self.stream.current.type != 'rparen': |
- if require_comma: |
- self.stream.expect('comma') |
- # support for trailing comma |
- if self.stream.current.type == 'rparen': |
- break |
- if self.stream.current.type == 'mul': |
- ensure(dyn_args is None and dyn_kwargs is None) |
- next(self.stream) |
- dyn_args = self.parse_expression() |
- elif self.stream.current.type == 'pow': |
- ensure(dyn_kwargs is None) |
- next(self.stream) |
- dyn_kwargs = self.parse_expression() |
- else: |
- ensure(dyn_args is None and dyn_kwargs is None) |
- if self.stream.current.type == 'name' and \ |
- self.stream.look().type == 'assign': |
- key = self.stream.current.value |
- self.stream.skip(2) |
- value = self.parse_expression() |
- kwargs.append(nodes.Keyword(key, value, |
- lineno=value.lineno)) |
- else: |
- ensure(not kwargs) |
- args.append(self.parse_expression()) |
- |
- require_comma = True |
- self.stream.expect('rparen') |
- |
- if node is None: |
- return args, kwargs, dyn_args, dyn_kwargs |
- return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, |
- lineno=token.lineno) |
- |
- def parse_filter(self, node, start_inline=False): |
- while self.stream.current.type == 'pipe' or start_inline: |
- if not start_inline: |
- next(self.stream) |
- token = self.stream.expect('name') |
- name = token.value |
- while self.stream.current.type == 'dot': |
- next(self.stream) |
- name += '.' + self.stream.expect('name').value |
- if self.stream.current.type == 'lparen': |
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) |
- else: |
- args = [] |
- kwargs = [] |
- dyn_args = dyn_kwargs = None |
- node = nodes.Filter(node, name, args, kwargs, dyn_args, |
- dyn_kwargs, lineno=token.lineno) |
- start_inline = False |
- return node |
- |
- def parse_test(self, node): |
- token = next(self.stream) |
- if self.stream.current.test('name:not'): |
- next(self.stream) |
- negated = True |
- else: |
- negated = False |
- name = self.stream.expect('name').value |
- while self.stream.current.type == 'dot': |
- next(self.stream) |
- name += '.' + self.stream.expect('name').value |
- dyn_args = dyn_kwargs = None |
- kwargs = [] |
- if self.stream.current.type == 'lparen': |
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) |
- elif self.stream.current.type in ('name', 'string', 'integer', |
- 'float', 'lparen', 'lbracket', |
- 'lbrace') and not \ |
- self.stream.current.test_any('name:else', 'name:or', |
- 'name:and'): |
- if self.stream.current.test('name:is'): |
- self.fail('You cannot chain multiple tests with is') |
- args = [self.parse_expression()] |
- else: |
- args = [] |
- node = nodes.Test(node, name, args, kwargs, dyn_args, |
- dyn_kwargs, lineno=token.lineno) |
- if negated: |
- node = nodes.Not(node, lineno=token.lineno) |
- return node |
- |
- def subparse(self, end_tokens=None): |
- body = [] |
- data_buffer = [] |
- add_data = data_buffer.append |
- |
- if end_tokens is not None: |
- self._end_token_stack.append(end_tokens) |
- |
- def flush_data(): |
- if data_buffer: |
- lineno = data_buffer[0].lineno |
- body.append(nodes.Output(data_buffer[:], lineno=lineno)) |
- del data_buffer[:] |
- |
- try: |
- while self.stream: |
- token = self.stream.current |
- if token.type == 'data': |
- if token.value: |
- add_data(nodes.TemplateData(token.value, |
- lineno=token.lineno)) |
- next(self.stream) |
- elif token.type == 'variable_begin': |
- next(self.stream) |
- add_data(self.parse_tuple(with_condexpr=True)) |
- self.stream.expect('variable_end') |
- elif token.type == 'block_begin': |
- flush_data() |
- next(self.stream) |
- if end_tokens is not None and \ |
- self.stream.current.test_any(*end_tokens): |
- return body |
- rv = self.parse_statement() |
- if isinstance(rv, list): |
- body.extend(rv) |
- else: |
- body.append(rv) |
- self.stream.expect('block_end') |
- else: |
- raise AssertionError('internal parsing error') |
- |
- flush_data() |
- finally: |
- if end_tokens is not None: |
- self._end_token_stack.pop() |
- |
- return body |
- |
- def parse(self): |
- """Parse the whole template into a `Template` node.""" |
- result = nodes.Template(self.subparse(), lineno=1) |
- result.set_environment(self.environment) |
- return result |