Automated merge with ssh://dev.pocoo.org/jinja2-main

--HG--
branch : trunk
diff --git a/examples/inheritance.py b/examples/inheritance.py
new file mode 100644
index 0000000..aa687c8
--- /dev/null
+++ b/examples/inheritance.py
@@ -0,0 +1,12 @@
+from jinja2 import Environment
+from jinja2.loaders import DictLoader
+
+
+env = Environment(loader=DictLoader({
+'a': '''[A[{% block body %}{% endblock %}]]''',
+'b': '''{% extends 'a' %}{% block body %}[B]{% endblock %}''',
+'c': '''{% extends 'b' %}{% block body %}###{{ super() }}###{% endblock %}'''
+}))
+
+
+print env.get_template('c').render()
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 3c2347d..4ddacd9 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -39,8 +39,7 @@
 
 def generate(node, environment, filename, stream=None):
     """Generate the python source for a node tree."""
-    is_child = node.find(nodes.Extends) is not None
-    generator = CodeGenerator(environment, is_child, filename, stream)
+    generator = CodeGenerator(environment, filename, stream)
     generator.visit(node)
     if stream is None:
         return generator.stream.getvalue()
@@ -114,16 +113,31 @@
 
     def __init__(self, parent=None):
         self.identifiers = Identifiers()
+
         # a toplevel frame is the root + soft frames such as if conditions.
         self.toplevel = False
+
         # the root frame is basically just the outermost frame, so no if
         # conditions.  This information is used to optimize inheritance
         # situations.
         self.rootlevel = False
-        self.parent = parent
+
+        # inside some tags we are using a buffer rather than yield statements.
+        # this for example affects {% filter %} or {% macro %}.  If a frame
+        # is buffered this variable points to the name of the list used as
+        # buffer.
         self.buffer = None
+
+        # if a frame has name_overrides, all read access to a name in this
+        # dict is redirected to a string expression.
         self.name_overrides = {}
+
+        # the name of the block we're in, otherwise None.
         self.block = parent and parent.block or None
