[svn] checked in today's jinja changes. implemented template loading and inheritance

--HG--
branch : trunk
diff --git a/jinja/__init__.py b/jinja/__init__.py
index 6550d76..f65931c 100644
--- a/jinja/__init__.py
+++ b/jinja/__init__.py
@@ -7,6 +7,6 @@
     :license: BSD, see LICENSE for more details.
 """
 from jinja.environment import Environment
-from jinja.template import Template
+from jinja.loaders import FileSystemLoader
 
-__all__ = ['Environment', 'Template']
+__all__ = ['Environment', 'FileSystemLoader']
diff --git a/jinja/datastructure.py b/jinja/datastructure.py
index c5fe44d..67a4b89 100644
--- a/jinja/datastructure.py
+++ b/jinja/datastructure.py
@@ -64,6 +64,19 @@
 Undefined = UndefinedType()
 
 
+class Deferred(object):
+    """
+    Object marking an deferred value. Deferred objects are
+    objects that are called first access in the context.
+    """
+
+    def __init__(self, factory):
+        self.factory = factory
+
+    def __call__(self, context, name):
+        return self.factory(context, name)
+
+
 class Context(object):
     """
     Dict like object.
@@ -78,8 +91,8 @@
             raise TypeError('%r requires environment as first argument. '
                             'The rest of the arguments are forwarded to '
                             'the default dict constructor.')
-        self._stack = [initial, {}]
-        self.globals, self.current = self._stack
+        self._stack = [self.environment.globals, initial, {}]
+        self.globals, _, self.current = self._stack
 
     def pop(self):
         if len(self._stack) <= 2:
@@ -92,13 +105,26 @@
         self._stack.append(data or {})
         self.current = self._stack[-1]
 
+    def to_dict(self):
+        """
+        Convert the context into a dict. This skips the globals.
+        """
+        result = {}
+        for layer in self._stack[1:]:
+            for key, value in layer.iteritems():
+                result[key] = value
+        return result
+
     def __getitem__(self, name):
         # don't give access to jinja internal variables
         if name.startswith('::'):
             return Undefined
         for d in _reversed(self._stack):
             if name in d:
-                return d[name]
+                rv = d[name]
+                if isinstance(rv, Deferred):
+                    d[name] = rv = rv(self, name)
+                return rv
         return Undefined
 
     def __setitem__(self, name, value):
@@ -193,7 +219,7 @@
     def __init__(self, generator):
         self._generator = generator
         self._pushed = []
-        self.last = (0, 'initial', '')
+        self.last = (1, 'initial', '')
 
     def __iter__(self):
         return self
diff --git a/jinja/defaults.py b/jinja/defaults.py
index 9cb5291..8c7c949 100644
--- a/jinja/defaults.py
+++ b/jinja/defaults.py
@@ -10,3 +10,8 @@
 """
 from jinja.filters import FILTERS as DEFAULT_FILTERS
 from jinja.tests import TESTS as DEFAULT_TESTS
+
+
+DEFAULT_NAMESPACE = {
+    'range':                xrange
+}
diff --git a/jinja/environment.py b/jinja/environment.py
index 46cede0..92a7618 100644
--- a/jinja/environment.py
+++ b/jinja/environment.py
@@ -10,9 +10,10 @@
 """
 from jinja.lexer import Lexer
 from jinja.parser import Parser
+from jinja.loaders import LoaderWrapper
 from jinja.datastructure import Undefined
 from jinja.exceptions import FilterNotFound, TestNotFound
-from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS
+from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
 class Environment(object):
@@ -29,6 +30,7 @@
                  comment_end_string='#}',
                  template_charset='utf-8',
                  charset='utf-8',
+                 namespace=None,
                  loader=None,
                  filters=None,
                  tests=None):
@@ -45,17 +47,34 @@
         self.template_charset = template_charset
         self.charset = charset
         self.loader = loader
-        self.filters = filters or DEFAULT_FILTERS.copy()
-        self.tests = filters or DEFAULT_TESTS.copy()
+        self.filters = filters is None and DEFAULT_FILTERS.copy() or filters
+        self.tests = tests is None and DEFAULT_TESTS.copy() or tests
+
+        # global namespace
+        self.globals = namespace is None and DEFAULT_NAMESPACE.copy() \
+                       or namespace
 
         # create lexer
         self.lexer = Lexer(self)
 
