summaryrefslogtreecommitdiffstats
path: root/third_party/python/Jinja2/jinja2/meta.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/Jinja2/jinja2/meta.py')
-rw-r--r--third_party/python/Jinja2/jinja2/meta.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/third_party/python/Jinja2/jinja2/meta.py b/third_party/python/Jinja2/jinja2/meta.py
new file mode 100644
index 0000000000..0057d6eaba
--- /dev/null
+++ b/third_party/python/Jinja2/jinja2/meta.py
@@ -0,0 +1,111 @@
+"""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