1207 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1207 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """AST nodes generated by the parser for the compiler. Also provides
 | |
| some node tree helper functions used by the parser and compiler in order
 | |
| to normalize nodes.
 | |
| """
 | |
| 
 | |
| import inspect
 | |
| import operator
 | |
| import typing as t
 | |
| from collections import deque
 | |
| 
 | |
| from markupsafe import Markup
 | |
| 
 | |
| from .utils import _PassArg
 | |
| 
 | |
| if t.TYPE_CHECKING:
 | |
|     import typing_extensions as te
 | |
| 
 | |
|     from .environment import Environment
 | |
| 
 | |
| _NodeBound = t.TypeVar("_NodeBound", bound="Node")
 | |
| 
 | |
| _binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
 | |
|     "*": operator.mul,
 | |
|     "/": operator.truediv,
 | |
|     "//": operator.floordiv,
 | |
|     "**": operator.pow,
 | |
|     "%": operator.mod,
 | |
|     "+": operator.add,
 | |
|     "-": operator.sub,
 | |
| }
 | |
| 
 | |
| _uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
 | |
|     "not": operator.not_,
 | |
|     "+": operator.pos,
 | |
|     "-": operator.neg,
 | |
| }
 | |
| 
 | |
| _cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
 | |
|     "eq": operator.eq,
 | |
|     "ne": operator.ne,
 | |
|     "gt": operator.gt,
 | |
|     "gteq": operator.ge,
 | |
|     "lt": operator.lt,
 | |
|     "lteq": operator.le,
 | |
|     "in": lambda a, b: a in b,
 | |
|     "notin": lambda a, b: a not in b,
 | |
| }
 | |
| 
 | |
| 
 | |
| class Impossible(Exception):
 | |
|     """Raised if the node could not perform a requested action."""
 | |
| 
 | |
| 
 | |
| class NodeType(type):
 | |
|     """A metaclass for nodes that handles the field and attribute
 | |
|     inheritance.  fields and attributes from the parent class are
 | |
|     automatically forwarded to the child."""
 | |
| 
 | |
|     def __new__(mcs, name, bases, d):  # type: ignore
 | |
|         for attr in "fields", "attributes":
 | |
|             storage: t.List[t.Tuple[str, ...]] = []
 | |
|             storage.extend(getattr(bases[0] if bases else object, attr, ()))
 | |
|             storage.extend(d.get(attr, ()))
 | |
|             assert len(bases) <= 1, "multiple inheritance not allowed"
 | |
|             assert len(storage) == len(set(storage)), "layout conflict"
 | |
|             d[attr] = tuple(storage)
 | |
|         d.setdefault("abstract", False)
 | |
|         return type.__new__(mcs, name, bases, d)
 | |
| 
 | |
| 
 | |
| class EvalContext:
 | |
|     """Holds evaluation time information.  Custom attributes can be attached
 | |
|     to it in extensions.
 | |
|     """
 | |
| 
 | |
|     def __init__(
 | |
|         self, environment: "Environment", template_name: t.Optional[str] = None
 | |
|     ) -> None:
 | |
|         self.environment = environment
 | |
|         if callable(environment.autoescape):
 | |
|             self.autoescape = environment.autoescape(template_name)
 | |
|         else:
 | |
|             self.autoescape = environment.autoescape
 | |
|         self.volatile = False
 | |
| 
 | |
|     def save(self) -> t.Mapping[str, t.Any]:
 | |
|         return self.__dict__.copy()
 | |
| 
 | |
|     def revert(self, old: t.Mapping[str, t.Any]) -> None:
 | |
|         self.__dict__.clear()
 | |
|         self.__dict__.update(old)
 | |
| 
 | |
| 
 | |
| def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext:
 | |
|     if ctx is None:
 | |
|         if node.environment is None:
 | |
|             raise RuntimeError(
 | |
|                 "if no eval context is passed, the node must have an"
 | |
|                 " attached environment."
 | |
|             )
 | |
|         return EvalContext(node.environment)
 | |
|     return ctx
 | |
| 
 | |
| 
 | |
| class Node(metaclass=NodeType):
 | |
|     """Baseclass for all Jinja nodes.  There are a number of nodes available
 | |
|     of different types.  There are four major types:
 | |
| 
 | |
|     -   :class:`Stmt`: statements
 | |
|     -   :class:`Expr`: expressions
 | |
|     -   :class:`Helper`: helper nodes
 | |
|     -   :class:`Template`: the outermost wrapper node
 | |
| 
 | |
|     All nodes have fields and attributes.  Fields may be other nodes, lists,
 | |
|     or arbitrary values.  Fields are passed to the constructor as regular
 | |
|     positional arguments, attributes as keyword arguments.  Each node has
 | |
|     two attributes: `lineno` (the line number of the node) and `environment`.
 | |
|     The `environment` attribute is set at the end of the parsing process for
 | |
|     all nodes automatically.
 | |
|     """
 | |
| 
 | |
|     fields: t.Tuple[str, ...] = ()
 | |
|     attributes: t.Tuple[str, ...] = ("lineno", "environment")
 | |
|     abstract = True
 | |
| 
 | |
|     lineno: int
 | |
|     environment: t.Optional["Environment"]
 | |
| 
 | |
|     def __init__(self, *fields: t.Any, **attributes: t.Any) -> None:
 | |