+    def loader(self, value):
+        """
+        Get or set the template loader.
+        """
+        self._loader = LoaderWrapper(self, value)
+    loader = property(lambda s: s._loader, loader, loader.__doc__)
+
     def parse(self, source):
         """Function that creates a new parser and parses the source."""
         parser = Parser(self, source)
         return parser.parse_page()
 
+    def from_string(self, source):
+        """Load a template from a string."""
+        from jinja.parser import Parser
+        from jinja.translators.python import PythonTranslator
+        return PythonTranslator.process(self, Parser(self, source).parse())
+
     def to_unicode(self, value):
         """
         Convert a value to unicode with the rules defined on the environment.
diff --git a/jinja/exceptions.py b/jinja/exceptions.py
index ec052ea..d5f2af7 100644
--- a/jinja/exceptions.py
+++ b/jinja/exceptions.py
@@ -32,6 +32,15 @@
         KeyError.__init__(self, message)
 
 
+class TemplateNotFound(IOError, TemplateError):
+    """
+    Raised if a template does not exist.
+    """
+
+    def __init__(self, message):
+        IOError.__init__(self, message)
+
+
 class TemplateSyntaxError(SyntaxError, TemplateError):
     """
     Raised to tell the user that there is a problem with the template.
diff --git a/jinja/loaders.py b/jinja/loaders.py
new file mode 100644
index 0000000..d61e671
--- /dev/null
+++ b/jinja/loaders.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja.loaders
+    ~~~~~~~~~~~~~
+
+    Jinja loader classes.
+
+    :copyright: 2006 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+
+import codecs
+from os import path
+from jinja.parser import Parser
+from jinja.translators.python import PythonTranslator
+from jinja.exceptions import TemplateNotFound
+
+
+def get_template_filename(searchpath, name):
+    """
+    Return the filesystem filename wanted.
+    """
+    return path.join(searchpath, path.sep.join([p for p in name.split('/')
+                     if p and p[0] != '.']))
+
+
+class LoaderWrapper(object):
+    """
+    Wraps a loader so that it's bound to an environment.
+    """
+
+    def __init__(self, environment, loader):
+        self.environment = environment
+        self.loader = loader
+
+    def get_source(self, name, parent=None):
+        """
+        Retrieve the sourcecode of a template.
+        """
+        return self.loader.get_source(self.environment, name, parent)
+
+    def parse(self, name, parent=None):
+        """
+        Retreive a template and parse it.
+        """
+        return self.loader.parse(self.environment, name, parent)
+
+    def load(self, name, translator=PythonTranslator):
+        """
+        Translate a template and return it. This must not necesarily
+        be a template class. The javascript translator for example
+        will just output a string with the translated code.
+        """
+        return self.loader.load(self.environment, name, translator)
+
+
+class FileSystemLoader(object):
+    """
+    Loads templates from the filesystem::
+
+        from jinja import Environment, FileSystemLoader
+        e = Environment(loader=FileSystemLoader('templates/'))
+    """
+
+    def __init__(self, searchpath, use_cache=False, cache_size=40):
+        self.searchpath = searchpath
+        self.use_cache = use_cache
+        self.cache_size = cache_size
+        self.cache = {}
+
+    def get_source(self, environment, name, parent):
+        filename = get_template_filename(self.searchpath, name)
+        if path.exists(filename):
+            f = codecs.open(filename, 'r', environment.template_charset)
+            try:
+                return f.read()
+            finally:
+                f.close()
+        else:
+            raise TemplateNotFound(name)
+
+    def parse(self, environment, name, parent):
+        source = self.get_source(environment, name, parent)
+        return Parser(environment, source, name).parse()
+
+    def load(self, environment, name, translator):
+        if self.use_cache:
+            key = (name, translator)
+            if key in self.cache:
+                return self.cache[key]
+            if len(self.cache) >= self.cache_size:
+                self.cache.clear()
+        rv = translator.process(environment, self.parse(environment, name, None))
+        if self.use_cache:
+            self.cache[key] = rv
+        return rv
diff --git a/jinja/nodes.py b/jinja/nodes.py
index 2109b67..ebca43a 100644
--- a/jinja/nodes.py
+++ b/jinja/nodes.py
@@ -31,6 +31,9 @@
     jinja node.
     """
 
+    def get_items(self):
+        return []
+
     def getChildren(self):
         return self.get_items()
 
@@ -63,7 +66,7 @@
         self.lineno = lineno
         list.__init__(self, data or ())
 
-    getChildren = getChildNodes = lambda s: list(s)
+    getChildren = getChildNodes = lambda s: list(s) + s.get_items()
 
     def __repr__(self):
         return '%s(%s)' % (
@@ -74,15 +77,23 @@
 
 class Template(NodeList):
     """