+
+        # the parent of this frame
+        self.parent = parent
+
         if parent is not None:
             self.identifiers.declared.update(
                 parent.identifiers.declared |
@@ -214,33 +228,61 @@
 
 class CodeGenerator(NodeVisitor):
 
-    def __init__(self, environment, is_child, filename, stream=None):
+    def __init__(self, environment, filename, stream=None):
         if stream is None:
             stream = StringIO()
         self.environment = environment
-        self.is_child = is_child
         self.filename = filename
         self.stream = stream
+
+        # a registry for all blocks.  Because blocks are moved out
+        # into the global python scope they are registered here
         self.blocks = {}
-        self.indentation = 0
-        self.new_lines = 0
-        self.last_identifier = 0
+
+        # the number of extends statements so far
         self.extends_so_far = 0
+
+        # some templates have a rootlevel extends.  In this case we
+        # can safely assume that we're a child template and do some
+        # more optimizations.
         self.has_known_extends = False
+
+        # the number of new lines before the next write()
+        self._new_lines = 0
+
+        # the line number of the last written statement
         self._last_line = 0
+
+        # true if nothing was written so far.
         self._first_write = True
 
+        # used by the `temporary_identifier` method to get new
+        # unique, temporary identifier
+        self._last_identifier = 0
+
+        # the current indentation
+        self._indentation = 0
+
     def temporary_identifier(self):
-        self.last_identifier += 1
-        return 't%d' % self.last_identifier
+        """Get a new unique identifier."""
+        self._last_identifier += 1
+        return 't%d' % self._last_identifier
 
     def indent(self):
-        self.indentation += 1
+        """Indent by one."""
+        self._indentation += 1
 
     def outdent(self, step=1):
-        self.indentation -= step
+        """Outdent by step."""
+        self._indentation -= step
 
     def blockvisit(self, nodes, frame, indent=True, force_generator=True):
+        """Visit a list of nodes as block in a frame.  Per default the
+        code is indented, but this can be disabled by setting the indent
+        parameter to False.  If the current frame is no buffer a dummy
+        ``if 0: yield None`` is written automatically unless the
+        force_generator parameter is set to False.
+        """
         if indent:
             self.indent()
         if frame.buffer is None and force_generator:
@@ -254,26 +296,36 @@
             self.outdent()
 
     def write(self, x):
-        if self.new_lines:
+        """Write a string into the output stream."""
+        if self._new_lines:
             if not self._first_write:
-                self.stream.write('\n' * self.new_lines)
+                self.stream.write('\n' * self._new_lines)
             self._first_write = False
-            self.stream.write('    ' * self.indentation)
-            self.new_lines = 0
+            self.stream.write('    ' * self._indentation)
+            self._new_lines = 0
         self.stream.write(x)
 
     def writeline(self, x, node=None, extra=0):
+        """Combination of newline and write."""
         self.newline(node, extra)
         self.write(x)
 
     def newline(self, node=None, extra=0):
-        self.new_lines = max(self.new_lines, 1 + extra)
+        """Add one or more newlines before the next write."""
+        self._new_lines = max(self._new_lines, 1 + extra)
         if node is not None and node.lineno != self._last_line:
             self.write('# line: %s' % node.lineno)
-            self.new_lines = 1
+            self._new_lines = 1
             self._last_line = node.lineno
 
     def signature(self, node, frame, have_comma=True, extra_kwargs=None):
+        """Writes a function call to the stream for the current node.
+        Per default it will write a leading comma but this can be
+        disabled by setting have_comma to False.  If extra_kwargs is
+        given it must be a string that represents a single keyword
+        argument call that is inserted at the end of the regular
+        keyword argument calls.
+        """
         have_comma = have_comma and [True] or []
         def touch_comma():
             if have_comma:
@@ -300,6 +352,10 @@
             self.visit(node.dyn_kwargs, frame)
 
     def pull_locals(self, frame, indent=True):
+        """Pull all the references identifiers into the local scope.
+        This affects regular names, filters and tests.  If indent is
+        set to False, no automatic indentation will take place.
+        """
         if indent:
             self.indent()
         for name in frame.identifiers.undeclared:
@@ -312,6 +368,10 @@
             self.outdent()
 
     def collect_shadowed(self, frame):
+        """This function returns all the shadowed variables in a dict
+        in the form name: alias and will write the required assignments
+        into the current scope.  No indentation takes place.
+        """
         # make sure we "backup" overridden, local identifiers
         # TODO: we should probably optimize this and check if the
         # identifier is in use afterwards.
@@ -322,6 +382,17 @@
         return aliases
 
     def function_scoping(self, node, frame):
+        """In Jinja a few statements require the help of anonymous
+        functions.  Those are currently macros and call blocks and in
+        the future also recursive loops.  As there is currently
+        technical limitation that doesn't allow reading and writing a
+        variable in a scope where the initial value is coming from an
+        outer scope, this function tries to fall back with a common
+        error message.  Additionally the frame passed is modified so
+        that the argumetns are collected and callers are looked up.
+
+        This will return the modified frame.
+        """
         func_frame = frame.inner()
         func_frame.inspect(node.iter_child_nodes(), hard_scope=True)
 
@@ -420,6 +491,8 @@
             block_frame = Frame()
             block_frame.inspect(block.body)
             block_frame.block = name
+            block_frame.identifiers.add_special('super')
+            block_frame.name_overrides['super'] = 'context.super(%r)' % name
             self.writeline('def block_%s(context, environment=environment):'
                            % name, block, 1)
             self.pull_locals(block_frame)
diff --git a/jinja2/environment.py b/jinja2/environment.py
index ae3e2ec..dff1f13 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -32,6 +32,7 @@
                  comment_end_string='#}',
                  line_statement_prefix=None,
                  trim_blocks=False,
+                 optimized=True,
                  loader=None):
         """Here the possible initialization parameters:
 
@@ -52,6 +53,8 @@
         `trim_blocks`             If this is set to ``True`` the first newline
                                   after a block is removed (block, not
                                   variable tag!). Defaults to ``False``.
+        `optimized`               should the optimizer be enabled?  Default is
+                                  ``True``.
         `loader`                  the loader which should be used.
         ========================= ============================================
         """
@@ -65,6 +68,7 @@
         self.comment_end_string = comment_end_string
         self.line_statement_prefix = line_statement_prefix
         self.trim_blocks = trim_blocks
+        self.optimized = optimized
 
         # defaults
         self.filters = DEFAULT_FILTERS.copy()
@@ -101,11 +105,12 @@
         """
         return self.lexer.tokeniter(source, filename)
 
-    def compile(self, source, filename=None, raw=False):
+    def compile(self, source, filename=None, raw=False, globals=None):
         """Compile a node or source."""
         if isinstance(source, basestring):
             source = self.parse(source, filename)
-        node = optimize(source, self)
+        if self.optimized:
+            node = optimize(source, self, globals or {})
         source = generate(node, self, filename)
         if raw:
             return source
@@ -119,17 +124,25 @@
         function can be used to calculate the real filename."""
         return template
 
-    def get_template(self, name, parent=None):
+    def get_template(self, name, parent=None, globals=None):
         """Load a template."""
         if self.loader is None:
             raise TypeError('no loader for this environment specified')
         if parent is not None:
             name = self.join_path(name, parent)
