from threading import Lock
from jinja.parser import Parser
from jinja.translators.python import PythonTranslator, Template
-from jinja.exceptions import TemplateNotFound, TemplateSyntaxError
+from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \
+ TemplateIncludeError
from jinja.utils import CacheDict
from jinja.debugger import raise_syntax_error
raise_syntax_error(e, self.environment)
+ def get_controlled_loader(self):
+ """
+ Return a loader that runs in a controlled environment. (Keeps
+ track of templates that it loads and is not thread safe).
+ """
+ return ControlledLoader(self.environment, self.loader)
+
def _loader_missing(self, *args, **kwargs):
"""Helper method that overrides all other methods if no
loader is defined."""
return self.available
+class ControlledLoader(LoaderWrapper):
+ """
+ Used for template extending and including.
+ """
+
+ def __init__(self, environment, loader):
+ LoaderWrapper.__init__(self, environment, loader)
+ self._stack = []
+
+ def get_controlled_loader(self):
+ raise TypeError('Cannot get new controlled loader from an already '
+ 'controlled loader.')
+
+ def mark_as_processed(self):
+ """Mark the last parsed/sourced/included template as processed."""
+ if not self._stack:
+ raise RuntimeError('No template for marking found')
+ self._stack.pop()
+
+ def _controlled(method):
+ def new_method(self, name, *args, **kw):
+ if name in self._stack:
+ raise TemplateIncludeError('Circular imports/extends '
+ 'detected. %r appeared twice.' %
+ name)
+ self._stack.append(name)
+ return method(self, name, *args, **kw)
+ try:
+ new_method.__name__ = method.__name__
+ new_method.__doc__ = method.__doc__
+ except AttributeError:
+ pass
+ return new_method
+
+ get_source = _controlled(LoaderWrapper.get_source)
+ parse = _controlled(LoaderWrapper.parse)
+ load = _controlled(LoaderWrapper.load)
+ del _controlled
+
+
class BaseLoader(object):
"""
Use this class to implement loaders.
def __init__(self, environment, node, source):
self.environment = environment
+ self.loader = environment.loader.get_controlled_loader()
self.node = node
self.source = source
self.closed = False
Clean up stuff.
"""
self.closed = True
- self.handlers = self.node = self.environment = None
+ self.handlers = self.node = self.environment = self.loader = None
def translate(self):
"""
if child.__class__ not in (nodes.Text,
nodes.Block)]
- # load the template we inherit from and add not known blocks
- parent = self.environment.loader.parse(node.extends,
- node.filename)
+ # load the template we inherit from and add not known blocks.
+ # this also marks the templates on the controlled loader but
+ # are never removed. that's no problem because we don't allow
+ # parents we extend from as includes and the controlled loader
+ # is only used for this templated
+ parent = self.loader.parse(node.extends,
+ node.filename)
+
# look up all block nodes in the current template and
# add them to the override dict.
for n in get_nodes(nodes.Block, node):
result.append('%s = %r' % (file_id, filename))
result.append('debug_info = %s' % self.to_tuple(debug_mapping))
result.append('template_source = %r' % self.source)
+
return '\n'.join(result)
def handle_template_text(self, node):
"""
Include another template at the current position.
"""
- tmpl = self.environment.loader.parse(node.template,
- node.filename)
- return self.handle_node(tmpl.body)
+ tmpl = self.loader.parse(node.template,
+ node.filename)
+ try:
+ return self.handle_node(tmpl.body)
+ finally:
+ self.loader.mark_as_processed()
def handle_trans(self, node):
"""