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