"""Parse tokens from the lexer into nodes for the compiler.""" import typing import typing as t
from . import nodes 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
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 isNone:
lineno = self.stream.current.lineno raise exc(msg, lineno, self.name, self.filename)
def _fail_ut_eof(
self,
name: t.Optional[str],
end_token_stack: t.List[t.Tuple[str, ...]],
lineno: t.Optional[int],
) -> "te.NoReturn":
expected: t.Set[str] = set() for exprs in end_token_stack:
expected.update(map(describe_token_expr, exprs)) 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 isNone:
message = ["Unexpected end of template."] else:
message = [f"Encountered unknown tag {name!r}."]
if currently_looking: if name isnotNoneand name in expected:
message.append( "You probably made a nesting mistake. Jinja is expecting this tag,"
f" but currently looking for {currently_looking}."
) else:
message.append(
f"Jinja was looking for the following tags: {currently_looking}."
)
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. """
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 isnotNone:
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?""" if self.stream.current.type in ("variable_end", "block_end", "rparen"): returnTrue elif extra_end_rules isnotNone: return self.stream.current.test_any(extra_end_rules) # type: ignore returnFalse
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)
nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) return rv
def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: """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:
f = getattr(self, f"parse_{self.stream.current.value}") return f() # type: ignore 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 isnotNone: 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 isnot 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)
# 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."
)
# enforce that required blocks only contain whitespace or comments # by asserting that the body, if not empty, is just TemplateData nodes # with whitespace data if node.required andnot all(
isinstance(child, nodes.TemplateData) and child.data.isspace() for body in node.body for child in body.nodes # type: ignore
):
self.fail("Required blocks can only contain comments or whitespace")
def parse_context() -> bool: 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() returnTrue returnFalse
whileTrue: 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:
self.stream.expect("name") ifnot hasattr(node, "with_context"):
node.with_context = False return node
def parse_assign_target(
self,
with_tuple: bool = True,
name_only: bool = False,
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
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. """
target: nodes.Expr
ifnot 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()
def parse_or(self) -> nodes.Expr:
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) -> nodes.Expr:
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_tuple(
self,
simplified: bool = False,
with_condexpr: bool = True,
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` istrueif the parsing was triggered by an
expression in parentheses. This is used to figure out if an empty
tuple is a valid expression ornot. """
lineno = self.stream.current.lineno if simplified:
parse = self.parse_primary elif with_condexpr:
parse = self.parse_expression else:
whileTrue: 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
ifnot 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. ifnot 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:
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) -> nodes.Dict:
token = self.stream.expect("lbrace")
items: t.List[nodes.Pair] = [] 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: nodes.Expr) -> nodes.Expr: whileTrue:
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: nodes.Expr) -> nodes.Expr: whileTrue:
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 elif token_type == "lparen":
node = self.parse_call(node) else: break return node
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)
if end_tokens isnotNone:
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 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 isnotNoneand 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 isnotNone:
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
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.