inheritance uses a less awkward hack for contexts now and subclassing templates is possible

--HG--
branch : trunk
diff --git a/jinja2/__init__.py b/jinja2/__init__.py
index 7dbe329..8141aad 100644
--- a/jinja2/__init__.py
+++ b/jinja2/__init__.py
@@ -3,62 +3,46 @@
     jinja2
     ~~~~~~
 
-    Jinja is a `sandboxed`_ template engine written in pure Python. It
-    provides a `Django`_ like non-XML syntax and compiles templates into
-    executable python code. It's basically a combination of Django templates
-    and python code.
+    Jinja2 is a template engine written in pure Python.  It provides a
+    Django inspired non-XML syntax but supports inline expressions and
+    an optional sandboxed environment.
 
     Nutshell
     --------
 
-    Here a small example of a Jinja template::
+    Here a small example of a Jinja2 template::
 
         {% extends 'base.html' %}
         {% block title %}Memberlist{% endblock %}
         {% block content %}
           <ul>
           {% for user in users %}
-            <li><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
+            <li><a href="{{ user.url }}">{{ user.username }}</a></li>
           {% endfor %}
           </ul>
         {% endblock %}
 
-    Philosophy
-    ----------
 
-    Application logic is for the controller but don't try to make the life
-    for the template designer too hard by giving him too few functionality.
-
-    For more informations visit the new `jinja webpage`_ and `documentation`_.
-
-    Note
-    ----
-
-    This is the Jinja 1.0 release which is completely incompatible with the
-    old "pre 1.0" branch. The old branch will still receive security updates
-    and bugfixes but the 1.0 branch will be the only version that receives
-    support.
-
-    If you have an application that uses Jinja 0.9 and won't be updated in
-    the near future the best idea is to ship a Jinja 0.9 checkout together
-    with the application.
-
-    The `Jinja tip`_ is installable via `easy_install` with ``easy_install
-    Jinja==dev``.
-
-    .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
-    .. _Django: http://www.djangoproject.com/
-    .. _jinja webpage: http://jinja.pocoo.org/
-    .. _documentation: http://jinja.pocoo.org/documentation/index.html
-    .. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
-
-
-    :copyright: 2008 by Armin Ronacher.
+    :copyright: 2008 by Armin Ronacher, Christoph Hack.
     :license: BSD, see LICENSE for more details.
 """
-from jinja2.environment import Environment
+__docformat__ = 'restructuredtext en'
+try:
+    __version__ = __import__('pkg_resources') \
+        .get_distribution('Jinja2').version
+except:
+    __version__ = 'unknown'
+
+# high level interface
+from jinja2.environment import Environment, Template
+
+# loaders
 from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
-     DictLoader
+     DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader
+
+# undefined types
 from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+
+# decorators and public utilities
 from jinja2.filters import environmentfilter, contextfilter
-from jinja2.utils import Markup, escape, contextfunction
+from jinja2.utils import Markup, escape, environmentfunction, contextfunction
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 871728f..9bf1e4d 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -475,14 +475,12 @@
             self.blocks[block.name] = block
 
         # generate the root render function.
-        self.writeline('def root(globals, environment=environment'
-                       ', standalone=False):', extra=1)
-        self.indent()
-        self.writeline('context = TemplateContext(environment, globals, %r, '
-                       'blocks, standalone)' % self.name)
+        self.writeline('def root(context, environment=environment'
+                       '):', extra=1)
         if have_extends:
-            self.writeline('parent_root = None')
-        self.outdent()
+            self.indent()
+            self.writeline('parent_template = None')
+            self.outdent()
 
         # process the root
         frame = Frame()
@@ -490,7 +488,6 @@
         frame.toplevel = frame.rootlevel = True
         self.indent()
         self.pull_locals(frame, indent=False)
-        self.writeline('yield context')
         self.blockvisit(node.body, frame, indent=False)
         self.outdent()
 
@@ -498,14 +495,13 @@
         if have_extends:
             if not self.has_known_extends:
                 self.indent()
-                self.writeline('if parent_root is not None:')
+                self.writeline('if parent_template is not None:')
             self.indent()
-            self.writeline('stream = parent_root(context)')
-            self.writeline('stream.next()')
-            self.writeline('for event in stream:')
+            self.writeline('for event in parent_template.'
+                           'root_render_func(context):')
             self.indent()
             self.writeline('yield event')
-            self.outdent(1 + self.has_known_extends)
+            self.outdent(2 + (not self.has_known_extends))
 
         # at this point we now have the blocks collected and can visit them too.
         for name, block in self.blocks.iteritems():
@@ -513,7 +509,8 @@
             block_frame.inspect(block.body)
             block_frame.block = name
             block_frame.identifiers.add_special('super')
-            block_frame.name_overrides['super'] = 'context.super(%r)' % name
+            block_frame.name_overrides['super'] = 'context.super(%r, ' \
+                'block_%s)' % (name, name)
             self.writeline('def block_%s(context, environment=environment):'
                            % name, block, 1)
             self.pull_locals(block_frame)
@@ -524,9 +521,8 @@
                        extra=1)
 
         # add a function that returns the debug info
-        self.writeline('def get_debug_info():', extra=1)
-        self.indent()
-        self.writeline('return %r' % self.debug_info)
+        self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
+                                                    in self.debug_info))
 
     def visit_Block(self, node, frame):
         """Call a block and register it for the template."""
@@ -537,7 +533,7 @@
             if self.has_known_extends:
                 return
             if self.extends_so_far > 0:
-                self.writeline('if parent_root is None:')
+                self.writeline('if parent_template is None:')
                 self.indent()
                 level += 1
         self.writeline('for event in context.blocks[%r][-1](context):' % node.name)
@@ -565,7 +561,7 @@
             # time too, but i welcome it not to confuse users by throwing the
             # same error at different times just "because we can".
             if not self.has_known_extends:
-                self.writeline('if parent_root is not None:')
+                self.writeline('if parent_template is not None:')
                 self.indent()
             self.writeline('raise TemplateRuntimeError(%r)' %
                            'extended multiple times')
@@ -576,9 +572,15 @@
                 raise CompilerExit()
             self.outdent()
 
-        self.writeline('parent_root = environment.get_template(', node, 1)
+        self.writeline('parent_template = environment.get_template(', node, 1)
         self.visit(node.template, frame)
-        self.write(', %r).root_render_func' % self.name)
+        self.write(', %r)' % self.name)
+        self.writeline('for name, parent_block in parent_template.'
+                       'blocks.iteritems():')
+        self.indent()
+        self.writeline('context.blocks.setdefault(name, []).'
+                       'insert(0, parent_block)')
+        self.outdent()
 
         # if this extends statement was in the root level we can take
         # advantage of that information and simplify the generated code
@@ -601,11 +603,17 @@
             self.write(')')
             return
 
-        self.writeline('included_stream = environment.get_template(', node)
+        self.writeline('included_template = environment.get_template(', node)
         self.visit(node.template, frame)
-        self.write(').root_render_func(context, standalone=True)')
-        self.writeline('included_context = included_stream.next()')
-        self.writeline('for event in included_stream:')
+        self.write(')')
+        if frame.toplevel:
+            self.writeline('included_context = included_template.new_context('
+                           'context.get_root())')
+            self.writeline('for event in included_template.root_render_func('
+                           'included_context):')
+        else:
+            self.writeline('for event in included_template.root_render_func('
+                           'included_template.new_context(context.get_root())):')
         self.indent()
         if frame.buffer is None:
             self.writeline('yield event')
@@ -796,7 +804,7 @@
         # so that they don't appear in the output.
         outdent_later = False
         if frame.toplevel and self.extends_so_far != 0:
-            self.writeline('if parent_root is None:')
+            self.writeline('if parent_template is None:')
             self.indent()
             outdent_later = True
 
diff --git a/jinja2/debug.py b/jinja2/debug.py
index 75d70b2..d0157bb 100644
--- a/jinja2/debug.py
+++ b/jinja2/debug.py
@@ -38,7 +38,11 @@
 
     # figure the real context out
     real_locals = tb.tb_frame.f_locals.copy()
-    locals = dict(real_locals.get('context', {}))
+    ctx = real_locals.get('context')
+    if ctx:
+        locals = ctx.get_all()
+    else:
+        locals = {}
     for name, value in real_locals.iteritems():
         if name.startswith('l_'):
             locals[name[2:]] = value
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 5b705e4..9807184 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -13,12 +13,56 @@
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
-from jinja2.runtime import Undefined
+from jinja2.runtime import Undefined, TemplateContext
 from jinja2.debug import translate_exception
-from jinja2.utils import import_string
+from jinja2.utils import import_string, LRUCache
 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
+# for direct template usage we have up to ten living environments
+_spontaneous_environments = LRUCache(10)
+
+
+def _get_spontaneous_environment(*args):
+    """Return a new spontaneus environment.  A spontaneus environment is an
+    unnamed and unaccessable (in theory) environment that is used for
+    template generated from a string and not from the file system.
+    """
+    try:
+        env = _spontaneous_environments.get(args)
+    except TypeError:
+        return Environment(*args)
+    if env is not None:
+        return env
+    _spontaneous_environments[args] = env = Environment(*args)
+    return env
+
+
+def template_from_code(environment, code, globals, uptodate=None,
+                       template_class=None):
+    """Generate a new template object from code.  It's used in the
+    template constructor and the loader `load` implementation.
+    """
+    t = object.__new__(template_class or environment.template_class)
+    namespace = {
+        'environment':          environment,
+        '__jinja_template__':   t
+    }
+    exec code in namespace
+    t.environment = environment
+    t.name = namespace['name']
+    t.filename = code.co_filename
+    t.root_render_func = namespace['root']
+    t.blocks = namespace['blocks']
+    t.globals = globals
+
+    # debug and loader helpers
+    t._debug_info = namespace['debug_info']
+    t._uptodate = uptodate
+
+    return t
+
+
 class Environment(object):
     """The Jinja environment.
 