-        return self.loader.load(self, name)
+        globals = self.make_globals(globals)
+        return self.loader.load(self, name, globals)
 
-    def from_string(self, source, filename='<string>'):
+    def from_string(self, source, filename='<string>', globals=None):
         """Load a template from a string."""
-        return Template(self, self.compile(source, filename))
+        globals = self.make_globals(globals)
+        return Template(self, self.compile(source, filename), globals)
+
+    def make_globals(self, d):
+        """Return a dict for the globals."""
+        if d is None:
+            return self.globals
+        return dict(self.globals, **d)
 
 
 class Template(object):
@@ -144,12 +157,33 @@
         self.name = namespace['filename']
         self.root_render_func = namespace['root']
         self.blocks = namespace['blocks']
+        self.globals = globals
 
     def render(self, *args, **kwargs):
-        return u''.join(self.stream(*args, **kwargs))
+        return u''.join(self.generate(*args, **kwargs))
 
     def stream(self, *args, **kwargs):
-        gen = self.root_render_func(dict(*args, **kwargs))
+        return TemplateStream(self.generate(*args, **kwargs))
+
+    def generate(self, *args, **kwargs):
+        # assemble the context
+        context = self.globals.copy()
+        context.update(*args, **kwargs)
+
+        # if the environment is using the optimizer locals may never
+        # override globals as optimizations might have happened
+        # depending on values of certain globals.  This assertion goes
+        # away if the python interpreter is started with -O
+        if __debug__ and self.environment.optimized:
+            overrides = set(context) & set(self.globals)
+            if overrides:
+                plural = len(overrides) != 1 and 's' or ''
+                raise AssertionError('the per template variable%s %s '
+                                     'override%s global variable%s. '
+                                     'With an enabled optimizer this '
+                                     'will lead to unexpected results.' %
+                    (plural, ', '.join(overrides), plural or ' a', plural))
+        gen = self.root_render_func(context)
         # skip the first item which is a reference to the stream
         gen.next()
         return gen
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 97e51d6..4d1c335 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -19,10 +19,10 @@
     def get_source(self, environment, template):
         raise TemplateNotFound()
 
-    def load(self, environment, template):
+    def load(self, environment, template, globals=None):
         source, filename = self.get_source(environment, template)
-        code = environment.compile(source, filename)
-        return Template(environment, code)
+        code = environment.compile(source, filename, globals=globals)
+        return Template(environment, code, globals or {})
 
 
 class FileSystemLoader(BaseLoader):
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
index 4dbd5d9..48155c5 100644
--- a/jinja2/optimizer.py
+++ b/jinja2/optimizer.py
@@ -249,8 +249,9 @@
                                               environment=self.environment)
         except nodes.Impossible:
             return node
+
     visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
     visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
     visit_Not = visit_Compare = visit_Subscript = visit_Call = \
-    visit_Filter = visit_Test = fold
+    visit_Filter = visit_Test = visit_CondExpr = fold
     del fold
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index fb2802e..9a6d9f2 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -56,8 +56,17 @@
         # 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)
+            for name, parent_blocks in globals.blocks.iteritems():
+                self.blocks.setdefault(name, []).extend(parent_blocks)
+
+    def super(self, block):
+        """Render a parent block."""
+        try:
+            func = self.blocks[block][-2]
+        except LookupError:
+            return Undefined('super', extra='there is probably no parent '
+                             'block with this name')
+        return SuperBlock(block, self, func)
 
     def __setitem__(self, key, value):
         """If we set items to the dict we track the variables set so
@@ -92,6 +101,24 @@
         )
 
 
+class SuperBlock(object):
+    """When called this renders a parent block."""
+
+    def __init__(self, name, context, render_func):
+        self.name = name
+        self._context = context
+        self._render_func = render_func
+
+    def __call__(self):
+        return TemplateData(u''.join(self._render_func(self._context)))
+
+    def __repr__(self):
+        return '<%s %r>' % (
+            self.__class__.__name__,
+            self.name
+        )
+
+
 class IncludedTemplate(object):
     """Represents an included template."""
 
@@ -256,7 +283,7 @@
             self._undefined_hint += ' (' + extra + ')'
 
     def fail(self, *args, **kwargs):
-        raise TypeError(self._undefined_hint)
+        raise NameError(self._undefined_hint)
     __getattr__ = __getitem__ = __add__ = __mul__ = __div__ = \
     __realdiv__ = __floordiv__ = __mod__ = __pos__ = __neg__ = \
     __call__ = fail