11cb0ef41Sopenharmony_ci# -*- coding: utf-8 -*-
21cb0ef41Sopenharmony_ci"""
31cb0ef41Sopenharmony_ci    jinja2.meta
41cb0ef41Sopenharmony_ci    ~~~~~~~~~~~
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci    This module implements various functions that exposes information about
71cb0ef41Sopenharmony_ci    templates that might be interesting for various kinds of applications.
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ci    :copyright: (c) 2017 by the Jinja Team, see AUTHORS for more details.
101cb0ef41Sopenharmony_ci    :license: BSD, see LICENSE for more details.
111cb0ef41Sopenharmony_ci"""
121cb0ef41Sopenharmony_cifrom jinja2 import nodes
131cb0ef41Sopenharmony_cifrom jinja2.compiler import CodeGenerator
141cb0ef41Sopenharmony_cifrom jinja2._compat import string_types, iteritems
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_ciclass TrackingCodeGenerator(CodeGenerator):
181cb0ef41Sopenharmony_ci    """We abuse the code generator for introspection."""
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ci    def __init__(self, environment):
211cb0ef41Sopenharmony_ci        CodeGenerator.__init__(self, environment, '<introspection>',
221cb0ef41Sopenharmony_ci                               '<introspection>')
231cb0ef41Sopenharmony_ci        self.undeclared_identifiers = set()
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ci    def write(self, x):
261cb0ef41Sopenharmony_ci        """Don't write."""
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci    def enter_frame(self, frame):
291cb0ef41Sopenharmony_ci        """Remember all undeclared identifiers."""
301cb0ef41Sopenharmony_ci        CodeGenerator.enter_frame(self, frame)
311cb0ef41Sopenharmony_ci        for _, (action, param) in iteritems(frame.symbols.loads):
321cb0ef41Sopenharmony_ci            if action == 'resolve':
331cb0ef41Sopenharmony_ci                self.undeclared_identifiers.add(param)
341cb0ef41Sopenharmony_ci
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_cidef find_undeclared_variables(ast):
371cb0ef41Sopenharmony_ci    """Returns a set of all variables in the AST that will be looked up from
381cb0ef41Sopenharmony_ci    the context at runtime.  Because at compile time it's not known which
391cb0ef41Sopenharmony_ci    variables will be used depending on the path the execution takes at
401cb0ef41Sopenharmony_ci    runtime, all variables are returned.
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci    >>> from jinja2 import Environment, meta
431cb0ef41Sopenharmony_ci    >>> env = Environment()
441cb0ef41Sopenharmony_ci    >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
451cb0ef41Sopenharmony_ci    >>> meta.find_undeclared_variables(ast) == set(['bar'])
461cb0ef41Sopenharmony_ci    True
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci    .. admonition:: Implementation
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci       Internally the code generator is used for finding undeclared variables.
511cb0ef41Sopenharmony_ci       This is good to know because the code generator might raise a
521cb0ef41Sopenharmony_ci       :exc:`TemplateAssertionError` during compilation and as a matter of
531cb0ef41Sopenharmony_ci       fact this function can currently raise that exception as well.
541cb0ef41Sopenharmony_ci    """
551cb0ef41Sopenharmony_ci    codegen = TrackingCodeGenerator(ast.environment)
561cb0ef41Sopenharmony_ci    codegen.visit(ast)
571cb0ef41Sopenharmony_ci    return codegen.undeclared_identifiers
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci
601cb0ef41Sopenharmony_cidef find_referenced_templates(ast):
611cb0ef41Sopenharmony_ci    """Finds all the referenced templates from the AST.  This will return an
621cb0ef41Sopenharmony_ci    iterator over all the hardcoded template extensions, inclusions and
631cb0ef41Sopenharmony_ci    imports.  If dynamic inheritance or inclusion is used, `None` will be
641cb0ef41Sopenharmony_ci    yielded.
651cb0ef41Sopenharmony_ci
661cb0ef41Sopenharmony_ci    >>> from jinja2 import Environment, meta
671cb0ef41Sopenharmony_ci    >>> env = Environment()
681cb0ef41Sopenharmony_ci    >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
691cb0ef41Sopenharmony_ci    >>> list(meta.find_referenced_templates(ast))
701cb0ef41Sopenharmony_ci    ['layout.html', None]
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_ci    This function is useful for dependency tracking.  For example if you want
731cb0ef41Sopenharmony_ci    to rebuild parts of the website after a layout template has changed.
741cb0ef41Sopenharmony_ci    """
751cb0ef41Sopenharmony_ci    for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
761cb0ef41Sopenharmony_ci                              nodes.Include)):
771cb0ef41Sopenharmony_ci        if not isinstance(node.template, nodes.Const):
781cb0ef41Sopenharmony_ci            # a tuple with some non consts in there
791cb0ef41Sopenharmony_ci            if isinstance(node.template, (nodes.Tuple, nodes.List)):
801cb0ef41Sopenharmony_ci                for template_name in node.template.items:
811cb0ef41Sopenharmony_ci                    # something const, only yield the strings and ignore
821cb0ef41Sopenharmony_ci                    # non-string consts that really just make no sense
831cb0ef41Sopenharmony_ci                    if isinstance(template_name, nodes.Const):
841cb0ef41Sopenharmony_ci                        if isinstance(template_name.value, string_types):
851cb0ef41Sopenharmony_ci                            yield template_name.value
861cb0ef41Sopenharmony_ci                    # something dynamic in there
871cb0ef41Sopenharmony_ci                    else:
881cb0ef41Sopenharmony_ci                        yield None
891cb0ef41Sopenharmony_ci            # something dynamic we don't know about here
901cb0ef41Sopenharmony_ci            else:
911cb0ef41Sopenharmony_ci                yield None
921cb0ef41Sopenharmony_ci            continue
931cb0ef41Sopenharmony_ci        # constant is a basestring, direct template name
941cb0ef41Sopenharmony_ci        if isinstance(node.template.value, string_types):
951cb0ef41Sopenharmony_ci            yield node.template.value
961cb0ef41Sopenharmony_ci        # a tuple or list (latter *should* not happen) made of consts,
971cb0ef41Sopenharmony_ci        # yield the consts that are strings.  We could warn here for
981cb0ef41Sopenharmony_ci        # non string values
991cb0ef41Sopenharmony_ci        elif isinstance(node, nodes.Include) and \
1001cb0ef41Sopenharmony_ci             isinstance(node.template.value, (tuple, list)):
1011cb0ef41Sopenharmony_ci            for template_name in node.template.value:
1021cb0ef41Sopenharmony_ci                if isinstance(template_name, string_types):
1031cb0ef41Sopenharmony_ci                    yield template_name
1041cb0ef41Sopenharmony_ci        # something else we don't care about, we could warn here
1051cb0ef41Sopenharmony_ci        else:
1061cb0ef41Sopenharmony_ci            yield None
107