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