1e31aef6aSopenharmony_ci"""Functions that expose information about templates that might be 2e31aef6aSopenharmony_ciinteresting for introspection. 3e31aef6aSopenharmony_ci""" 4e31aef6aSopenharmony_ciimport typing as t 5e31aef6aSopenharmony_ci 6e31aef6aSopenharmony_cifrom . import nodes 7e31aef6aSopenharmony_cifrom .compiler import CodeGenerator 8e31aef6aSopenharmony_cifrom .compiler import Frame 9e31aef6aSopenharmony_ci 10e31aef6aSopenharmony_ciif t.TYPE_CHECKING: 11e31aef6aSopenharmony_ci from .environment import Environment 12e31aef6aSopenharmony_ci 13e31aef6aSopenharmony_ci 14e31aef6aSopenharmony_ciclass TrackingCodeGenerator(CodeGenerator): 15e31aef6aSopenharmony_ci """We abuse the code generator for introspection.""" 16e31aef6aSopenharmony_ci 17e31aef6aSopenharmony_ci def __init__(self, environment: "Environment") -> None: 18e31aef6aSopenharmony_ci super().__init__(environment, "<introspection>", "<introspection>") 19e31aef6aSopenharmony_ci self.undeclared_identifiers: t.Set[str] = set() 20e31aef6aSopenharmony_ci 21e31aef6aSopenharmony_ci def write(self, x: str) -> None: 22e31aef6aSopenharmony_ci """Don't write.""" 23e31aef6aSopenharmony_ci 24e31aef6aSopenharmony_ci def enter_frame(self, frame: Frame) -> None: 25e31aef6aSopenharmony_ci """Remember all undeclared identifiers.""" 26e31aef6aSopenharmony_ci super().enter_frame(frame) 27e31aef6aSopenharmony_ci 28e31aef6aSopenharmony_ci for _, (action, param) in frame.symbols.loads.items(): 29e31aef6aSopenharmony_ci if action == "resolve" and param not in self.environment.globals: 30e31aef6aSopenharmony_ci self.undeclared_identifiers.add(param) 31e31aef6aSopenharmony_ci 32e31aef6aSopenharmony_ci 33e31aef6aSopenharmony_cidef find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: 34e31aef6aSopenharmony_ci """Returns a set of all variables in the AST that will be looked up from 35e31aef6aSopenharmony_ci the context at runtime. Because at compile time it's not known which 36e31aef6aSopenharmony_ci variables will be used depending on the path the execution takes at 37e31aef6aSopenharmony_ci runtime, all variables are returned. 38e31aef6aSopenharmony_ci 39e31aef6aSopenharmony_ci >>> from jinja2 import Environment, meta 40e31aef6aSopenharmony_ci >>> env = Environment() 41e31aef6aSopenharmony_ci >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') 42e31aef6aSopenharmony_ci >>> meta.find_undeclared_variables(ast) == {'bar'} 43e31aef6aSopenharmony_ci True 44e31aef6aSopenharmony_ci 45e31aef6aSopenharmony_ci .. admonition:: Implementation 46e31aef6aSopenharmony_ci 47e31aef6aSopenharmony_ci Internally the code generator is used for finding undeclared variables. 48e31aef6aSopenharmony_ci This is good to know because the code generator might raise a 49e31aef6aSopenharmony_ci :exc:`TemplateAssertionError` during compilation and as a matter of 50e31aef6aSopenharmony_ci fact this function can currently raise that exception as well. 51e31aef6aSopenharmony_ci """ 52e31aef6aSopenharmony_ci codegen = TrackingCodeGenerator(ast.environment) # type: ignore 53e31aef6aSopenharmony_ci codegen.visit(ast) 54e31aef6aSopenharmony_ci return codegen.undeclared_identifiers 55e31aef6aSopenharmony_ci 56e31aef6aSopenharmony_ci 57e31aef6aSopenharmony_ci_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include) 58e31aef6aSopenharmony_ci_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include] 59e31aef6aSopenharmony_ci 60e31aef6aSopenharmony_ci 61e31aef6aSopenharmony_cidef find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]: 62e31aef6aSopenharmony_ci """Finds all the referenced templates from the AST. This will return an 63e31aef6aSopenharmony_ci iterator over all the hardcoded template extensions, inclusions and 64e31aef6aSopenharmony_ci imports. If dynamic inheritance or inclusion is used, `None` will be 65e31aef6aSopenharmony_ci yielded. 66e31aef6aSopenharmony_ci 67e31aef6aSopenharmony_ci >>> from jinja2 import Environment, meta 68e31aef6aSopenharmony_ci >>> env = Environment() 69e31aef6aSopenharmony_ci >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') 70e31aef6aSopenharmony_ci >>> list(meta.find_referenced_templates(ast)) 71e31aef6aSopenharmony_ci ['layout.html', None] 72e31aef6aSopenharmony_ci 73e31aef6aSopenharmony_ci This function is useful for dependency tracking. For example if you want 74e31aef6aSopenharmony_ci to rebuild parts of the website after a layout template has changed. 75e31aef6aSopenharmony_ci """ 76e31aef6aSopenharmony_ci template_name: t.Any 77e31aef6aSopenharmony_ci 78e31aef6aSopenharmony_ci for node in ast.find_all(_ref_types): 79e31aef6aSopenharmony_ci template: nodes.Expr = node.template # type: ignore 80e31aef6aSopenharmony_ci 81e31aef6aSopenharmony_ci if not isinstance(template, nodes.Const): 82e31aef6aSopenharmony_ci # a tuple with some non consts in there 83e31aef6aSopenharmony_ci if isinstance(template, (nodes.Tuple, nodes.List)): 84e31aef6aSopenharmony_ci for template_name in template.items: 85e31aef6aSopenharmony_ci # something const, only yield the strings and ignore 86e31aef6aSopenharmony_ci # non-string consts that really just make no sense 87e31aef6aSopenharmony_ci if isinstance(template_name, nodes.Const): 88e31aef6aSopenharmony_ci if isinstance(template_name.value, str): 89e31aef6aSopenharmony_ci yield template_name.value 90e31aef6aSopenharmony_ci # something dynamic in there 91e31aef6aSopenharmony_ci else: 92e31aef6aSopenharmony_ci yield None 93e31aef6aSopenharmony_ci # something dynamic we don't know about here 94e31aef6aSopenharmony_ci else: 95e31aef6aSopenharmony_ci yield None 96e31aef6aSopenharmony_ci continue 97e31aef6aSopenharmony_ci # constant is a basestring, direct template name 98e31aef6aSopenharmony_ci if isinstance(template.value, str): 99e31aef6aSopenharmony_ci yield template.value 100e31aef6aSopenharmony_ci # a tuple or list (latter *should* not happen) made of consts, 101e31aef6aSopenharmony_ci # yield the consts that are strings. We could warn here for 102e31aef6aSopenharmony_ci # non string values 103e31aef6aSopenharmony_ci elif isinstance(node, nodes.Include) and isinstance( 104e31aef6aSopenharmony_ci template.value, (tuple, list) 105e31aef6aSopenharmony_ci ): 106e31aef6aSopenharmony_ci for template_name in template.value: 107e31aef6aSopenharmony_ci if isinstance(template_name, str): 108e31aef6aSopenharmony_ci yield template_name 109e31aef6aSopenharmony_ci # something else we don't care about, we could warn here 110e31aef6aSopenharmony_ci else: 111e31aef6aSopenharmony_ci yield None 112