|         if self.abstract:
 | |
|             raise TypeError("abstract nodes are not instantiable")
 | |
|         if fields:
 | |
|             if len(fields) != len(self.fields):
 | |
|                 if not self.fields:
 | |
|                     raise TypeError(f"{type(self).__name__!r} takes 0 arguments")
 | |
|                 raise TypeError(
 | |
|                     f"{type(self).__name__!r} takes 0 or {len(self.fields)}"
 | |
|                     f" argument{'s' if len(self.fields) != 1 else ''}"
 | |
|                 )
 | |
|             for name, arg in zip(self.fields, fields):
 | |
|                 setattr(self, name, arg)
 | |
|         for attr in self.attributes:
 | |
|             setattr(self, attr, attributes.pop(attr, None))
 | |
|         if attributes:
 | |
|             raise TypeError(f"unknown attribute {next(iter(attributes))!r}")
 | |
| 
 | |
|     def iter_fields(
 | |
|         self,
 | |
|         exclude: t.Optional[t.Container[str]] = None,
 | |
|         only: t.Optional[t.Container[str]] = None,
 | |
|     ) -> t.Iterator[t.Tuple[str, t.Any]]:
 | |
|         """This method iterates over all fields that are defined and yields
 | |
|         ``(key, value)`` tuples.  Per default all fields are returned, but
 | |
|         it's possible to limit that to some fields by providing the `only`
 | |
|         parameter or to exclude some using the `exclude` parameter.  Both
 | |
|         should be sets or tuples of field names.
 | |
|         """
 | |
|         for name in self.fields:
 | |
|             if (
 | |
|                 (exclude is None and only is None)
 | |
|                 or (exclude is not None and name not in exclude)
 | |
|                 or (only is not None and name in only)
 | |
|             ):
 | |
|                 try:
 | |
|                     yield name, getattr(self, name)
 | |
|                 except AttributeError:
 | |
|                     pass
 | |
| 
 | |
|     def iter_child_nodes(
 | |
|         self,
 | |
|         exclude: t.Optional[t.Container[str]] = None,
 | |
|         only: t.Optional[t.Container[str]] = None,
 | |
|     ) -> t.Iterator["Node"]:
 | |
|         """Iterates over all direct child nodes of the node.  This iterates
 | |
|         over all fields and yields the values of they are nodes.  If the value
 | |
|         of a field is a list all the nodes in that list are returned.
 | |
|         """
 | |
|         for _, item in self.iter_fields(exclude, only):
 | |
|             if isinstance(item, list):
 | |
|                 for n in item:
 | |
|                     if isinstance(n, Node):
 | |
|                         yield n
 | |
|             elif isinstance(item, Node):
 | |
|                 yield item
 | |
| 
 | |
|     def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
 | |
|         """Find the first node of a given type.  If no such node exists the
 | |
|         return value is `None`.
 | |
|         """
 | |
|         for result in self.find_all(node_type):
 | |
|             return result
 | |
| 
 | |
|         return None
 | |
| 
 | |
|     def find_all(
 | |
|         self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
 | |
|     ) -> t.Iterator[_NodeBound]:
 | |
|         """Find all the nodes of a given type.  If the type is a tuple,
 | |
|         the check is performed for any of the tuple items.
 | |
|         """
 | |
|         for child in self.iter_child_nodes():
 | |
|             if isinstance(child, node_type):
 | |
|                 yield child  # type: ignore
 | |
|             yield from child.find_all(node_type)
 | |
| 
 | |
|     def set_ctx(self, ctx: str) -> "Node":
 | |
|         """Reset the context of a node and all child nodes.  Per default the
 | |
|         parser will all generate nodes that have a 'load' context as it's the
 | |
|         most common one.  This method is used in the parser to set assignment
 | |
|         targets and other nodes to a store context.
 | |
|         """
 | |
|         todo = deque([self])
 | |
|         while todo:
 | |
|             node = todo.popleft()
 | |
|             if "ctx" in node.fields:
 | |
|                 node.ctx = ctx  # type: ignore
 | |
|             todo.extend(node.iter_child_nodes())
 | |
|         return self
 | |
| 
 | |
|     def set_lineno(self, lineno: int, override: bool = False) -> "Node":
 | |
|         """Set the line numbers of the node and children."""
 | |
|         todo = deque([self])
 | |
|         while todo:
 | |
|             node = todo.popleft()
 | |
|             if "lineno" in node.attributes:
 | |
|                 if node.lineno is None or override:
 | |
|                     node.lineno = lineno
 | |
|             todo.extend(node.iter_child_nodes())
 | |
|         return self
 | |
| 
 | |
|     def set_environment(self, environment: "Environment") -> "Node":
 | |
|         """Set the environment for all nodes."""
 | |
|         todo = deque([self])
 | |
|         while todo:
 | |
|             node = todo.popleft()
 | |
|             node.environment = environment
 | |
|             todo.extend(node.iter_child_nodes())
 | |
|         return self
 | |
| 
 | |
|     def __eq__(self, other: t.Any) -> bool:
 | |
|         if type(self) is not type(other):
 | |
|             return NotImplemented
 | |
| 
 | |
|         return tuple(self.iter_fields()) == tuple(other.iter_fields())
 | |
| 
 | |
|     __hash__ = object.__hash__
 | |
| 
 | |
