You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
4.2 KiB

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