-    A template.
+    Node that represents a template.
     """
 
-    def __init__(self, filename, node):
-        if node.__class__ is not NodeList:
-            node = (node,)
-        NodeList.__init__(self, 0, node)
+    def __init__(self, filename, body, blocks, extends):
+        if body.__class__ is not NodeList:
+            body = (body,)
+        NodeList.__init__(self, 0, body)
+        self.blocks = blocks
+        self.extends = extends
         set_filename(filename, self)
 
+    def get_items(self):
+        result = self.blocks.values()
+        if self.extends is not None:
+            result.append(self.extends)
+        return result
+
 
 class ForLoop(Node):
     """
@@ -190,3 +201,43 @@
             self.arguments,
             self.body
         )
+
+
+class Block(Node):
+    """
+    A node that represents a block.
+    """
+
+    def __init__(self, lineno, name, body):
+        self.lineno = lineno
+        self.name = name
+        self.body = body
+
+    def replace(self, node):
+        """
+        Replace the current data with the data of another block node.
+        """
+        assert node.__class__ is Block
+        self.__dict__.update(node.__dict__)
+
+    def get_items(self):
+        return [self.name, self.body]
+
+    def __repr__(self):
+        return 'Block(%r, %r)' % (
+            self.name,
+            self.body
+        )
+
+
+class Extends(Node):
+    """
+    A node that represents the extends tag.
+    """
+
+    def __init__(self, lineno, template):
+        self.lineno = lineno
+        self.template = template
+
+    def __repr__(self):
+        return 'Extends(%r)' % self.template
diff --git a/jinja/parser.py b/jinja/parser.py
index 49f0fa2..c4a4728 100644
--- a/jinja/parser.py
+++ b/jinja/parser.py
@@ -24,23 +24,14 @@
 switch_if = lambda p, t, d: t == 'name' and d in ('else', 'elif', 'endif')
 end_of_if = lambda p, t, d: t == 'name' and d == 'endif'
 end_of_macro = lambda p, t, d: t == 'name' and d == 'endmacro'
+end_of_block_tag = lambda p, t, d: t == 'name' and d == 'endblock'
 
 
 class Parser(object):
     """
     The template parser class.
 
-    Transforms sourcecode into an abstract syntax tree::
-
-        >>> parse("{% for item in seq|reversed %}{{ item }}{% endfor %}")
-        Document(ForLoop(AssignName('item'), Filter(Name('seq'), Name('reversed')),
-                         Print('item'), None))
-        >>> parse("{% if true %}foo{% else %}bar{% endif %}")
-        Document(IfCondition(Name('true'), Data('foo'), Data('bar')))
-        >>> parse("{% if false %}...{% elif 0 > 1 %}...{% else %}...{% endif %}")
-        Document(IfCondition(Name('false'), Data('...'),
-                             IfCondition(Compare('>', Const(0), Const(1)),
-                                         Data('...'), Data('...'))))
+    Transforms sourcecode into an abstract syntax tree.
     """
 
     def __init__(self, environment, source, filename=None):
@@ -50,14 +41,18 @@
         self.source = source
         self.filename = filename
         self.tokenstream = environment.lexer.tokenize(source)
-        self._parsed = False
+
+        self.extends = None
+        self.blocks = {}
 
         self.directives = {
             'for':          self.handle_for_directive,
             'if':           self.handle_if_directive,
             'cycle':        self.handle_cycle_directive,
             'print':        self.handle_print_directive,
-            'macro':        self.handle_macro_directive
+            'macro':        self.handle_macro_directive,
+            'block':        self.handle_block_directive,
+            'extends':      self.handle_extends_directive
         }
 
     def handle_for_directive(self, lineno, gen):
@@ -163,6 +158,43 @@
         defaults = [None] * (len(ast.argnames) - len(ast.defaults)) + ast.defaults
         return nodes.Macro(lineno, ast.name, zip(ast.argnames, defaults), body)
 