@@ -93,11 +137,14 @@
         self.comment_end_string = comment_end_string
         self.line_statement_prefix = line_statement_prefix
         self.trim_blocks = trim_blocks
+
+        # load extensions
         self.extensions = []
         for extension in extensions:
             if isinstance(extension, basestring):
                 extension = import_string(extension)
-            self.extensions.append(extension(self))
+            # extensions are instanciated early but initalized later.
+            self.extensions.append(object.__new__(extension))
 
         # runtime information
         self.undefined = undefined
@@ -108,8 +155,6 @@
         self.filters = DEFAULT_FILTERS.copy()
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
-        for extension in self.extensions:
-            extension.update_globals(self.globals)
 
         # set the loader provided
         self.loader = loader
@@ -117,6 +162,10 @@
         # create lexer
         self.lexer = Lexer(self)
 
+        # initialize extensions
+        for extension in self.extensions:
+            extension.__init__(self)
+
     def subscribe(self, obj, argument):
         """Get an item or attribute of an object."""
         try:
@@ -180,11 +229,11 @@
         globals = self.make_globals(globals)
         return self.loader.load(self, name, globals)
 
-    def from_string(self, source, globals=None):
+    def from_string(self, source, globals=None, template_class=None):
         """Load a template from a string."""
         globals = self.make_globals(globals)
