Spaces:
Running
Running
"""Functions that expose information about templates that might be | |
interesting for introspection. | |
""" | |
import typing as t | |
from . import nodes | |
from .compiler import CodeGenerator | |
from .compiler import Frame | |
if t.TYPE_CHECKING: | |
from .environment import Environment | |
class TrackingCodeGenerator(CodeGenerator): | |
"""We abuse the code generator for introspection.""" | |
def __init__(self, environment: "Environment") -> None: | |
super().__init__(environment, "<introspection>", "<introspection>") | |
self.undeclared_identifiers: t.Set[str] = set() | |
def write(self, x: str) -> None: | |
"""Don't write.""" | |
def enter_frame(self, frame: Frame) -> None: | |
"""Remember all undeclared identifiers.""" | |
super().enter_frame(frame) | |
for _, (action, param) in frame.symbols.loads.items(): | |
if action == "resolve" and param not in self.environment.globals: | |
self.undeclared_identifiers.add(param) | |
def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: | |
"""Returns a set of all variables in the AST that will be looked up from | |
the context at runtime. Because at compile time it's not known which | |
variables will be used depending on the path the execution takes at | |
runtime, all variables are returned. | |
>>> from jinja2 import Environment, meta | |
>>> env = Environment() | |
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') | |
>>> meta.find_undeclared_variables(ast) == {'bar'} | |
True | |
.. admonition:: Implementation | |
Internally the code generator is used for finding undeclared variables. | |
This is good to know because the code generator might raise a | |
:exc:`TemplateAssertionError` during compilation and as a matter of | |
fact this function can currently raise that exception as well. | |
""" | |
codegen = TrackingCodeGenerator(ast.environment) # type: ignore | |
codegen.visit(ast) | |
return codegen.undeclared_identifiers | |
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include) | |
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include] | |
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]: | |
"""Finds all the referenced templates from the AST. This will return an | |
iterator over all the hardcoded template extensions, inclusions and | |
imports. If dynamic inheritance or inclusion is used, `None` will be | |
yielded. | |
>>> from jinja2 import Environment, meta | |
>>> env = Environment() | |
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') | |
>>> list(meta.find_referenced_templates(ast)) | |
['layout.html', None] | |
This function is useful for dependency tracking. For example if you want | |
to rebuild parts of the website after a layout template has changed. | |
""" | |
template_name: t.Any | |
for node in ast.find_all(_ref_types): | |
template: nodes.Expr = node.template # type: ignore | |
if not isinstance(template, nodes.Const): | |
# a tuple with some non consts in there | |
if isinstance(template, (nodes.Tuple, nodes.List)): | |
for template_name in template.items: | |
# something const, only yield the strings and ignore | |
# non-string consts that really just make no sense | |
if isinstance(template_name, nodes.Const): | |
if isinstance(template_name.value, str): | |
yield template_name.value | |
# something dynamic in there | |
else: | |
yield None | |
# something dynamic we don't know about here | |
else: | |
yield None | |
continue | |
# constant is a basestring, direct template name | |
if isinstance(template.value, str): | |
yield template.value | |
# a tuple or list (latter *should* not happen) made of consts, | |
# yield the consts that are strings. We could warn here for | |
# non string values | |
elif isinstance(node, nodes.Include) and isinstance( | |
template.value, (tuple, list) | |
): | |
for template_name in template.value: | |
if isinstance(template_name, str): | |
yield template_name | |
# something else we don't care about, we could warn here | |
else: | |
yield None | |