diff options
author | floatdrop <floatdrop@yandex-team.ru> | 2022-02-10 16:47:15 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:47:15 +0300 |
commit | e63b84f1d39557d9e46ac380b1f388271894293c (patch) | |
tree | 338cdaff3fb027e030b847db66df06019a0e3149 /contrib/python/Jinja2/py3/jinja2/parser.py | |
parent | f60febb7ea449535e7b073c386c7ff0539637fc0 (diff) | |
download | ydb-e63b84f1d39557d9e46ac380b1f388271894293c.tar.gz |
Restoring authorship annotation for <floatdrop@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/Jinja2/py3/jinja2/parser.py')
-rw-r--r-- | contrib/python/Jinja2/py3/jinja2/parser.py | 1098 |
1 files changed, 549 insertions, 549 deletions
diff --git a/contrib/python/Jinja2/py3/jinja2/parser.py b/contrib/python/Jinja2/py3/jinja2/parser.py index 7ad73fcc0f0..0b8f9ca61af 100644 --- a/contrib/python/Jinja2/py3/jinja2/parser.py +++ b/contrib/python/Jinja2/py3/jinja2/parser.py @@ -7,7 +7,7 @@ from .exceptions import TemplateAssertionError from .exceptions import TemplateSyntaxError from .lexer import describe_token from .lexer import describe_token_expr - + if t.TYPE_CHECKING: import typing_extensions as te from .environment import Environment @@ -32,7 +32,7 @@ _statement_keywords = frozenset( ] ) _compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"]) - + _math_nodes: t.Dict[str, t.Type[nodes.Expr]] = { "add": nodes.Add, "sub": nodes.Sub, @@ -40,14 +40,14 @@ _math_nodes: t.Dict[str, t.Type[nodes.Expr]] = { "div": nodes.Div, "floordiv": nodes.FloorDiv, "mod": nodes.Mod, -} - - +} + + class Parser: """This is the central parsing class Jinja uses. It's passed to - extensions and can be used to parse expressions or statements. - """ - + extensions and can be used to parse expressions or statements. + """ + def __init__( self, environment: "Environment", @@ -56,35 +56,35 @@ class Parser: filename: t.Optional[str] = None, state: t.Optional[str] = None, ) -> None: - self.environment = environment - self.stream = environment._tokenize(source, name, filename, state) - self.name = name - self.filename = filename - self.closed = False + self.environment = environment + self.stream = environment._tokenize(source, name, filename, state) + self.name = name + self.filename = filename + self.closed = False self.extensions: t.Dict[ str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]] ] = {} - for extension in environment.iter_extensions(): - for tag in extension.tags: - self.extensions[tag] = extension.parse - self._last_identifier = 0 + for extension in environment.iter_extensions(): + for tag in extension.tags: + self.extensions[tag] = extension.parse + self._last_identifier = 0 self._tag_stack: t.List[str] = [] self._end_token_stack: t.List[t.Tuple[str, ...]] = [] - + def fail( self, msg: str, lineno: t.Optional[int] = None, exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError, ) -> "te.NoReturn": - """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) - + """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: t.Optional[str], @@ -92,220 +92,220 @@ class Parser: lineno: t.Optional[int], ) -> "te.NoReturn": expected: t.Set[str] = set() - for exprs in end_token_stack: + for exprs in end_token_stack: expected.update(map(describe_token_expr, exprs)) - if end_token_stack: + if end_token_stack: currently_looking: t.Optional[str] = " or ".join( map(repr, map(describe_token_expr, end_token_stack[-1])) ) - else: - currently_looking = None - - if name is None: + else: + currently_looking = None + + if name is None: message = ["Unexpected end of template."] - else: + else: message = [f"Encountered unknown tag {name!r}."] - - if currently_looking: - if name is not None and name in expected: + + 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," f" but currently looking for {currently_looking}." ) - else: + else: message.append( f"Jinja was looking for the following tags: {currently_looking}." ) - - if self._tag_stack: + + if self._tag_stack: message.append( "The innermost block that needs to be closed is" f" {self._tag_stack[-1]!r}." ) - + self.fail(" ".join(message), lineno) - + def fail_unknown_tag( self, name: str, lineno: t.Optional[int] = None ) -> "te.NoReturn": - """Called if the parser encounters an unknown tag. Tries to fail - with a human readable error message that could help to identify - the problem. - """ + """Called if the parser encounters an unknown tag. Tries to fail + with a human readable error message that could help to identify + the problem. + """ self._fail_ut_eof(name, self._end_token_stack, lineno) - + def fail_eof( self, end_tokens: t.Optional[t.Tuple[str, ...]] = None, lineno: t.Optional[int] = None, ) -> "te.NoReturn": - """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) + """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) self._fail_ut_eof(None, stack, lineno) - + def is_tuple_end( self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None ) -> bool: - """Are we at the end of a tuple?""" + """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 True + elif extra_end_rules is not None: return self.stream.current.test_any(extra_end_rules) # type: ignore - return False - + return False + def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName: - """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" - self._last_identifier += 1 - rv = object.__new__(nodes.InternalName) + """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" + self._last_identifier += 1 + rv = object.__new__(nodes.InternalName) nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) return rv # type: ignore - + def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: - """Parse a single statement.""" - token = self.stream.current + """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: + self._tag_stack.append(token.value) + pop_tag = True + try: + if token.value in _statement_keywords: f = getattr(self, f"parse_{self.stream.current.value}") return f() # type: ignore if token.value == "call": - return self.parse_call_block() + 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() - + 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: t.Tuple[str, ...], drop_needle: bool = False ) -> t.List[nodes.Node]: - """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 + """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. + + # 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 + 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 - + self.fail_eof(end_tokens) + + if drop_needle: + next(self.stream) + return result + def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]: - """Parse an assign statement.""" - lineno = next(self.stream).lineno - target = self.parse_assign_target(with_namespace=True) + """Parse an assign statement.""" + lineno = next(self.stream).lineno + target = self.parse_assign_target(with_namespace=True) if self.stream.skip_if("assign"): - expr = self.parse_tuple() - return nodes.Assign(target, expr, lineno=lineno) - filter_node = self.parse_filter(None) + expr = self.parse_tuple() + return nodes.Assign(target, expr, lineno=lineno) + filter_node = self.parse_filter(None) body = self.parse_statements(("name:endset",), drop_needle=True) - return nodes.AssignBlock(target, filter_node, body, lineno=lineno) - + return nodes.AssignBlock(target, filter_node, body, lineno=lineno) + def parse_for(self) -> nodes.For: - """Parse a for loop.""" + """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 + test = None if self.stream.skip_if("name:if"): - test = self.parse_expression() + 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_ = [] + 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) -> nodes.If: - """Parse an if construct.""" + """Parse an if construct.""" node = result = nodes.If(lineno=self.stream.expect("name:if").lineno) while True: - node.test = self.parse_tuple(with_condexpr=False) + node.test = self.parse_tuple(with_condexpr=False) node.body = self.parse_statements(("name:elif", "name:else", "name:endif")) - node.elif_ = [] - node.else_ = [] - token = next(self.stream) + node.elif_ = [] + node.else_ = [] + token = next(self.stream) if token.test("name:elif"): - node = nodes.If(lineno=self.stream.current.lineno) - result.elif_.append(node) - continue + node = nodes.If(lineno=self.stream.current.lineno) + result.elif_.append(node) + continue elif token.test("name:else"): result.else_ = self.parse_statements(("name:endif",), drop_needle=True) - break - return result - + break + return result + def parse_with(self) -> nodes.With: - node = nodes.With(lineno=next(self.stream).lineno) + node = nodes.With(lineno=next(self.stream).lineno) targets: t.List[nodes.Expr] = [] values: t.List[nodes.Expr] = [] while self.stream.current.type != "block_end": - if targets: + if targets: self.stream.expect("comma") - target = self.parse_assign_target() + target = self.parse_assign_target() target.set_ctx("param") - targets.append(target) + targets.append(target) self.stream.expect("assign") - values.append(self.parse_expression()) - node.targets = targets - node.values = values + values.append(self.parse_expression()) + node.targets = targets + node.values = values node.body = self.parse_statements(("name:endwith",), drop_needle=True) - return node - + return node + def parse_autoescape(self) -> nodes.Scope: - node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno) + node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno) node.options = [nodes.Keyword("autoescape", self.parse_expression())] node.body = self.parse_statements(("name:endautoescape",), drop_needle=True) - return nodes.Scope([node]) - + return nodes.Scope([node]) + def parse_block(self) -> nodes.Block: - node = nodes.Block(lineno=next(self.stream).lineno) + node = nodes.Block(lineno=next(self.stream).lineno) node.name = self.stream.expect("name").value node.scoped = self.stream.skip_if("name:scoped") node.required = self.stream.skip_if("name:required") - - # 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. + + # 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) # enforce that required blocks only contain whitespace or comments @@ -319,13 +319,13 @@ class Parser: self.fail("Required blocks can only contain comments or whitespace") self.stream.skip_if("name:" + node.name) - return node - + return node + def parse_extends(self) -> nodes.Extends: - node = nodes.Extends(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - return node - + node = nodes.Extends(lineno=next(self.stream).lineno) + node.template = self.parse_expression() + return node + def parse_import_context( self, node: _ImportInclude, default: bool ) -> _ImportInclude: @@ -333,36 +333,36 @@ class Parser: "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 - + self.stream.skip() + else: + node.with_context = default + return node + def parse_include(self) -> nodes.Include: - node = nodes.Include(lineno=next(self.stream).lineno) - node.template = self.parse_expression() + 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) - + node.ignore_missing = True + self.stream.skip(2) + else: + node.ignore_missing = False + return self.parse_import_context(node, True) + def parse_import(self) -> nodes.Import: - node = nodes.Import(lineno=next(self.stream).lineno) - node.template = self.parse_expression() + 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) - + node.target = self.parse_assign_target(name_only=True).name + return self.parse_import_context(node, False) + def parse_from(self) -> nodes.FromImport: - node = nodes.FromImport(lineno=next(self.stream).lineno) - node.template = self.parse_expression() + node = nodes.FromImport(lineno=next(self.stream).lineno) + node.template = self.parse_expression() self.stream.expect("name:import") - node.names = [] - + node.names = [] + def parse_context() -> bool: if ( self.stream.current.value @@ -373,17 +373,17 @@ class Parser: and self.stream.look().test("name:context") ): node.with_context = next(self.stream).value == "with" - self.stream.skip() - return True - return False - + self.stream.skip() + return True + return False + while True: - if node.names: + 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 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", @@ -391,71 +391,71 @@ class Parser: 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) + 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 + else: self.stream.expect("name") if not hasattr(node, "with_context"): - node.with_context = False - return node - + node.with_context = False + return node + def parse_signature(self, node: _MacroCall) -> None: args = node.args = [] defaults = node.defaults = [] self.stream.expect("lparen") while self.stream.current.type != "rparen": - if args: + if args: self.stream.expect("comma") - arg = self.parse_assign_target(name_only=True) + arg = self.parse_assign_target(name_only=True) arg.set_ctx("param") if self.stream.skip_if("assign"): - defaults.append(self.parse_expression()) - elif defaults: + defaults.append(self.parse_expression()) + elif defaults: self.fail("non-default argument follows default argument") - args.append(arg) + args.append(arg) self.stream.expect("rparen") - + def parse_call_block(self) -> nodes.CallBlock: - node = nodes.CallBlock(lineno=next(self.stream).lineno) + node = nodes.CallBlock(lineno=next(self.stream).lineno) if self.stream.current.type == "lparen": - self.parse_signature(node) - else: - node.args = [] - node.defaults = [] - + self.parse_signature(node) + else: + node.args = [] + node.defaults = [] + call_node = self.parse_expression() if not isinstance(call_node, nodes.Call): self.fail("expected call", node.lineno) node.call = call_node node.body = self.parse_statements(("name:endcall",), drop_needle=True) - return node - + return node + def parse_filter_block(self) -> nodes.FilterBlock: - node = nodes.FilterBlock(lineno=next(self.stream).lineno) + node = nodes.FilterBlock(lineno=next(self.stream).lineno) node.filter = self.parse_filter(None, start_inline=True) # type: ignore node.body = self.parse_statements(("name:endfilter",), drop_needle=True) - return node - + return node + def parse_macro(self) -> nodes.Macro: - node = nodes.Macro(lineno=next(self.stream).lineno) - node.name = self.parse_assign_target(name_only=True).name - self.parse_signature(node) + 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 - + return node + def parse_print(self) -> nodes.Output: - node = nodes.Output(lineno=next(self.stream).lineno) - node.nodes = [] + node = nodes.Output(lineno=next(self.stream).lineno) + node.nodes = [] while self.stream.current.type != "block_end": - if node.nodes: + if node.nodes: self.stream.expect("comma") - node.nodes.append(self.parse_expression()) - return node - + node.nodes.append(self.parse_expression()) + return node + @typing.overload def parse_assign_target( self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ... @@ -480,205 +480,205 @@ class Parser: with_namespace: bool = False, ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: """Parse an assignment target. As Jinja 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 - `with_namespace` is enabled, a namespace assignment may be parsed. - """ + 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 + `with_namespace` is enabled, a namespace assignment may be parsed. + """ target: nodes.Expr if with_namespace and self.stream.look().type == "dot": token = self.stream.expect("name") - next(self.stream) # dot + next(self.stream) # dot attr = self.stream.expect("name") - target = nodes.NSRef(token.value, attr.value, lineno=token.lineno) - elif name_only: + target = nodes.NSRef(token.value, attr.value, lineno=token.lineno) + elif name_only: token = self.stream.expect("name") target = nodes.Name(token.value, "store", lineno=token.lineno) - else: - if with_tuple: + else: + if with_tuple: target = self.parse_tuple( simplified=True, extra_end_rules=extra_end_rules ) - else: - target = self.parse_primary() + else: + target = self.parse_primary() target.set_ctx("store") - if not target.can_assign(): + if not target.can_assign(): self.fail( f"can't assign to {type(target).__name__.lower()!r}", target.lineno ) - + return target # type: ignore def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr: - """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() - + """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) -> nodes.Expr: - lineno = self.stream.current.lineno - expr1 = self.parse_or() + lineno = self.stream.current.lineno + expr1 = self.parse_or() expr3: t.Optional[nodes.Expr] while self.stream.skip_if("name:if"): - expr2 = self.parse_or() + 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 - + 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) -> nodes.Expr: - lineno = self.stream.current.lineno - left = self.parse_and() + 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 - + right = self.parse_and() + left = nodes.Or(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + def parse_and(self) -> nodes.Expr: - lineno = self.stream.current.lineno - left = self.parse_not() + 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 - + right = self.parse_not() + left = nodes.And(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + def parse_not(self) -> nodes.Expr: if self.stream.current.test("name:not"): - lineno = next(self.stream).lineno - return nodes.Not(self.parse_not(), lineno=lineno) - return self.parse_compare() - + lineno = next(self.stream).lineno + return nodes.Not(self.parse_not(), lineno=lineno) + return self.parse_compare() + def parse_compare(self) -> nodes.Expr: - lineno = self.stream.current.lineno - expr = self.parse_math1() - ops = [] + lineno = self.stream.current.lineno + expr = self.parse_math1() + ops = [] while True: - token_type = self.stream.current.type - if token_type in _compare_operators: - next(self.stream) - ops.append(nodes.Operand(token_type, self.parse_math1())) + token_type = self.stream.current.type + if token_type in _compare_operators: + next(self.stream) + ops.append(nodes.Operand(token_type, self.parse_math1())) elif self.stream.skip_if("name:in"): ops.append(nodes.Operand("in", self.parse_math1())) elif self.stream.current.test("name:not") and self.stream.look().test( "name:in" ): - self.stream.skip(2) + self.stream.skip(2) ops.append(nodes.Operand("notin", self.parse_math1())) - else: - break - lineno = self.stream.current.lineno - if not ops: - return expr - return nodes.Compare(expr, ops, lineno=lineno) - + else: + break + lineno = self.stream.current.lineno + if not ops: + return expr + return nodes.Compare(expr, ops, lineno=lineno) + def parse_math1(self) -> nodes.Expr: - lineno = self.stream.current.lineno - left = self.parse_concat() + lineno = self.stream.current.lineno + left = self.parse_concat() while self.stream.current.type in ("add", "sub"): - cls = _math_nodes[self.stream.current.type] - next(self.stream) - right = self.parse_concat() - left = cls(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - + cls = _math_nodes[self.stream.current.type] + next(self.stream) + right = self.parse_concat() + left = cls(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + def parse_concat(self) -> nodes.Expr: - lineno = self.stream.current.lineno - args = [self.parse_math2()] + lineno = self.stream.current.lineno + args = [self.parse_math2()] while self.stream.current.type == "tilde": - next(self.stream) - args.append(self.parse_math2()) - if len(args) == 1: - return args[0] - return nodes.Concat(args, lineno=lineno) - + next(self.stream) + args.append(self.parse_math2()) + if len(args) == 1: + return args[0] + return nodes.Concat(args, lineno=lineno) + def parse_math2(self) -> nodes.Expr: - lineno = self.stream.current.lineno - left = self.parse_pow() + lineno = self.stream.current.lineno + left = self.parse_pow() while self.stream.current.type in ("mul", "div", "floordiv", "mod"): - cls = _math_nodes[self.stream.current.type] - next(self.stream) - right = self.parse_pow() - left = cls(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - + cls = _math_nodes[self.stream.current.type] + next(self.stream) + right = self.parse_pow() + left = cls(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + def parse_pow(self) -> nodes.Expr: - lineno = self.stream.current.lineno - left = self.parse_unary() + 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 - + 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: bool = True) -> nodes.Expr: - token_type = self.stream.current.type - lineno = self.stream.current.lineno + token_type = self.stream.current.type + lineno = self.stream.current.lineno node: nodes.Expr if token_type == "sub": - next(self.stream) - node = nodes.Neg(self.parse_unary(False), lineno=lineno) + 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 - + 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) -> nodes.Expr: - token = self.stream.current + token = self.stream.current node: nodes.Expr 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.Const(None, lineno=token.lineno) + else: node = nodes.Name(token.value, "load", lineno=token.lineno) - next(self.stream) + next(self.stream) elif token.type == "string": - next(self.stream) - buf = [token.value] - lineno = token.lineno + next(self.stream) + buf = [token.value] + lineno = token.lineno while self.stream.current.type == "string": - buf.append(self.stream.current.value) - next(self.stream) + 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) + 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) + next(self.stream) + node = self.parse_tuple(explicit_parentheses=True) self.stream.expect("rparen") elif token.type == "lbracket": - node = self.parse_list() + node = self.parse_list() elif token.type == "lbrace": - node = self.parse_dict() - else: + node = self.parse_dict() + else: self.fail(f"unexpected {describe_token(token)!r}", token.lineno) - return node - + return node + def parse_tuple( self, simplified: bool = False, @@ -686,275 +686,275 @@ class Parser: extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, explicit_parentheses: bool = False, ) -> t.Union[nodes.Tuple, nodes.Expr]: - """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: + """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: def parse() -> nodes.Expr: return self.parse_expression(with_condexpr=False) args: t.List[nodes.Expr] = [] - is_tuple = False + is_tuple = False while True: - if args: + if args: self.stream.expect("comma") - if self.is_tuple_end(extra_end_rules): - break - args.append(parse()) + 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: + 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," f" got {describe_token(self.stream.current)!r}" ) - + return nodes.Tuple(args, "load", lineno=lineno) - + def parse_list(self) -> nodes.List: token = self.stream.expect("lbracket") items: t.List[nodes.Expr] = [] while self.stream.current.type != "rbracket": - if items: + if items: self.stream.expect("comma") if self.stream.current.type == "rbracket": - break - items.append(self.parse_expression()) + break + items.append(self.parse_expression()) self.stream.expect("rbracket") - return nodes.List(items, lineno=token.lineno) - + return nodes.List(items, lineno=token.lineno) + def parse_dict(self) -> nodes.Dict: token = self.stream.expect("lbrace") items: t.List[nodes.Pair] = [] while self.stream.current.type != "rbrace": - if items: + if items: self.stream.expect("comma") if self.stream.current.type == "rbrace": - break - key = self.parse_expression() + break + key = self.parse_expression() self.stream.expect("colon") - value = self.parse_expression() - items.append(nodes.Pair(key, value, lineno=key.lineno)) + value = self.parse_expression() + items.append(nodes.Pair(key, value, lineno=key.lineno)) self.stream.expect("rbrace") - return nodes.Dict(items, lineno=token.lineno) - + return nodes.Dict(items, lineno=token.lineno) + def parse_postfix(self, node: nodes.Expr) -> nodes.Expr: while True: - token_type = self.stream.current.type + 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 + 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 - + node = self.parse_call(node) + else: + break + return node + def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr: while True: - token_type = self.stream.current.type + token_type = self.stream.current.type if token_type == "pipe": node = self.parse_filter(node) # type: ignore 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 + 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 - + node = self.parse_call(node) + else: + break + return node + def parse_subscript( self, node: nodes.Expr ) -> t.Union[nodes.Getattr, nodes.Getitem]: - token = next(self.stream) + token = next(self.stream) arg: nodes.Expr if token.type == "dot": - attr_token = self.stream.current - next(self.stream) + 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) + arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) return nodes.Getitem(node, arg, "load", lineno=token.lineno) if token.type == "lbracket": args: t.List[nodes.Expr] = [] while self.stream.current.type != "rbracket": - if args: + if args: self.stream.expect("comma") - args.append(self.parse_subscribed()) + args.append(self.parse_subscribed()) self.stream.expect("rbracket") - if len(args) == 1: - arg = args[0] - else: + 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", token.lineno) - + def parse_subscribed(self) -> nodes.Expr: - lineno = self.stream.current.lineno + lineno = self.stream.current.lineno args: t.List[t.Optional[nodes.Expr]] - + if self.stream.current.type == "colon": - next(self.stream) - args = [None] - else: - node = self.parse_expression() + next(self.stream) + args = [None] + else: + node = self.parse_expression() if self.stream.current.type != "colon": - return node - next(self.stream) - args = [node] - + return node + next(self.stream) + args = [node] + if self.stream.current.type == "colon": - args.append(None) + args.append(None) elif self.stream.current.type not in ("rbracket", "comma"): - args.append(self.parse_expression()) - else: - args.append(None) - + args.append(self.parse_expression()) + else: + args.append(None) + if self.stream.current.type == "colon": - next(self.stream) + 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) - + args.append(self.parse_expression()) + else: + args.append(None) + else: + args.append(None) + + return nodes.Slice(lineno=lineno, *args) + def parse_call_args(self) -> t.Tuple: token = self.stream.expect("lparen") - args = [] - kwargs = [] + args = [] + kwargs = [] dyn_args = None dyn_kwargs = None - require_comma = False - + require_comma = False + def ensure(expr: bool) -> None: - if not expr: + if not expr: self.fail("invalid syntax for function call expression", token.lineno) - + while self.stream.current.type != "rparen": - if require_comma: + if require_comma: self.stream.expect("comma") - # support for trailing comma + # support for trailing comma if self.stream.current.type == "rparen": - break + 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() + 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_kwargs is None) + next(self.stream) + dyn_kwargs = self.parse_expression() + else: if ( self.stream.current.type == "name" and self.stream.look().type == "assign" ): # Parsing a kwarg ensure(dyn_kwargs is None) - key = self.stream.current.value - self.stream.skip(2) - value = self.parse_expression() + key = self.stream.current.value + self.stream.skip(2) + value = self.parse_expression() kwargs.append(nodes.Keyword(key, value, lineno=value.lineno)) - else: + else: # Parsing an arg ensure(dyn_args is None and dyn_kwargs is None and not kwargs) - args.append(self.parse_expression()) - - require_comma = True + args.append(self.parse_expression()) + + require_comma = True self.stream.expect("rparen") return args, kwargs, dyn_args, dyn_kwargs - + def parse_call(self, node: nodes.Expr) -> nodes.Call: # The lparen will be expected in parse_call_args, but the lineno # needs to be recorded before the stream is advanced. token = self.stream.current args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) - + def parse_filter( self, node: t.Optional[nodes.Expr], start_inline: bool = False ) -> t.Optional[nodes.Expr]: while self.stream.current.type == "pipe" or start_inline: - if not start_inline: - next(self.stream) + if not start_inline: + next(self.stream) token = self.stream.expect("name") - name = token.value + name = token.value while self.stream.current.type == "dot": - next(self.stream) + next(self.stream) name += "." + self.stream.expect("name").value if self.stream.current.type == "lparen": args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() - else: - args = [] - kwargs = [] - dyn_args = dyn_kwargs = 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 - + start_inline = False + return node + def parse_test(self, node: nodes.Expr) -> nodes.Expr: - token = next(self.stream) + token = next(self.stream) if self.stream.current.test("name:not"): - next(self.stream) - negated = True - else: - negated = False + next(self.stream) + negated = True + else: + negated = False name = self.stream.expect("name").value while self.stream.current.type == "dot": - next(self.stream) + next(self.stream) name += "." + self.stream.expect("name").value - dyn_args = dyn_kwargs = None - kwargs = [] + dyn_args = dyn_kwargs = None + kwargs = [] if self.stream.current.type == "lparen": args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() elif ( @@ -975,66 +975,66 @@ class Parser: arg_node = self.parse_primary() arg_node = self.parse_postfix(arg_node) args = [arg_node] - else: - args = [] + 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 - + if negated: + node = nodes.Not(node, lineno=token.lineno) + return node + def subparse( self, end_tokens: t.Optional[t.Tuple[str, ...]] = None ) -> t.List[nodes.Node]: body: t.List[nodes.Node] = [] data_buffer: t.List[nodes.Node] = [] - add_data = data_buffer.append - - if end_tokens is not None: - self._end_token_stack.append(end_tokens) - + add_data = data_buffer.append + + if end_tokens is not None: + self._end_token_stack.append(end_tokens) + def flush_data() -> None: - 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 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: + if token.value: add_data(nodes.TemplateData(token.value, lineno=token.lineno)) - next(self.stream) + next(self.stream) elif token.type == "variable_begin": - next(self.stream) - add_data(self.parse_tuple(with_condexpr=True)) + 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) + 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) + return body + rv = self.parse_statement() + if isinstance(rv, list): + body.extend(rv) + else: + body.append(rv) self.stream.expect("block_end") - else: + else: raise AssertionError("internal parsing error") - - flush_data() - finally: - if end_tokens is not None: - self._end_token_stack.pop() - return body - + + flush_data() + finally: + if end_tokens is not None: + self._end_token_stack.pop() + return body + def parse(self) -> nodes.Template: - """Parse the whole template into a `Template` node.""" - result = nodes.Template(self.subparse(), lineno=1) - result.set_environment(self.environment) - return result + """Parse the whole template into a `Template` node.""" + result = nodes.Template(self.subparse(), lineno=1) + result.set_environment(self.environment) + return result |