-        return Template(self, self.compile(source, globals=globals),
-                        globals)
+        return template_from_code(self, self.compile(source, globals=globals),
+                                  globals, template_class)
 
     def make_globals(self, d):
         """Return a dict for the globals."""
@@ -194,24 +243,57 @@
 
 
 class Template(object):
-    """Represents a template."""
+    """The central template object.  This class represents a compiled template
+    and is used to evaluate it.
 
-    def __init__(self, environment, code, globals, uptodate=None):
-        namespace = {
-            'environment':          environment,
-            '__jinja_template__':   self
-        }
-        exec code in namespace
-        self.environment = environment
-        self.name = namespace['name']
-        self.filename = code.co_filename
-        self.root_render_func = namespace['root']
-        self.blocks = namespace['blocks']
-        self.globals = globals
+    Normally the template object is generated from an `Environment` but it
+    also has a constructor that makes it possible to create a template
+    instance directly using the constructor.  It takes the same arguments as
+    the environment constructor but it's not possible to specify a loader.
 
-        # debug and loader helpers
-        self._get_debug_info = namespace['get_debug_info']
-        self._uptodate = uptodate
+    Every template object has a few methods and members that are guaranteed
+    to exist.  However it's important that a template object should be
+    considered immutable.  Modifications on the object are not supported.
+
+    Template objects created from the constructor rather than an environment
+    do have an `environment` attribute that points to a temporary environment
+    that is probably shared with other templates created with the constructor
+    and compatible settings.
+
+    >>> template = Template('Hello {{ name }}!')
+    >>> template.render(name='John Doe')
+    u'Hello John Doe!'
+
+    >>> stream = template.stream(name='John Doe')
+    >>> stream.next()
+    u'Hello John Doe!'
+    >>> stream.next()
+    Traceback (most recent call last):
+        ...
+    StopIteration
+    """
+
+    def __new__(cls, source,
+                block_start_string='{%',
+                block_end_string='%}',
+                variable_start_string='{{',
+                variable_end_string='}}',
+                comment_start_string='{#',
+                comment_end_string='#}',
+                line_statement_prefix=None,
+                trim_blocks=False,
+                optimized=True,
+                undefined=Undefined,
+                extensions=(),
+                finalize=unicode):
+        # make sure extensions are hashable
+        extensions = tuple(extensions)
+        env = _get_spontaneous_environment(
+            block_start_string, block_end_string, variable_start_string,
+            variable_end_string, comment_start_string, comment_end_string,
+            line_statement_prefix, trim_blocks, optimized, undefined,
+            None, extensions, finalize)
+        return env.from_string(source, template_class=cls)
 
     def render(self, *args, **kwargs):
         """Render the template into a string."""