|     def __repr__(self) -> str:
 | |
|         args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields)
 | |
|         return f"{type(self).__name__}({args_str})"
 | |
| 
 | |
|     def dump(self) -> str:
 | |
|         def _dump(node: t.Union[Node, t.Any]) -> None:
 | |
|             if not isinstance(node, Node):
 | |
|                 buf.append(repr(node))
 | |
|                 return
 | |
| 
 | |
|             buf.append(f"nodes.{type(node).__name__}(")
 | |
|             if not node.fields:
 | |
|                 buf.append(")")
 | |
|                 return
 | |
|             for idx, field in enumerate(node.fields):
 | |
|                 if idx:
 | |
|                     buf.append(", ")
 | |
|                 value = getattr(node, field)
 | |
|                 if isinstance(value, list):
 | |
|                     buf.append("[")
 | |
|                     for idx, item in enumerate(value):
 | |
|                         if idx:
 | |
|                             buf.append(", ")
 | |
|                         _dump(item)
 | |
|                     buf.append("]")
 | |
|                 else:
 | |
|                     _dump(value)
 | |
|             buf.append(")")
 | |
| 
 | |
|         buf: t.List[str] = []
 | |
|         _dump(self)
 | |
|         return "".join(buf)
 | |
| 
 | |
| 
 | |
| class Stmt(Node):
 | |
|     """Base node for all statements."""
 | |
| 
 | |
|     abstract = True
 | |
| 
 | |
| 
 | |
| class Helper(Node):
 | |
|     """Nodes that exist in a specific context only."""
 | |
| 
 | |
|     abstract = True
 | |
| 
 | |
| 
 | |
| class Template(Node):
 | |
|     """Node that represents a template.  This must be the outermost node that
 | |
|     is passed to the compiler.
 | |
|     """
 | |
| 
 | |
|     fields = ("body",)
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class Output(Stmt):
 | |
|     """A node that holds multiple expressions which are then printed out.
 | |
|     This is used both for the `print` statement and the regular template data.
 | |
|     """
 | |
| 
 | |
|     fields = ("nodes",)
 | |
|     nodes: t.List["Expr"]
 | |
| 
 | |
| 
 | |
| class Extends(Stmt):
 | |
|     """Represents an extends statement."""
 | |
| 
 | |
|     fields = ("template",)
 | |
|     template: "Expr"
 | |
| 
 | |
| 
 | |
| class For(Stmt):
 | |
|     """The for loop.  `target` is the target for the iteration (usually a
 | |
|     :class:`Name` or :class:`Tuple`), `iter` the iterable.  `body` is a list
 | |
|     of nodes that are used as loop-body, and `else_` a list of nodes for the
 | |
|     `else` block.  If no else node exists it has to be an empty list.
 | |
| 
 | |
|     For filtered nodes an expression can be stored as `test`, otherwise `None`.
 | |
|     """
 | |
| 
 | |
|     fields = ("target", "iter", "body", "else_", "test", "recursive")
 | |
|     target: Node
 | |
|     iter: Node
 | |
|     body: t.List[Node]
 | |
|     else_: t.List[Node]
 | |
|     test: t.Optional[Node]
 | |
|     recursive: bool
 | |
| 
 | |
| 
 | |
| class If(Stmt):
 | |
|     """If `test` is true, `body` is rendered, else `else_`."""
 | |
| 
 | |
|     fields = ("test", "body", "elif_", "else_")
 | |
|     test: Node
 | |
|     body: t.List[Node]
 | |
|     elif_: t.List["If"]
 | |
|     else_: t.List[Node]
 | |
| 
 | |
| 
 | |
| class Macro(Stmt):
 | |
|     """A macro definition.  `name` is the name of the macro, `args` a list of
 | |
|     arguments and `defaults` a list of defaults if there are any.  `body` is
 | |
|     a list of nodes for the macro body.
 | |
|     """
 | |
| 
 | |
|     fields = ("name", "args", "defaults", "body")
 | |
|     name: str
 | |
|     args: t.List["Name"]
 | |
|     defaults: t.List["Expr"]
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class CallBlock(Stmt):
 | |
|     """Like a macro without a name but a call instead.  `call` is called with
 | |
|     the unnamed macro as `caller` argument this node holds.
 | |
|     """
 | |
| 
 | |
|     fields = ("call", "args", "defaults", "body")
 | |
|     call: "Call"
 | |
|     args: t.List["Name"]
 | |
|     defaults: t.List["Expr"]
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class FilterBlock(Stmt):
 | |
|     """Node for filter sections."""
 | |
| 
 | |
|     fields = ("body", "filter")
 | |
|     body: t.List[Node]
 | |
|     filter: "Filter"
 | |
| 
 | |
| 
 | |
| class With(Stmt):
 | |
|     """Specific node for with statements.  In older versions of Jinja the
 | |
|     with statement was implemented on the base of the `Scope` node instead.
 | |
| 
 | |
|     .. versionadded:: 2.9.3
 | |
|     """
 | |
| 
 | |
|     fields = ("targets", "values", "body")
 | |
|     targets: t.List["Expr"]
 | |
|     values: t.List["Expr"]
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class Block(Stmt):
 | |
|     """A node that represents a block.
 | |
| 
 | |
|     .. versionchanged:: 3.0.0
 | |
|         the `required` field was added.
 | |
|     """
 | |