+    def handle_block_directive(self, lineno, gen):
+        """
+        Handle block directives used for inheritance.
+        """
+        tokens = list(gen)
+        if not tokens:
+            raise TemplateSyntaxError('block requires a name', lineno)
+        block_name = tokens.pop(0)
+        if block_name[1] != 'name':
+            raise TemplateSyntaxError('expected \'name\', got %r' %
+                                      block_name[1], lineno)
+        if tokens:
+            print tokens
+            raise TemplateSyntaxError('block got too many arguments, '
+                                      'requires one.', lineno)
+
+        if block_name[2] in self.blocks:
+            raise TemplateSyntaxError('block %r defined twice' %
+                                      block_name[2], lineno)
+
+        body = self.subparse(end_of_block_tag, True)
+        self.close_remaining_block()
+        rv = nodes.Block(lineno, block_name[2], body)
+        self.blocks[block_name[2]] = rv
+        return rv
+
+    def handle_extends_directive(self, lineno, gen):
+        """
+        Handle extends directive used for inheritance.
+        """
+        tokens = list(gen)
+        if len(tokens) != 1 or tokens[0][1] != 'string':
+            raise TemplateSyntaxError('extends requires a string', lineno)
+        if self.extends is not None:
+            raise TemplateSyntaxError('extends called twice', lineno)
+        self.extends = nodes.Extends(lineno, tokens[0][2][1:-1])
+
     def parse_python(self, lineno, gen, template='%s'):
         """
         Convert the passed generator into a flat string representing
@@ -179,7 +211,7 @@
         try:
             ast = parse(source, 'exec')
         except SyntaxError, e:
-            raise TemplateSyntaxError(str(e), lineno + e.lineno - 1)
+            raise TemplateSyntaxError('invalid syntax', lineno + e.lineno)
         assert len(ast.node.nodes) == 1, 'get %d nodes, 1 expected' % len(ast.node.nodes)
         result = ast.node.nodes[0]
         nodes.inc_lineno(lineno, result)
@@ -187,9 +219,10 @@
 
     def parse(self):
         """
-        Parse the template and return a Template.
+        Parse the template and return a Template node.
         """
-        return nodes.Template(self.filename, self.subparse(None))
+        body = self.subparse(None)
+        return nodes.Template(self.filename, body, self.blocks, self.extends)
 
     def subparse(self, test, drop_needle=False):
         """
@@ -247,7 +280,10 @@
                     node = self.directives[data](lineno, gen)
                 else:
                     raise TemplateSyntaxError('unknown directive %r' % data, lineno)
-                result.append(node)
+                # some tags like the extends tag do not output nodes.
+                # so just skip that.
+                if node is not None:
+                    result.append(node)
 
             # here the only token we should get is "data". all other
             # tokens just exist in block or variable sections. (if the
diff --git a/jinja/template.py b/jinja/template.py
deleted file mode 100644
index 780caaa..0000000
--- a/jinja/template.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    jinja.template
-    ~~~~~~~~~~~~~~
-
-    Template class.
-
-    :copyright: 2006 by Armin Ronacher.
-    :license: BSD, see LICENSE for more details.
-
-"""
-from jinja.nodes import Node
-from jinja.datastructure import Context
-from jinja.translators.python import parse_and_translate, translate
-
-
-def evaluate_source(source):
-    """
-    Evaluate a sourcecode and return the generate function.
-    """
-    ns = {}
-    exec source in ns
-    return ns['generate']
-
-
-class Template(object):
-    """
-    Represents a finished template.
-    """
-
-    def __init__(self, environment, source):
-        if isinstance(source, basestring):
-            self.source = parse_and_translate(environment, source)
-        elif isinstance(source, Node):
-            self.source = translate(environment, source)
-        else:
-            raise TypeError('unsupported source type %r' %
-                            source.__class__.__name__)
-        self.environment = environment
-        self.generate_func = None
-
-    def render(self, *args, **kwargs):
-        """
-        Render a template.
-        """
-        if self.generate_func is None:
-            self.generate_func = evaluate_source(self.source)
-        result = []
-        ctx = Context(self.environment, *args, **kwargs)
-        self.generate_func(ctx, result.append)
-        return u''.join(result)
diff --git a/jinja/translators/__init__.py b/jinja/translators/__init__.py
index b92b79e..f8576bc 100644
--- a/jinja/translators/__init__.py
+++ b/jinja/translators/__init__.py
@@ -9,3 +9,22 @@
     :copyright: 2006 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+
+
+class Translator(object):
+    """
+    Base class of all translators.
+    """
+
+    def process(environment, tree):
+        """
+        Process the given ast with the rules defined in
+        environment and return a translated version of it.
+        The translated object can be anything. The python
+        translator for example outputs Template instances,
+        a javascript translator would probably output strings.
+
+        This is a static function.
+        """
+        pass
+    process = staticmethod(process)
diff --git a/jinja/translators/python.py b/jinja/translators/python.py
index ed312a8..dde6364 100644
--- a/jinja/translators/python.py
+++ b/jinja/translators/python.py
@@ -11,10 +11,31 @@
 from compiler import ast
 from jinja import nodes
 from jinja.parser import Parser
+from jinja.datastructure import Context
 from jinja.exceptions import TemplateSyntaxError
+from jinja.translators import Translator
 
 
-class PythonTranslator(object):
+class Template(object):
+    """
+    Represents a finished template.
+    """
+
+    def __init__(self, environment, generate_func):
+        self.environment = environment
+        self.generate_func = generate_func
+
+    def render(self, *args, **kwargs):
+        """
+        Render a template.
+        """
+        result = []
+        ctx = Context(self.environment, *args, **kwargs)
+        self.generate_func(ctx, result.append)
+        return u''.join(result)
+
+
+class PythonTranslator(Translator):
     """
     Pass this translator a ast tree to get valid python code.
     """
@@ -40,6 +61,7 @@
             nodes.Cycle:            self.handle_cycle,
             nodes.Print:            self.handle_print,
             nodes.Macro:            self.handle_macro,
+            nodes.Block:            self.handle_block,
             # used python nodes
             ast.Name:               self.handle_name,
             ast.AssName:            self.handle_name,
@@ -78,7 +100,17 @@
                 ast.GenExpr:        'generator expressions'
             })
 
-        self.reset()
+    # -- public methods
+
+    def process(environment, node):
+        translator = PythonTranslator(environment, node)
+        source = translator.translate()
+        ns = {}
+        exec source in ns
+        return Template(environment, ns['generate'])
+    process = staticmethod(process)
+
+    # -- private methods
 
     def indent(self, text):
         """