@@ -249,42 +331,53 @@
                                      'With an enabled optimizer this '
                                      'will lead to unexpected results.' %
                     (plural, ', '.join(overrides), plural or ' a', plural))
-        gen = self.root_render_func(dict(self.globals, **context))
-        # skip the first item which is a reference to the context
-        gen.next()
 
         try:
-            for event in gen:
+            for event in self.root_render_func(self.new_context(context)):
                 yield event
         except:
-            exc_info = translate_exception(sys.exc_info())
-            raise exc_info[0], exc_info[1], exc_info[2]
+            exc_type, exc_value, tb = translate_exception(sys.exc_info())
+            raise exc_type, exc_value, tb
+
+    def new_context(self, vars):
+        """Create a new template context for this template."""
+        return TemplateContext(self.environment, dict(self.globals, **vars),
+                               self.name, self.blocks)
 
     def get_corresponding_lineno(self, lineno):
         """Return the source line number of a line number in the
         generated bytecode as they are not in sync.
         """
-        for template_line, code_line in reversed(self._get_debug_info()):
+        for template_line, code_line in reversed(self.debug_info):
             if code_line <= lineno:
                 return template_line
         return 1
 
     @property
     def is_up_to_date(self):
-        """Check if the template is still up to date."""
+        """If this variable is `False` there is a newer version available."""
         if self._uptodate is None:
             return True
         return self._uptodate()
 