| 
 | |
|     fields = ("name", "body", "scoped", "required")
 | |
|     name: str
 | |
|     body: t.List[Node]
 | |
|     scoped: bool
 | |
|     required: bool
 | |
| 
 | |
| 
 | |
| class Include(Stmt):
 | |
|     """A node that represents the include tag."""
 | |
| 
 | |
|     fields = ("template", "with_context", "ignore_missing")
 | |
|     template: "Expr"
 | |
|     with_context: bool
 | |
|     ignore_missing: bool
 | |
| 
 | |
| 
 | |
| class Import(Stmt):
 | |
|     """A node that represents the import tag."""
 | |
| 
 | |
|     fields = ("template", "target", "with_context")
 | |
|     template: "Expr"
 | |
|     target: str
 | |
|     with_context: bool
 | |
| 
 | |
| 
 | |
| class FromImport(Stmt):
 | |
|     """A node that represents the from import tag.  It's important to not
 | |
|     pass unsafe names to the name attribute.  The compiler translates the
 | |
|     attribute lookups directly into getattr calls and does *not* use the
 | |
|     subscript callback of the interface.  As exported variables may not
 | |
|     start with double underscores (which the parser asserts) this is not a
 | |
|     problem for regular Jinja code, but if this node is used in an extension
 | |
|     extra care must be taken.
 | |
| 
 | |
|     The list of names may contain tuples if aliases are wanted.
 | |
|     """
 | |
| 
 | |
|     fields = ("template", "names", "with_context")
 | |
|     template: "Expr"
 | |
|     names: t.List[t.Union[str, t.Tuple[str, str]]]
 | |
|     with_context: bool
 | |
| 
 | |
| 
 | |
| class ExprStmt(Stmt):
 | |
|     """A statement that evaluates an expression and discards the result."""
 | |
| 
 | |
|     fields = ("node",)
 | |
|     node: Node
 | |
| 
 | |
| 
 | |
| class Assign(Stmt):
 | |
|     """Assigns an expression to a target."""
 | |
| 
 | |
|     fields = ("target", "node")
 | |
|     target: "Expr"
 | |
|     node: Node
 | |
| 
 | |
| 
 | |
| class AssignBlock(Stmt):
 | |
|     """Assigns a block to a target."""
 | |
| 
 | |
|     fields = ("target", "filter", "body")
 | |
|     target: "Expr"
 | |
|     filter: t.Optional["Filter"]
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class Expr(Node):
 | |
|     """Baseclass for all expressions."""
 | |
| 
 | |
|     abstract = True
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         """Return the value of the expression as constant or raise
 | |
|         :exc:`Impossible` if this was not possible.
 | |
| 
 | |
|         An :class:`EvalContext` can be provided, if none is given
 | |
|         a default context is created which requires the nodes to have
 | |
|         an attached environment.
 | |
| 
 | |
|         .. versionchanged:: 2.4
 | |
|            the `eval_ctx` parameter was added.
 | |
|         """
 | |
|         raise Impossible()
 | |
| 
 | |
|     def can_assign(self) -> bool:
 | |
|         """Check if it's possible to assign something to this node."""
 | |
|         return False
 | |
| 
 | |
| 
 | |
| class BinExpr(Expr):
 | |
|     """Baseclass for all binary expressions."""
 | |
| 
 | |
|     fields = ("left", "right")
 | |
|     left: Expr
 | |
|     right: Expr
 | |
|     operator: str
 | |
|     abstract = True
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         # intercepted operators cannot be folded at compile time
 | |
|         if (
 | |
|             eval_ctx.environment.sandboxed
 | |
|             and self.operator in eval_ctx.environment.intercepted_binops  # type: ignore
 | |
|         ):
 | |
|             raise Impossible()
 | |
|         f = _binop_to_func[self.operator]
 | |
|         try:
 | |
|             return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
| 
 | |
| class UnaryExpr(Expr):
 | |
|     """Baseclass for all unary expressions."""
 | |
| 
 | |
|     fields = ("node",)
 | |
|     node: Expr
 | |
|     operator: str
 | |
|     abstract = True
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         # intercepted operators cannot be folded at compile time
 | |
|         if (
 | |
|             eval_ctx.environment.sandboxed
 | |
|             and self.operator in eval_ctx.environment.intercepted_unops  # type: ignore
 | |
|         ):
 | |
|             raise Impossible()
 | |
|         f = _uaop_to_func[self.operator]
 | |
|         try:
 | |
|             return f(self.node.as_const(eval_ctx))
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
| 
 | |
| class Name(Expr):
 | |
|     """Looks up a name or stores a value in a name.
 | |
|     The `ctx` of the node can be one of the following values:
 | |
| 
 | |
|     -   `store`: store a value in the name
 | |
|     -   `load`: load that name
 | |
|     -   `param`: like `store` but if the name was defined as function parameter.
 | |
|     """
 | |
| 
 | |
|     fields = ("name", "ctx")
 | |
|     name: str
 | |
|     ctx: str
 | |
| 
 | |
|     def can_assign(self) -> bool:
 | |
|         return self.name not in {"true", "false", "none", "True", "False", "None"}
 | |
| 
 | |
| 
 | |
| class NSRef(Expr):
 | |
|     """Reference to a namespace value assignment"""
 | |
| 
 | |
|     fields = ("name", "attr")
 | |
