this fixes #277.
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 10 Nov 2007 23:35:36 +0000 (00:35 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 10 Nov 2007 23:35:36 +0000 (00:35 +0100)
--HG--
branch : trunk

jinja/exceptions.py
jinja/loaders.py
jinja/translators/python.py

index 1e7c632bde0cbbfe643e76356690b7698d2e7ff7..d24dc9240d6d08ee866fd4e7bf189eea73deef84 100644 (file)
@@ -82,3 +82,10 @@ class TemplateRuntimeError(TemplateError):
     Raised by the template engine if a tag encountered an error when
     rendering.
     """
+
+
+class TemplateIncludeError(TemplateError):
+    """
+    Raised by the `ControlledLoader` if recursive includes where
+    detected.
+    """
index 24eaa470e7e7875c96087abbaf2c0318ad219a05..ffb4789554e8ac1a0cf1cddf09198be83c059ab1 100644 (file)
@@ -19,7 +19,8 @@ from os import path
 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
 
 
@@ -100,6 +101,13 @@ class LoaderWrapper(object):
             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."""
@@ -109,6 +117,46 @@ class LoaderWrapper(object):
         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.
index c367d4c4c033b1d417571880b322dd77dc443b8d..d7c77e3601cbbc56f64ff454bbc84a12123ff129 100644 (file)
@@ -144,6 +144,7 @@ class PythonTranslator(Translator):
 
     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
@@ -278,7 +279,7 @@ class PythonTranslator(Translator):
         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):
         """
@@ -325,9 +326,14 @@ class PythonTranslator(Translator):
                              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):
@@ -492,6 +498,7 @@ class PythonTranslator(Translator):
             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):
@@ -834,9 +841,12 @@ class PythonTranslator(Translator):
         """
         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):
         """