@@ -104,10 +136,36 @@
 
     def handle_template(self, node):
         """
-        Handle a template node. Basically do nothing but calling the
-        handle_node_list function.
+        Handle the overall template node. This node is the first node and ensures
+        that we get the bootstrapping code. It also knows about inheritance
+        information. It only occours as outer node, never in the tree itself.
+
+        Nevertheless we call indent here to simplify futur changes.
         """
-        return self.handle_node_list(node)
+        # if there is a parent template we parse the parent template and
+        # update the blocks there. Once this is done we drop the current
+        # template in favor of the new one. Do that until we found the
+        # root template.
+        while node.extends is not None:
+            tmpl = self.environment.loader.parse(node.extends.template,
+                                                 node.filename)
+            for block in tmpl.blocks.itervalues():
+                if block.name in node.blocks:
+                    block.replace(node.blocks[block.name])
+            node = tmpl
+
+        lines = [self.indent(
+            'from jinja.datastructure import Undefined, LoopContext, CycleContext\n'
+            'def generate(context, write, write_var=None):\n'
+            '    environment = context.environment\n'
+            '    if write_var is None:\n'
+            '        write_var = lambda x: write(environment.finish_var(x))'
+        )]
+        write = lambda x: lines.append(self.indent(x))
+        self.indention += 1
+        lines.append(self.handle_node_list(node))
+
+        return '\n'.join(lines)
 
     def handle_template_text(self, node):
         """
@@ -252,6 +310,32 @@
 
         return '\n'.join(buf)
 
+    def handle_block(self, node):
+        """
+        Handle blocks in the sourcecode. We only use them to
+        call the current block implementation that is stored somewhere
+        else.
+        """
+        rv = self.handle_node(node.body)
+        if not rv:
+            return self.indent('# empty block from %r, line %s' % (
+                node.filename or '?',
+                node.lineno
+            ))
+
+        buf = []
+        write = lambda x: buf.append(self.indent(x))
+
+        write('# block from %r, line %s' % (
+            node.filename or '?',
+            node.lineno
+        ))
+        write('context.push()')
+        buf.append(self.handle_node(node.body))
+        write('context.pop()')
+        buf.append(self.indent('# end of block'))
+        return '\n'.join(buf)
+
     # -- python nodes
 
     def handle_name(self, node):
@@ -532,29 +616,9 @@
         return '[%s]' % ':'.join(args)
 
     def reset(self):
-        self.indention = 1
+        self.indention = 0
         self.last_cycle_id = 0
 
     def translate(self):
-        return (
-            'from jinja.datastructure import Undefined, LoopContext, CycleContext\n'
-            'def generate(context, write, write_var=None):\n'
-            '    environment = context.environment\n'
-            '    if write_var is None:\n'
-            '        write_var = lambda x: write(environment.finish_var(x))\n'
-        ) + self.handle_node(self.node)
-
-
-def translate(environment, node):
-    """
-    Do the translation to python.
-    """
-    return PythonTranslator(environment, node).translate()
-
-
-def parse_and_translate(environment, source, filename=None):
-    """
-    Parse sourcecode and translate it to python
-    """
-    node = Parser(environment, source, filename).parse()
-    return PythonTranslator(environment, node).translate()
+        self.reset()
+        return self.handle_node(self.node)
diff --git a/syntax.html b/syntax.html
deleted file mode 100644
index bc10e08..0000000
--- a/syntax.html
+++ /dev/null
@@ -1,61 +0,0 @@
-Jinja 1.0 Syntax
-================
-
-<ul>
-{% for char in my_string|upper|replace(" ", "") %}
-    <li>{{ loop.index }} - {{ char|e }}</li>
-{% endfor %}
-</ul>
-
-{{ variable|strip(" ")|escape|replace("a", "b") }}
-
-{% if item == 42 %}
-    ...
-{% endif %}
-
-{% if item|odd? %}
-    applies the odd? filter which returns true if the
-    item in is odd.
-{% endif %}
-
-{{ item|e(true) }} -- escape the variable for attributes
-
-<ul>
-{% for item in seq %}
-    <li>{{ item.current|e }}
-        {% if item.items %}<ul>{% recurse item.items %}</ul>{% endif %}
-    </li>
-{% endfor %}
-</ul>
-
-How a Filter Looks Like
-=======================
-
-def replace(search, repl):
-    def wrapped(env, value):
-        return env.to_unicode(value).replace(search, repl)
-    return wrapped
-
-
-def escape(attr=False):
-    def wrapped(env, value):
-        return cgi.escape(env.to_unicode(value), attr)
-    return wrapped
-
-
-def odd():
-    return lambda env, value: value % 2 == 1
-odd.__name__ = 'odd?'
-
-
-@stringfilter
-def replace(value, search, repl):
-    return value.replace(search, repl)
-
-
-def stringfilter(f):
-    def decorator(*args):
-        def wrapped(env, value):
-            return f(env.to_unicode(value), *args)
-        return wrapped
-    return decorator
diff --git a/syntax.txt b/syntax.txt
deleted file mode 100644
index 381ec74..0000000
--- a/syntax.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-For Loops
----------
-
-Simple Syntax::
-
-    {% for <item> in <seq> %}
-        <data>
-    {% endfor %}
-
-Item Unpacking::
-
-    {% for <item1>, <item2> in <seq> %}
-        <data>
-    {% endfor %}
-
-With else block::
-    
-    {% for <item> in <seq> %}
-        <data>
-    {% else %}
-        <data>
-    {% endfor %}
diff --git a/test.html b/test.html
deleted file mode 100644
index f846b06..0000000
--- a/test.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Frameset//EN"
-  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <title>{% block "page_title" %}Untitled{% endblock %} | My Webpage</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-    <link rel="stylesheet" href="screen.css" type="text/css" media="screen" />
-    <link rel="stylesheet" href="print.css" type="text/css" media="print" />
-    {% block html_head %}{% endblock %}
-  </head>
-  <body>
-    <div id="header">
-      <h1>My Webpage</h1>
-      <ul id="navigation">
-        {% for item in page_navigation %}
-          <li><a href="{{ item.url|e }}"{% if item.active %}
-            class="active"{% endif %}>{{ item.caption|e }}</a></li>
-        {% endfor %}
-      </ul>
-    </div>
-    <div id="body">
-      {% block body %}
-        content goes here.
-      {% endblock %}
-    </div>
-    <div id="sidebar">
-      <h2>Who is online?</h2>
-      <ul>
-      {% for user in online_users %}
-        <li class="row_{% cycle 'even', 'odd' %}"><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
-      {% endfor %}
-      </ul>
-      <h2>Sitemap</h2>
-      {% macro draw_sitemap(items) %}
-        <ul>
-        {% for item in items %}
-          <li>{{ item.name|e }}{% if item.items %}{{ draw_sitemap(item.items) }}{% endif %}</li>
-        {% endfor %}
-        </ul>
-      {% endmacro %}
-      {{ draw_sitemap(sitemap) }}
-      <!-- alternative solution -->
-      <ul>
-      {% for item in items %}
-        <li>{{ item.name|e }}{% if item.items %}<ul>{% recurse item.items %}</ul>{% endif %}</li>
-      {% endfor %}
-      </ul>
-    </div>
-    <div id="footer">
-      &copy; Copyrigh by yourself.
-    </div>
-  </body>
-</html>
diff --git a/test.py b/test.py
deleted file mode 100644
index eb079f4..0000000
--- a/test.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from jinja.environment import Environment
-
-e = Environment()
-
-def test_lexer(x):
-    for pos, token, data in e.lexer.tokenize(x):
-        print '%-8d%-30r%-40r' % (pos, token, data)
-
-
-def test_parser(x):
-    from jinja.parser import Parser
-    from jinja.translators.python import translate
-    print translate(e, Parser(e, x).parse())
-
-
-def load_template(x):
-    from jinja.template import Template
-    return Template(e, x)
diff --git a/tests/inheritance.py b/tests/inheritance.py
new file mode 100644
index 0000000..1c56168
--- /dev/null
+++ b/tests/inheritance.py
@@ -0,0 +1,7 @@
+from jinja import Environment, FileSystemLoader
+e = Environment(loader=FileSystemLoader('templates'))
+
+from jinja.parser import Parser
+from jinja.translators.python import PythonTranslator
+
+print PythonTranslator(e, e.loader.parse('index.html')).translate()
diff --git a/tests/mockup.txt b/tests/mockup.txt
deleted file mode 100644
index ff7e5b2..0000000
--- a/tests/mockup.txt
+++ /dev/null
@@ -1,153 +0,0 @@
-source index.html::
-
-    {% page extends='layout.html', defaultfilter='autoescape',
-            charset='utf-8' %}
-
-    {% def title %}Index | {{ super.title }}{% enddef %}
-    {% def body %}
-      <h1>Index</h1>
-      <p>
-        This is just a mockup template so that you can see how the new
-        jinja syntax looks like. It also shows the python output of this
-        template when compiled.
-      </p>
-      <ul>
-        {% for user in users %}
-          <li class="{% cycle 'row1', 'row2' %}">{{ user.username }}</li>
-        {% endfor %}
-      </ul>
-    {% enddef %}
-
-source layout.html::
-
-    {% page defaultfilter='autoescape', charset='utf-8' %}
-    <html>
-      <head>
-        <title>{% block title %}My Webpage{% endblock %}</title>
-      </head>
-      <body>
-        <div class="header">
-          <h1>My Webpage</h1>
-          <h2>{{ self.title }}</h2>
-        </div>
-        <div class="body">
-          {{ self.body }}
-        </div>
-        <div class="footer">
-          {% block footer %}
-            Copyright 2007 by the Pocoo Team.
-          {% endblock %}
-        </div>
-      </body>
-    </html>
-
-generated python code::
-
-    from jinja.runtime import Template, Markup
-
-    class Layer1Template(Template):
-
-        defaultfilter = 'autoescape'
-
-        def p_title(self):
-            yield Markup(u'My Webpage')
-
-        def p_footer(self):
-            yield Markup(u'\n        Copyright 2007 by the Pocoo Team.\n      ')
-
-        def p_outer_body(self):
-            yield Markup(u'<html>\n  <head>\n    <title>\n')
-            for i in self.resolve('self.title'):
-                yield i
-            yield Markup(u'</title>\n  </head>\n  <body>\n    <div class="header">\n'
-                         u'      <h1>My Webpage</h1>\n      <h2>')
-            for i in self.resolve('self.title'):
-                yield i
-            yield Markup(u'</h2>\n    </div>\n    <div class="body">\n      ')
-            for i in self.resolve('self.body'):
-                yield i
-            yield Markup(u'\n    </div>\n    <div class="footer">\n      ')
-            for i in self.resolve('self.footer'):
-                yield i
-            yield Markup(u'\n    </div>\n  </body>\n</html')
-
-    class Layer2Template(Layer1Template):
-
-        defaultfilter = 'autoescape'
-
-        def p_title(self):
-            yield Markup(u'Index | ')
-            for i in self.resolve('super.title'):
-                yield i
-
-        def p_body(self):
-            yield Markup(u'\n  <h1>Index</h1>\n  <p>\n    This is just a mockup '
-                         u'template so that you can see how the new jinja syntax '
-                         u'looks like. It also shows the python output of this '
-                         u'template when compiled.\n  </p>\n  <ul>\n    ')
-            iteration_1 = self.start_iteration(['user'], 'users')
-            for _ in iteration_1:
-                yield Markup(u'\n      <li class="')
-                for i in self.cycle(u'row1', u'row2'):
-                    yield i
-                yield Markup(u'">')
-                for i in self.resolve('user.username'):
-                    yield i
-                yield Markup(u'</li>\n      ')
-            iteration_1.close()
-
-    def get_template(context):
-        return Layer2Template(context)
-
-generated javascript code::
-
-    (function(c) {
-        var m = function(x) { return new Jinja.Markup(x) };
-
-        var l1 = function() {};
-        l1.defaultfilter = 'autoescape';
-        l1.prototype.p_title = function() {
-            this.yield(m('My Webpage'));
-        };
-        l1.prototype.p_footer = function() {
-            this.yield(m('\n        Copyright 2007 by the Pocoo Team.\n      '));
-        };
-        l1.prototype.p_outer_body = function() {
-            this.yield(m('<html>\n  <head>\n    <title>\n'));
-            this.resolve('self.title');
-            this.yield(m('</title>\n  </head>\n  <body>\n    <div class="header">\n'
-                         '      <h1>My Webpage</h1>\n      <h2>'));
-            this.resolve('self.title');
-            this.yield(m('</h2>\n    </div>\n    <div class="body">\n      '));
-            this.resolve('self.body');
-            this.yield(m('\n    </div>\n    <div class="footer">\n      '));
-            this.resolve('self.footer');
-            this.yield(m('\n    </div>\n  </body>\n</html'));
-        };
-
-        var l2 = function() {};
-        l2.defaultfilter = 'autoescape';
-        l2.prototype.p_title = function() {
-            this.yield(m('Index | '));
-            this.resolve('super.title');
-        };
-        l2.prototype.p_body = function() {
-            this.yield(m('\n  <h1>Index</h1>\n  <p>\n    This is just a mockup '
-                         'template so that you can see how the new jinja syntax '
-                         'looks like. It also shows the python output of this '
-                         'template when compiled.\n  </p>\n  <ul>\n    '));
-            var iteration_1 = this.startIteration(['user'], 'users');
-            while (iteration_1.next()) {
-                this.yield(m('\n      <li class="'));
-                this.cycle('row1', 'row2');
-                this.yield(m('">'));
-                this.resolve('user.username');
-                this.yield('</li>\n      ');
-            };
-            iteration_1.close();
-        };
-
-        return function(context) {
-            return new Jinja.Template(context, l1, l2);
-        };
-    })();
diff --git a/tests/run.py b/tests/run.py
deleted file mode 100644
index b323b64..0000000
--- a/tests/run.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from jinja import Environment, FileSystemLoader
-
-env = Environment(
-    loader=FileSystemLoader(['.'], suffix='')
-)
-env.filters['odd?'] = lambda c, x: x % 2 == 1
-env.filters['even?'] = lambda x, x: x % 2 == 0
-
-t = env.get_template('index.html')
-print t.render(
-    users=[
-        dict(username='foo', user_id=1),
-        dict(username='bar', user_id=2)
-    ]
-)
diff --git a/tests/templates/index.html b/tests/templates/index.html
new file mode 100644
index 0000000..1f8cb7a
--- /dev/null
+++ b/tests/templates/index.html
@@ -0,0 +1,5 @@
+{% extends "layout.html" %}
+{% block page_title %}Index{% endblock %}
+{% block body %}
+  This is the index page.
+{% endblock %}
diff --git a/tests/templates/layout.html b/tests/templates/layout.html
new file mode 100644
index 0000000..bf7b5f0
--- /dev/null
+++ b/tests/templates/layout.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Frameset//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+<html>
+  <head>
+    <title>{% block page_title %}Untitled{% endblock %} | My Webpage</title>
+    {% block page_head %}{% endblock %}
+  </head>
+  <body>
+    <div id="header">
+      <h1>My Webpage</h1>
+      <ul id="navigation">
+      {% for item in navigation_items %}
+        <li><a href="{{ item.url|escape }}">{{ item.caption|escape }}</a></li>
+      {% endfor %}
+      </ul>
+    </div>
+    <div id="body">
+      {% block body %}
+        content goes here.
+      {% endblock %}
+    </div>
+  </body>
+</html>