|     name: str
 | |
|     attr: str
 | |
| 
 | |
|     def can_assign(self) -> bool:
 | |
|         # We don't need any special checks here; NSRef assignments have a
 | |
|         # runtime check to ensure the target is a namespace object which will
 | |
|         # have been checked already as it is created using a normal assignment
 | |
|         # which goes through a `Name` node.
 | |
|         return True
 | |
| 
 | |
| 
 | |
| class Literal(Expr):
 | |
|     """Baseclass for literals."""
 | |
| 
 | |
|     abstract = True
 | |
| 
 | |
| 
 | |
| class Const(Literal):
 | |
|     """All constant values.  The parser will return this node for simple
 | |
|     constants such as ``42`` or ``"foo"`` but it can be used to store more
 | |
|     complex values such as lists too.  Only constants with a safe
 | |
|     representation (objects where ``eval(repr(x)) == x`` is true).
 | |
|     """
 | |
| 
 | |
|     fields = ("value",)
 | |
|     value: t.Any
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         return self.value
 | |
| 
 | |
|     @classmethod
 | |
|     def from_untrusted(
 | |
|         cls,
 | |
|         value: t.Any,
 | |
|         lineno: t.Optional[int] = None,
 | |
|         environment: "t.Optional[Environment]" = None,
 | |
|     ) -> "Const":
 | |
|         """Return a const object if the value is representable as
 | |
|         constant value in the generated code, otherwise it will raise
 | |
|         an `Impossible` exception.
 | |
|         """
 | |
|         from .compiler import has_safe_repr
 | |
| 
 | |
|         if not has_safe_repr(value):
 | |
|             raise Impossible()
 | |
|         return cls(value, lineno=lineno, environment=environment)
 | |
| 
 | |
| 
 | |
| class TemplateData(Literal):
 | |
|     """A constant template string."""
 | |
| 
 | |
|     fields = ("data",)
 | |
|     data: str
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         if eval_ctx.volatile:
 | |
|             raise Impossible()
 | |
|         if eval_ctx.autoescape:
 | |
|             return Markup(self.data)
 | |
|         return self.data
 | |
| 
 | |
| 
 | |
| class Tuple(Literal):
 | |
|     """For loop unpacking and some other things like multiple arguments
 | |
|     for subscripts.  Like for :class:`Name` `ctx` specifies if the tuple
 | |
|     is used for loading the names or storing.
 | |
|     """
 | |
| 
 | |
|     fields = ("items", "ctx")
 | |
|     items: t.List[Expr]
 | |
|     ctx: str
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return tuple(x.as_const(eval_ctx) for x in self.items)
 | |
| 
 | |
|     def can_assign(self) -> bool:
 | |
|         for item in self.items:
 | |
|             if not item.can_assign():
 | |
|                 return False
 | |
|         return True
 | |
| 
 | |
| 
 | |
| class List(Literal):
 | |
|     """Any list literal such as ``[1, 2, 3]``"""
 | |
| 
 | |
|     fields = ("items",)
 | |
|     items: t.List[Expr]
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return [x.as_const(eval_ctx) for x in self.items]
 | |
| 
 | |
| 
 | |
| class Dict(Literal):
 | |
|     """Any dict literal such as ``{1: 2, 3: 4}``.  The items must be a list of
 | |
|     :class:`Pair` nodes.
 | |
|     """
 | |
| 
 | |
|     fields = ("items",)
 | |
|     items: t.List["Pair"]
 | |
| 
 | |
|     def as_const(
 | |
|         self, eval_ctx: t.Optional[EvalContext] = None
 | |
|     ) -> t.Dict[t.Any, t.Any]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return dict(x.as_const(eval_ctx) for x in self.items)
 | |
| 
 | |
| 
 | |
| class Pair(Helper):
 | |
|     """A key, value pair for dicts."""
 | |
| 
 | |
|     fields = ("key", "value")
 | |
|     key: Expr
 | |
|     value: Expr
 | |
| 
 | |
|     def as_const(
 | |
|         self, eval_ctx: t.Optional[EvalContext] = None
 | |
|     ) -> t.Tuple[t.Any, t.Any]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
 | |
| 
 | |
| 
 | |
| class Keyword(Helper):
 | |
|     """A key, value pair for keyword arguments where key is a string."""
 | |
| 
 | |
|     fields = ("key", "value")
 | |
|     key: str
 | |
|     value: Expr
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return self.key, self.value.as_const(eval_ctx)
 | |
| 
 | |
| 
 | |
| class CondExpr(Expr):
 | |
|     """A conditional expression (inline if expression).  (``{{
 | |
|     foo if bar else baz }}``)
 | |
|     """
 | |
| 
 | |
|     fields = ("test", "expr1", "expr2")
 | |
|     test: Expr
 | |
|     expr1: Expr
 | |
|     expr2: t.Optional[Expr]
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         if self.test.as_const(eval_ctx):
 | |
|             return self.expr1.as_const(eval_ctx)
 | |
| 
 | |
|         # if we evaluate to an undefined object, we better do that at runtime
 | |
|         if self.expr2 is None:
 | |
|             raise Impossible()
 | |
| 
 | |
|         return self.expr2.as_const(eval_ctx)
 | |
| 
 | |
| 
 | |
| def args_as_const(
 | |
|     node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
 | |
| ) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
 | |