+    @property
+    def debug_info(self):
+        """The debug info mapping."""
+        return [tuple(map(int, x.split('='))) for x in
+                self._debug_info.split('&')]
+
     def __repr__(self):
         return '<%s %r>' % (
             self.__class__.__name__,
-            self.name
+            self.name or '<from string>'
         )
 
 
 class TemplateStream(object):
-    """Wraps a genererator for outputing template streams."""
+    """This class wraps a generator returned from `Template.generate` so that
+    it's possible to buffer multiple elements so that it's possible to return
+    them from a WSGI application which flushes after each iteration.
+    """
 
     def __init__(self, gen):
         self._gen = gen
@@ -300,31 +393,37 @@
         """Enable buffering. Buffer `size` items before yielding them."""
         if size <= 1:
             raise ValueError('buffer size too small')
-        self.buffered = True
 
-        def buffering_next():
+        def generator():
             buf = []
             c_size = 0
             push = buf.append
             next = self._gen.next
 
-            try:
-                while 1:
-                    item = next()
-                    if item:
-                        push(item)
+            while 1:
+                try:
+                    while 1:
+                        push(next())
                         c_size += 1
-                    if c_size >= size:
-                        raise StopIteration()
-            except StopIteration:
-                if not c_size:
-                    raise
-            return u''.join(buf)
+                        if c_size >= size:
+                            raise StopIteration()
+                except StopIteration:
+                    if not c_size:
+                        raise
+                yield u''.join(buf)
+                del buf[:]
+                c_size = 0
 
-        self._next = buffering_next
+        self.buffered = True
+        self._next = generator().next
 
     def __iter__(self):
         return self
 
     def next(self):
         return self._next()
+
+
+# hook in default template class.  if anyone reads this comment: ignore that
+# it's possible to use custom templates ;-)
+Environment.template_class = Template
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 947150d..acce835 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -25,10 +25,6 @@
     def __init__(self, environment):
         self.environment = environment
 
-    def update_globals(self, globals):
-        """Called to inject runtime variables into the globals."""
-        pass
-
     def parse(self, parser):
         """Called if one of the tags matched."""
 
@@ -37,6 +33,13 @@
     """An example extension that adds cacheable blocks."""
     tags = set(['cache'])
 
+    def __init__(self, environment):
+        Extension.__init__(self, environment)
+        def dummy_cache_support(name, timeout=None, caller=None):
+            if caller is not None:
+                return caller()
+        environment.globals['cache_support'] = dummy_cache_support
+
     def parse(self, parser):
         lineno = parser.stream.next().lineno
         args = [parser.parse_expression()]
diff --git a/jinja2/i18n.py b/jinja2/i18n.py
index 9125ee9..6718962 100644
--- a/jinja2/i18n.py
+++ b/jinja2/i18n.py
@@ -97,9 +97,9 @@
 class TransExtension(Extension):
     tags = set(['trans'])
 
