codekingpro/portable-devtools
114k
1"""Functions that expose information about templates that might be2interesting for introspection.3"""4 5import typing as t6 7from . import nodes8from .compiler import CodeGenerator9from .compiler import Frame10 11if t.TYPE_CHECKING:12 from .environment import Environment13 14 15class TrackingCodeGenerator(CodeGenerator):16 """We abuse the code generator for introspection."""17 18 def __init__(self, environment: "Environment") -> None:19 super().__init__(environment, "<introspection>", "<introspection>")20 self.undeclared_identifiers: t.Set[str] = set()21 22 def write(self, x: str) -> None:23 """Don't write."""24 25 def enter_frame(self, frame: Frame) -> None:26 """Remember all undeclared identifiers."""27 super().enter_frame(frame)28 29 for _, (action, param) in frame.symbols.loads.items():30 if action == "resolve" and param not in self.environment.globals:31 self.undeclared_identifiers.add(param)32 33 34def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:35 """Returns a set of all variables in the AST that will be looked up from36 the context at runtime. Because at compile time it's not known which37 variables will be used depending on the path the execution takes at38 runtime, all variables are returned.39 40 >>> from jinja2 import Environment, meta41 >>> env = Environment()42 >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')43 >>> meta.find_undeclared_variables(ast) == {'bar'}44 True45 46 .. admonition:: Implementation47 48 Internally the code generator is used for finding undeclared variables.49 This is good to know because the code generator might raise a50 :exc:`TemplateAssertionError` during compilation and as a matter of51 fact this function can currently raise that exception as well.52 """53 codegen = TrackingCodeGenerator(ast.environment) # type: ignore54 codegen.visit(ast)55 return codegen.undeclared_identifiers56 57 58_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)59_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]60 61 62def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:63 """Finds all the referenced templates from the AST. This will return an64 iterator over all the hardcoded template extensions, inclusions and65 imports. If dynamic inheritance or inclusion is used, `None` will be66 yielded.67 68 >>> from jinja2 import Environment, meta69 >>> env = Environment()70 >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')71 >>> list(meta.find_referenced_templates(ast))72 ['layout.html', None]73 74 This function is useful for dependency tracking. For example if you want75 to rebuild parts of the website after a layout template has changed.76 """77 template_name: t.Any78 79 for node in ast.find_all(_ref_types):80 template: nodes.Expr = node.template # type: ignore81 82 if not isinstance(template, nodes.Const):83 # a tuple with some non consts in there84 if isinstance(template, (nodes.Tuple, nodes.List)):85 for template_name in template.items:86 # something const, only yield the strings and ignore87 # non-string consts that really just make no sense88 if isinstance(template_name, nodes.Const):89 if isinstance(template_name.value, str):90 yield template_name.value91 # something dynamic in there92 else:93 yield None94 # something dynamic we don't know about here95 else:96 yield None97 continue98 # constant is a basestring, direct template name99 if isinstance(template.value, str):100 yield template.value101 # a tuple or list (latter *should* not happen) made of consts,102 # yield the consts that are strings. We could warn here for103 # non string values104 elif isinstance(node, nodes.Include) and isinstance(105 template.value, (tuple, list)106 ):107 for template_name in template.value:108 if isinstance(template_name, str):109 yield template_name110 # something else we don't care about, we could warn here111 else:112 yield None113 