|     args = [x.as_const(eval_ctx) for x in node.args]
 | |
|     kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
 | |
| 
 | |
|     if node.dyn_args is not None:
 | |
|         try:
 | |
|             args.extend(node.dyn_args.as_const(eval_ctx))
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
|     if node.dyn_kwargs is not None:
 | |
|         try:
 | |
|             kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
|     return args, kwargs
 | |
| 
 | |
| 
 | |
| class _FilterTestCommon(Expr):
 | |
|     fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
 | |
|     node: Expr
 | |
|     name: str
 | |
|     args: t.List[Expr]
 | |
|     kwargs: t.List[Pair]
 | |
|     dyn_args: t.Optional[Expr]
 | |
|     dyn_kwargs: t.Optional[Expr]
 | |
|     abstract = True
 | |
|     _is_filter = True
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         if eval_ctx.volatile:
 | |
|             raise Impossible()
 | |
| 
 | |
|         if self._is_filter:
 | |
|             env_map = eval_ctx.environment.filters
 | |
|         else:
 | |
|             env_map = eval_ctx.environment.tests
 | |
| 
 | |
|         func = env_map.get(self.name)
 | |
|         pass_arg = _PassArg.from_obj(func)  # type: ignore
 | |
| 
 | |
|         if func is None or pass_arg is _PassArg.context:
 | |
|             raise Impossible()
 | |
| 
 | |
|         if eval_ctx.environment.is_async and (
 | |
|             getattr(func, "jinja_async_variant", False) is True
 | |
|             or inspect.iscoroutinefunction(func)
 | |
|         ):
 | |
|             raise Impossible()
 | |
| 
 | |
|         args, kwargs = args_as_const(self, eval_ctx)
 | |
|         args.insert(0, self.node.as_const(eval_ctx))
 | |
| 
 | |
|         if pass_arg is _PassArg.eval_context:
 | |
|             args.insert(0, eval_ctx)
 | |
|         elif pass_arg is _PassArg.environment:
 | |
|             args.insert(0, eval_ctx.environment)
 | |
| 
 | |
|         try:
 | |
|             return func(*args, **kwargs)
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
| 
 | |
| class Filter(_FilterTestCommon):
 | |
|     """Apply a filter to an expression. ``name`` is the name of the
 | |
|     filter, the other fields are the same as :class:`Call`.
 | |
| 
 | |
|     If ``node`` is ``None``, the filter is being used in a filter block
 | |
|     and is applied to the content of the block.
 | |
|     """
 | |
| 
 | |
|     node: t.Optional[Expr]  # type: ignore
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         if self.node is None:
 | |
|             raise Impossible()
 | |
| 
 | |
|         return super().as_const(eval_ctx=eval_ctx)
 | |
| 
 | |
| 
 | |
| class Test(_FilterTestCommon):
 | |
|     """Apply a test to an expression. ``name`` is the name of the test,
 | |
|     the other field are the same as :class:`Call`.
 | |
| 
 | |
|     .. versionchanged:: 3.0
 | |
|         ``as_const`` shares the same logic for filters and tests. Tests
 | |
|         check for volatile, async, and ``@pass_context`` etc.
 | |
|         decorators.
 | |
|     """
 | |
| 
 | |
|     _is_filter = False
 | |
| 
 | |
| 
 | |
| class Call(Expr):
 | |
|     """Calls an expression.  `args` is a list of arguments, `kwargs` a list
 | |
|     of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
 | |
|     and `dyn_kwargs` has to be either `None` or a node that is used as
 | |
|     node for dynamic positional (``*args``) or keyword (``**kwargs``)
 | |
|     arguments.
 | |
|     """
 | |
| 
 | |
|     fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
 | |
|     node: Expr
 | |
|     args: t.List[Expr]
 | |
|     kwargs: t.List[Keyword]
 | |
|     dyn_args: t.Optional[Expr]
 | |
|     dyn_kwargs: t.Optional[Expr]
 | |
| 
 | |
| 
 | |
| class Getitem(Expr):
 | |
|     """Get an attribute or item from an expression and prefer the item."""
 | |
| 
 | |
|     fields = ("node", "arg", "ctx")
 | |
|     node: Expr
 | |
|     arg: Expr
 | |
|     ctx: str
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         if self.ctx != "load":
 | |
|             raise Impossible()
 | |
| 
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         try:
 | |
|             return eval_ctx.environment.getitem(
 | |
|                 self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx)
 | |
|             )
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
| 
 | |
| class Getattr(Expr):
 | |
|     """Get an attribute or item from an expression that is a ascii-only
 | |
|     bytestring and prefer the attribute.
 | |
|     """
 | |
| 
 | |
|     fields = ("node", "attr", "ctx")
 | |
|     node: Expr
 | |
|     attr: str
 | |
|     ctx: str
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         if self.ctx != "load":
 | |
|             raise Impossible()
 | |
| 
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         try:
 | |
|             return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr)
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
| 
 | |
| class Slice(Expr):
 | |
|     """Represents a slice object.  This must only be used as argument for
 | |
|     :class:`Subscript`.
 | |
|     """
 | |
| 
 | |
|     fields = ("start", "stop", "step")
 | |
|     start: t.Optional[Expr]
 | |
|     stop: t.Optional[Expr]
 | |
|     step: t.Optional[Expr]
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
| 
 | |