-    def update_globals(self, globals):
-        """Inject noop translation functions."""
-        globals.update({
+    def __init__(self, environment):
+        Extension.__init__(self, environment)
+        environment.globals.update({
             '_':        lambda x: x,
             'gettext':  lambda x: x,
             'ngettext': lambda s, p, n: (s, p)[n != 1]
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
index beb9866..5576ac1 100644
--- a/jinja2/lexer.py
+++ b/jinja2/lexer.py
@@ -188,14 +188,14 @@
     """
 
     def __call__(cls, environment):
-        key = hash((environment.block_start_string,
-                    environment.block_end_string,
-                    environment.variable_start_string,
-                    environment.variable_end_string,
-                    environment.comment_start_string,
-                    environment.comment_end_string,
-                    environment.line_statement_prefix,
-                    environment.trim_blocks))
+        key = (environment.block_start_string,
+               environment.block_end_string,
+               environment.variable_start_string,
+               environment.variable_end_string,
+               environment.comment_start_string,
+               environment.comment_end_string,
+               environment.line_statement_prefix,
+               environment.trim_blocks)
 
         # use the cached lexer if possible
         if key in _lexer_cache:
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index dc3ccfb..3958169 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -10,7 +10,7 @@
 """
 from os import path
 from jinja2.exceptions import TemplateNotFound
-from jinja2.environment import Template
+from jinja2.environment import template_from_code
 from jinja2.utils import LRUCache
 
 
@@ -30,8 +30,7 @@
 
 
 class BaseLoader(object):
-    """
-    Baseclass for all loaders.  Subclass this and override `get_source` to
+    """Baseclass for all loaders.  Subclass this and override `get_source` to
     implement a custom loading mechanism.
 
     The environment provides a `get_template` method that will automatically
@@ -82,7 +81,7 @@
 
         source, filename, uptodate = self.get_source(environment, name)
         code = environment.compile(source, name, filename, globals)
-        template = Template(environment, code, globals, uptodate)
+        template = template_from_code(environment, code, globals, uptodate)
         if self.cache is not None:
             self.cache[name] = template
         return template
@@ -144,3 +143,63 @@
         if template in self.mapping:
             return self.mapping[template], None, None
         raise TemplateNotFound(template)
+
+
+class FunctionLoader(BaseLoader):
+    """A loader that is passed a function which does the loading.  The
+    function has to work like a `get_source` method but the return value for
+    not existing templates may be `None` instead of a `TemplateNotFound`
+    exception.
+    """
+
+    def __init__(self, load_func, cache_size=50, auto_reload=True):
+        BaseLoader.__init__(self, cache_size, auto_reload)
+        self.load_func = load_func
+
+    def get_source(self, environment, template):
+        rv = self.load_func(environment, template)
+        if rv is None:
+            raise TemplateNotFound(template)
+        return rv
+
+
+class PrefixLoader(BaseLoader):
+    """A loader that is passed a dict of loaders where each loader is bound
+    to a prefix.  The caching is independent of the actual loaders so the
+    per loader cache settings are ignored.  The prefix is delimited from the
+    template by a slash.
+    """
+
+    def __init__(self, mapping, delimiter='/', cache_size=50,
+                 auto_reload=True):
+        BaseLoader.__init__(self, cache_size, auto_reload)
+        self.mapping = mapping
+        self.delimiter = delimiter
+
+    def get_source(self, environment, template):
+        try:
+            prefix, template = template.split(self.delimiter, 1)
+            loader = self.mapping[prefix]
+        except (ValueError, KeyError):
+            raise TemplateNotFound(template)
+        return loader.get_source(environment, template)
+
+
+class ChoiceLoader(BaseLoader):
+    """This loader works like the `PrefixLoader` just that no prefix is
+    specified.  If a template could not be found by one loader the next one
+    is tried.  Like for the `PrefixLoader` the cache settings of the actual
+    loaders don't matter as the choice loader does the caching.
+    """
+
+    def __init__(self, loaders, cache_size=50, auto_reload=True):
+        BaseLoader.__init__(self, cache_size, auto_reload)
+        self.loaders = loaders
+
+    def get_source(self, environment, template):
+        for loader in self.loaders:
+            try:
+                return loader.get_source(environment, template)
+            except TemplateNotFound:
+                pass
+        raise TemplateNotFound(template)
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 7688f62..62686c4 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -452,11 +452,13 @@
         obj = self.node.as_const()
 
         # don't evaluate context functions
-        if type(obj) is FunctionType and \
-           getattr(obj, 'contextfunction', False):
-            raise Impossible()
-
         args = [x.as_const() for x in self.args]
+        if type(obj) is FunctionType:
+            if getattr(obj, 'contextfunction', False):
+                raise Impossible()
+            elif obj.environmentfunction:
+                args.insert(0, self.environment)
+
         kwargs = dict(x.as_const() for x in self.kwargs)
         if self.dyn_args is not None:
             try:
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 8cc1b2f..9018d52 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -8,10 +8,6 @@
     :copyright: Copyright 2008 by Armin Ronacher.
     :license: GNU GPL.
 """
-try:
-    from collections import defaultdict
-except ImportError:
-    defaultdict = None
 from types import FunctionType
 from jinja2.utils import Markup, partial
 from jinja2.exceptions import UndefinedError
@@ -21,62 +17,72 @@
            'Macro', 'IncludedTemplate', 'Markup']
 
 
-class TemplateContext(dict):
+class TemplateContext(object):
     """Holds the variables of the local template or of the global one.  It's
     not save to use this class outside of the compiled code.  For example
     update and other methods will not work as they seem (they don't update
     the exported variables for example).
     """
 
-    def __init__(self, environment, globals, name, blocks, standalone):
-        dict.__init__(self, globals)
+    def __init__(self, environment, parent, name, blocks):
+        self.parent = parent
+        self.vars = {}
         self.environment = environment
-        self.exported = set()
+        self.exported_vars = set()
         self.name = name
+
+        # bind functions to the context of environment if required
+        for name, obj in self.parent.iteritems():
+            if type(obj) is FunctionType:
+                if getattr(obj, 'contextfunction', 0):
+                    self.vars[key] = partial(obj, self)
+                elif getattr(obj, 'environmentfunction', 0):
+                    self.vars[key] = partial(obj, environment)
+
+        # create the initial mapping of blocks.  Whenever template inheritance
+        # takes place the runtime will update this mapping with the new blocks
+        # from the template.
         self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
 
-        # give all context functions the context as first argument
-        for key, value in self.iteritems():
-            if type(value) is FunctionType and \
-               getattr(value, 'contextfunction', False):
-                dict.__setitem__(self, key, partial(value, self))
-
-        # if the template is in standalone mode we don't copy the blocks over.
-        # this is used for includes for example but otherwise, if the globals
-        # are a template context, this template is participating in a template
-        # inheritance chain and we have to copy the blocks over.
-        if not standalone and isinstance(globals, TemplateContext):
-            for name, parent_blocks in globals.blocks.iteritems():
-                self.blocks.setdefault(name, []).extend(parent_blocks)
-
-    def super(self, block):
+    def super(self, name, current):
         """Render a parent block."""
-        try:
-            func = self.blocks[block][-2]
-        except LookupError:
+        last = None
+        for block in self.blocks[name]:
+            if block is current:
+                break
+            last = block
+        if last is None:
             return self.environment.undefined('there is no parent block '
                                               'called %r.' % block)
-        return SuperBlock(block, self, func)
+        return SuperBlock(block, self, last)
 
-    def __setitem__(self, key, value):
-        """If we set items to the dict we track the variables set so
-        that includes can access the exported variables."""
-        dict.__setitem__(self, key, value)
-        self.exported.add(key)
+    def update(self, mapping):
+        """Update vars from a mapping but don't export them."""
+        self.vars.update(mapping)
 
     def get_exported(self):
-        """Get a dict of all exported variables."""
-        return dict((k, self[k]) for k in self.exported)
+        """Get a new dict with the exported variables."""
+        return dict((k, self.vars[k]) for k in self.exported_vars)
 
-    # if there is a default dict, dict has a __missing__ method we can use.
-    if defaultdict is None:
-        def __getitem__(self, name):
-            if name in self:
-                return self[name]
-            return self.environment.undefined(name=name)
-    else:
-        def __missing__(self, name):
-            return self.environment.undefined(name=name)
+    def get_root(self):
+        """Return a new dict with all the non local variables."""
+        return dict(self.parent)
+
+    def get_all(self):
+        """Return a copy of the complete context as dict."""
+        return dict(self.parent, **self.vars)
+
+    def __setitem__(self, key, value):
+        self.vars[key] = value
+        self.exported_vars.add(key)
+
+    def __getitem__(self, key):
+        if key in self.vars:
+            return self.vars[key]
+        try:
+            return self.parent[key]
+        except KeyError:
+            return self.environment.undefined(name=key)
 
     def __repr__(self):
         return '<%s %s of %r>' % (
@@ -109,10 +115,9 @@
 
     def __init__(self, environment, context, template):
         template = environment.get_template(template)
-        gen = template.root_render_func(context, standalone=True)
-        context = gen.next()
+        context = template.new_context(context.get_root())
         self._name = template.name
-        self._rendered_body = u''.join(gen)
+        self._rendered_body = u''.join(template.root_render_func(context))
         self._context = context.get_exported()
 
     __getitem__ = lambda x, n: x._context[n]
@@ -257,6 +262,28 @@
         )
 
 
+def fail_with_undefined_error(self, *args, **kwargs):
+    """Regular callback function for undefined objects that raises an
+    `UndefinedError` on call.
+    """
+    if self._undefined_hint is None:
+        if self._undefined_obj is None:
+            hint = '%r is undefined' % self._undefined_name
+        elif not isinstance(self._undefined_name, basestring):
+            hint = '%r object has no element %r' % (
+                self._undefined_obj.__class__.__name__,
+                self._undefined_name
+            )
+        else:
+            hint = '%r object has no attribute %r' % (
+                self._undefined_obj.__class__.__name__,
+                self._undefined_name
+            )
+    else:
+        hint = self._undefined_hint
+    raise UndefinedError(hint)
+
+
 class Undefined(object):
     """The default undefined implementation.  This undefined implementation
     can be printed and iterated over, but every other access will raise a
@@ -268,30 +295,10 @@
         self._undefined_obj = obj
         self._undefined_name = name
 
-    def _fail_with_error(self, *args, **kwargs):
-        if self._undefined_hint is None:
-            if self._undefined_obj is None:
-                hint = '%r is undefined' % self._undefined_name
-            elif not isinstance(self._undefined_name, basestring):
-                hint = '%r object has no element %r' % (
-                    self._undefined_obj.__class__.__name__,
-                    self._undefined_name
-                )
-            else:
-                hint = '%r object has no attribute %r' % (
-                    self._undefined_obj.__class__.__name__,
-                    self._undefined_name
-                )
-        else:
-            hint = self._undefined_hint
-        raise UndefinedError(hint)
     __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
     __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
     __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
-    __getattr__ = __getitem__ = _fail_with_error
-
-    def __unicode__(self):
-        return u''
+    __getattr__ = __getitem__ = fail_with_undefined_error
 
     def __str__(self):
         return self.__unicode__().encode('utf-8')
@@ -299,6 +306,9 @@
     def __repr__(self):
         return 'Undefined'
 
+    def __unicode__(self):
+        return u''
+
     def __len__(self):
         return 0
 
@@ -325,9 +335,9 @@
 
 
 class StrictUndefined(Undefined):
-    """An undefined that barks on print and iteration as well as boolean tests.
-    In other words: you can do nothing with it except checking if it's defined
-    using the `defined` test.
+    """An undefined that barks on print and iteration as well as boolean
+    tests.  In other words: you can do nothing with it except checking if it's
+    defined using the `defined` test.
     """
 
-    __iter__ = __unicode__ = __len__ = __nonzero__ = Undefined._fail_with_error
+    __iter__ = __unicode__ = __len__ = __nonzero__ = fail_with_undefined_error
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 6e9dbc0..639cda6 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -33,6 +33,14 @@
     return f
 
 
+def environmentfunction(f):
+    """Mark a callable as environment callable.  An environment callable is
+    passed the current environment as first argument.
+    """
+    f.environmentfunction = True
+    return f
+
+
 def import_string(import_name, silent=False):
     """Imports an object based on a string.  This use useful if you want to
     use import paths as endpoints or something similar.  An import path can