|         def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
 | |
|             if obj is None:
 | |
|                 return None
 | |
|             return obj.as_const(eval_ctx)
 | |
| 
 | |
|         return slice(const(self.start), const(self.stop), const(self.step))
 | |
| 
 | |
| 
 | |
| class Concat(Expr):
 | |
|     """Concatenates the list of expressions provided after converting
 | |
|     them to strings.
 | |
|     """
 | |
| 
 | |
|     fields = ("nodes",)
 | |
|     nodes: t.List[Expr]
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
 | |
| 
 | |
| 
 | |
| class Compare(Expr):
 | |
|     """Compares an expression with some other expressions.  `ops` must be a
 | |
|     list of :class:`Operand`\\s.
 | |
|     """
 | |
| 
 | |
|     fields = ("expr", "ops")
 | |
|     expr: Expr
 | |
|     ops: t.List["Operand"]
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         result = value = self.expr.as_const(eval_ctx)
 | |
| 
 | |
|         try:
 | |
|             for op in self.ops:
 | |
|                 new_value = op.expr.as_const(eval_ctx)
 | |
|                 result = _cmpop_to_func[op.op](value, new_value)
 | |
| 
 | |
|                 if not result:
 | |
|                     return False
 | |
| 
 | |
|                 value = new_value
 | |
|         except Exception as e:
 | |
|             raise Impossible() from e
 | |
| 
 | |
|         return result
 | |
| 
 | |
| 
 | |
| class Operand(Helper):
 | |
|     """Holds an operator and an expression."""
 | |
| 
 | |
|     fields = ("op", "expr")
 | |
|     op: str
 | |
|     expr: Expr
 | |
| 
 | |
| 
 | |
| class Mul(BinExpr):
 | |
|     """Multiplies the left with the right node."""
 | |
| 
 | |
|     operator = "*"
 | |
| 
 | |
| 
 | |
| class Div(BinExpr):
 | |
|     """Divides the left by the right node."""
 | |
| 
 | |
|     operator = "/"
 | |
| 
 | |
| 
 | |
| class FloorDiv(BinExpr):
 | |
|     """Divides the left by the right node and converts the
 | |
|     result into an integer by truncating.
 | |
|     """
 | |
| 
 | |
|     operator = "//"
 | |
| 
 | |
| 
 | |
| class Add(BinExpr):
 | |
|     """Add the left to the right node."""
 | |
| 
 | |
|     operator = "+"
 | |
| 
 | |
| 
 | |
| class Sub(BinExpr):
 | |
|     """Subtract the right from the left node."""
 | |
| 
 | |
|     operator = "-"
 | |
| 
 | |
| 
 | |
| class Mod(BinExpr):
 | |
|     """Left modulo right."""
 | |
| 
 | |
|     operator = "%"
 | |
| 
 | |
| 
 | |
| class Pow(BinExpr):
 | |
|     """Left to the power of right."""
 | |
| 
 | |
|     operator = "**"
 | |
| 
 | |
| 
 | |
| class And(BinExpr):
 | |
|     """Short circuited AND."""
 | |
| 
 | |
|     operator = "and"
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
 | |
| 
 | |
| 
 | |
| class Or(BinExpr):
 | |
|     """Short circuited OR."""
 | |
| 
 | |
|     operator = "or"
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
 | |
| 
 | |
| 
 | |
| class Not(UnaryExpr):
 | |
|     """Negate the expression."""
 | |
| 
 | |
|     operator = "not"
 | |
| 
 | |
| 
 | |
| class Neg(UnaryExpr):
 | |
|     """Make the expression negative."""
 | |
| 
 | |
|     operator = "-"
 | |
| 
 | |
| 
 | |
| class Pos(UnaryExpr):
 | |
|     """Make the expression positive (noop for most expressions)"""
 | |
| 
 | |
|     operator = "+"
 | |
| 
 | |
| 
 | |
| # Helpers for extensions
 | |
| 
 | |
| 
 | |
| class EnvironmentAttribute(Expr):
 | |
|     """Loads an attribute from the environment object.  This is useful for
 | |
|     extensions that want to call a callback stored on the environment.
 | |
|     """
 | |
| 
 | |
|     fields = ("name",)
 | |
|     name: str
 | |
| 
 | |
| 
 | |
| class ExtensionAttribute(Expr):
 | |
|     """Returns the attribute of an extension bound to the environment.
 | |
|     The identifier is the identifier of the :class:`Extension`.
 | |
| 
 | |
|     This node is usually constructed by calling the
 | |
|     :meth:`~jinja2.ext.Extension.attr` method on an extension.
 | |
|     """
 | |
| 
 | |
|     fields = ("identifier", "name")
 | |
|     identifier: str
 | |
|     name: str
 | |
| 
 | |
| 
 | |
| class ImportedName(Expr):
 | |
|     """If created with an import name the import name is returned on node
 | |
|     access.  For example ``ImportedName('cgi.escape')`` returns the `escape`
 | |
|     function from the cgi module on evaluation.  Imports are optimized by the
 | |
|     compiler so there is no need to assign them to local variables.
 | |
|     """
 | |
| 
 | |
|     fields = ("importname",)
 | |
|     importname: str
 | |
| 
 | |
| 
 | |
| class InternalName(Expr):
 | |
|     """An internal name in the compiler.  You cannot create these nodes
 | |
|     yourself but the parser provides a
 | |
|     :meth:`~jinja2.parser.Parser.free_identifier` method that creates
 | |
|     a new identifier for you.  This identifier is not available from the
 | |
|     template and is not treated specially by the compiler.
 | |
|     """
 | |
| 
 | |
|     fields = ("name",)
 | |
|     name: str
 | |
| 
 | |
|     def __init__(self) -> None:
 | |
|         raise TypeError(
 | |
|             "Can't create internal names.  Use the "
 | |
|             "`free_identifier` method on a parser."
 | |
|         )
 | |
| 
 | |
| 
 | |
| class MarkSafe(Expr):
 | |
|     """Mark the wrapped expression as safe (wrap it as `Markup`)."""
 | |
| 
 | |
|     fields = ("expr",)
 | |
|     expr: Expr
 | |
| 
 | |
|     def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         return Markup(self.expr.as_const(eval_ctx))
 | |
| 
 | |
| 
 | |
| class MarkSafeIfAutoescape(Expr):
 | |
|     """Mark the wrapped expression as safe (wrap it as `Markup`) but
 | |
|     only if autoescaping is active.
 | |
| 
 | |
|     .. versionadded:: 2.5
 | |
|     """
 | |
| 
 | |
|     fields = ("expr",)
 | |
|     expr: Expr
 | |
| 
 | |
|     def as_const(
 | |
|         self, eval_ctx: t.Optional[EvalContext] = None
 | |
|     ) -> t.Union[Markup, t.Any]:
 | |
|         eval_ctx = get_eval_context(self, eval_ctx)
 | |
|         if eval_ctx.volatile:
 | |
|             raise Impossible()
 | |
|         expr = self.expr.as_const(eval_ctx)
 | |
|         if eval_ctx.autoescape:
 | |
|             return Markup(expr)
 | |
|         return expr
 | |
| 
 | |
| 
 | |
| class ContextReference(Expr):
 | |
|     """Returns the current template context.  It can be used like a
 | |
|     :class:`Name` node, with a ``'load'`` ctx and will return the
 | |
|     current :class:`~jinja2.runtime.Context` object.
 | |
| 
 | |
|     Here an example that assigns the current template name to a
 | |
|     variable named `foo`::
 | |
| 
 | |
|         Assign(Name('foo', ctx='store'),
 | |
|                Getattr(ContextReference(), 'name'))
 | |
| 
 | |
|     This is basically equivalent to using the
 | |
|     :func:`~jinja2.pass_context` decorator when using the high-level
 | |
|     API, which causes a reference to the context to be passed as the
 | |
|     first argument to a function.
 | |
|     """
 | |
| 
 | |
| 
 | |
| class DerivedContextReference(Expr):
 | |
|     """Return the current template context including locals. Behaves
 | |
|     exactly like :class:`ContextReference`, but includes local
 | |
|     variables, such as from a ``for`` loop.
 | |
| 
 | |
|     .. versionadded:: 2.11
 | |
|     """
 | |
| 
 | |
| 
 | |
| class Continue(Stmt):
 | |
|     """Continue a loop."""
 | |
| 
 | |
| 
 | |
| class Break(Stmt):
 | |
|     """Break a loop."""
 | |
| 
 | |
| 
 | |
| class Scope(Stmt):
 | |
|     """An artificial scope."""
 | |
| 
 | |
|     fields = ("body",)
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class OverlayScope(Stmt):
 | |
|     """An overlay scope for extensions.  This is a largely unoptimized scope
 | |
|     that however can be used to introduce completely arbitrary variables into
 | |
|     a sub scope from a dictionary or dictionary like object.  The `context`
 | |
|     field has to evaluate to a dictionary object.
 | |
| 
 | |
|     Example usage::
 | |
| 
 | |
|         OverlayScope(context=self.call_method('get_context'),
 | |
|                      body=[...])
 | |
| 
 | |
|     .. versionadded:: 2.10
 | |
|     """
 | |
| 
 | |
|     fields = ("context", "body")
 | |
|     context: Expr
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| class EvalContextModifier(Stmt):
 | |
|     """Modifies the eval context.  For each option that should be modified,
 | |
|     a :class:`Keyword` has to be added to the :attr:`options` list.
 | |
| 
 | |
|     Example to change the `autoescape` setting::
 | |
| 
 | |
|         EvalContextModifier(options=[Keyword('autoescape', Const(True))])
 | |
|     """
 | |
| 
 | |
|     fields = ("options",)
 | |
|     options: t.List[Keyword]
 | |
| 
 | |
| 
 | |
| class ScopedEvalContextModifier(EvalContextModifier):
 | |
|     """Modifies the eval context and reverts it later.  Works exactly like
 | |
|     :class:`EvalContextModifier` but will only modify the
 | |
|     :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
 | |
|     """
 | |
| 
 | |
|     fields = ("body",)
 | |
|     body: t.List[Node]
 | |
| 
 | |
| 
 | |
| # make sure nobody creates custom nodes
 | |
| def _failing_new(*args: t.Any, **kwargs: t.Any) -> "te.NoReturn":
 | |
|     raise TypeError("can't create custom node types")
 | |
| 
 | |
| 
 | |
| NodeType.__new__ = staticmethod(_failing_new)  # type: ignore
 | |
| del _failing_new
 | 
