work on tha runtime

--HG--
branch : trunk
diff --git a/jdebug.py b/jdebug.py
deleted file mode 100644
index f5463a6..0000000
--- a/jdebug.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    jdebug
-    ~~~~~~
-
-    Helper module to simplify jinja debugging. Use
-
-    :copyright: 2006 by Armin Ronacher.
-    :license: BSD, see LICENSE for more details.
-"""
-import os
-import sys
-import gc
-from jinja import Environment
-from jinja.parser import Parser
-from jinja.lexer import Lexer
-from jinja.translators.python import PythonTranslator
-
-
-__all__ = ['e', 't', 'p', 'l', 'm']
-
-
-_global_frame = sys._getframe()
-e = Environment()
-t = e.from_string
-
-
-def p(x=None, f=None):
-    if x is None and f is not None:
-        x = e.loader.get_source(f)
-    print PythonTranslator(e, Parser(e, x, f).parse(), None).translate()
-
-def l(x):
-    for item in e.lexer.tokenize(x):
-        print '%5s  %-20s  %r' % (item.lineno,
-                                  item.type,
-                                  item.value)
-
-class MemoryGuard(object):
-
-    def __init__(self):
-        self.guarded_objects = {}
-        self.clear = self.guarded_objects.clear
-
-    def freeze(self):
-        self.clear()
-        for obj in gc.get_objects():
-            self.guarded_objects[id(obj)] = True
-
-    def get_delta(self):
-        frm = sys._getframe()
-        result = []
-        for obj in gc.get_objects():
-            if id(obj) not in self.guarded_objects and \
-               obj is not frm and obj is not result:
-                result.append(obj)
-        return result
-
-
-m = MemoryGuard()
-
-
-if __name__ == '__main__':
-    if len(sys.argv) > 1:
-        from jinja import FileSystemLoader
-        e.loader = FileSystemLoader(sys.argv[1])
-    if len(sys.argv) > 2:
-        p(f=sys.argv[2])
-    else:
-        p(sys.stdin.read())
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 66af667..6175916 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -30,6 +30,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.visit(node)
@@ -341,14 +342,14 @@
 
     def visit_Block(self, node, frame):
         """Call a block and register it for the template."""
-        # if we know that we are a child template, there is no need to
-        # check if we are one
-        if self.has_known_extends and frame.toplevel:
-            return
         if frame.toplevel:
+            # if we know that we are a child template, there is no need to
+            # check if we are one
+            if self.has_known_extends:
+                return
             self.writeline('if parent_root is None:')
             self.indent()
-        self.writeline('for event in context.blocks[-1][%r](context):' % node.name)
+        self.writeline('for event in context.blocks[0][%r](context):' % node.name)
         self.indent()
         self.writeline('yield event')
         self.outdent(1 + frame.toplevel)
@@ -383,7 +384,7 @@
 
         self.writeline('parent_root = extends(', node, 1)
         self.visit(node.template, frame)
-        self.write(', context)')
+        self.write(', context, environment)')
 
         # if this extends statement was in the root level we can take
         # advantage of that information and simplify the generated code
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 8ba4fce..a350620 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -10,6 +10,8 @@
 """
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
+from jinja2.optimizer import optimize
+from jinja2.compiler import generate
 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
@@ -29,7 +31,8 @@
                  comment_start_string='{#',
                  comment_end_string='#}',
                  trim_blocks=False,
-                 template_charset='utf-8'):
+                 template_charset='utf-8',
+                 loader=None):
         """Here the possible initialization parameters:
 
         ========================= ============================================
@@ -47,6 +50,7 @@
                                   after a block is removed (block, not
                                   variable tag!). Defaults to ``False``.
         `template_charset`        the charset of the templates.
+        `loader`                  the loader which should be used.
         ========================= ============================================
         """
 
@@ -71,6 +75,9 @@
         if not hasattr(self, 'finalize'):
             self.finalize = unicode
 
+        # set the loader provided
+        self.loader = loader
+
         # create lexer
         self.lexer = Lexer(self)
 
@@ -91,3 +98,47 @@
         The tuples are returned in the form ``(lineno, token, value)``.
         """
         return self.lexer.tokeniter(source, filename)
+
+    def compile(self, source, filename=None, raw=False):
+        """Compile a node or source."""
+        if isinstance(source, basestring):
+            source = self.parse(source, filename)
+        node = optimize(source, self)
+        source = generate(node, self)
+        if raw:
+            return source
+        if isinstance(filename, unicode):
+            filename = filename.encode('utf-8')
+        return compile(source, filename, 'exec')
+
+    def join_path(self, template, parent):
+        """Join a template with the parent.  By default all the lookups are
+        relative to the loader root, but if the paths should be relative this
+        function can be used to calculate the real filename."""
+        return template
+
+    def get_template(self, name, parent=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)
+
+
+class Template(object):
+    """Represents a template."""
+
+    def __init__(self, environment, code):
+        namespace = {'environment': environment}
+        exec code in namespace
+        self.environment = environment
+        self.root_render_func = namespace['root']
+        self.blocks = namespace['blocks']
+
+    def render(self, *args, **kwargs):
+        return u''.join(self.stream(*args, **kwargs))
+
+    def stream(self, *args, **kwargs):
+        context = dict(*args, **kwargs)
+        return self.root_render_func(context)
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
index 6e9fc89..b19e4ca 100644
--- a/jinja2/lexer.py
+++ b/jinja2/lexer.py
@@ -21,9 +21,6 @@
 from weakref import WeakValueDictionary
 
 
-__all__ = ['Lexer', 'Failure', 'keywords']
-
-
 # cache for the lexers. Exists in order to be able to have multiple
 # environments with the same lexer
 _lexer_cache = WeakValueDictionary()
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 40bc1d7..796b49f 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -1,10 +1,47 @@
 # -*- coding: utf-8 -*-
 """
-    jinja.loaders
-    ~~~~~~~~~~~~~
+    jinja2.loaders
+    ~~~~~~~~~~~~~~
 
     Jinja loader classes.
 
-    :copyright: 2007 by Armin Ronacher, Bryan McLemore.
+    :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+from os import path
+from jinja2.exceptions import TemplateNotFound
+from jinja2.environment import Template
+
+
+class BaseLoader(object):
+
+    def get_source(self, environment, template):
+        raise TemplateNotFound()
+
+    def load(self, environment, template):
+        source, filename = self.get_source(environment, template)
+        code = environment.compile(source, filename)
+        return Template(environment, code)
+
+
+class FileSystemLoader(BaseLoader):
+
+    def __init__(self, path, encoding='utf-8'):
+        self.path = path
+        self.encoding = encoding
+
+    def get_source(self, environment, template):
+        pieces = []
+        for piece in template.split('/'):
+            if piece == '..':
+                raise TemplateNotFound()
+            elif piece != '.':
+                pieces.append(piece)
+        filename = path.join(self.path, *pieces)
+        if not path.isfile(filename):
+            raise TemplateNotFound(template)
+        f = file(filename)
+        try:
+            return f.read().decode(self.encoding)
+        finally:
+            f.close()
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
index 167f6eb..bd97fa0 100644
--- a/jinja2/optimizer.py
+++ b/jinja2/optimizer.py
@@ -24,6 +24,13 @@
 from jinja2.runtime import subscribe, LoopContext
 
 
+def optimize(node, environment, context_hint=None):
+    """The context hint can be used to perform an static optimization
+    based on the context given."""
+    optimizer = Optimizer(environment)
+    return optimizer.visit(node, ContextStack(context_hint))
+
+
 class ContextStack(object):
     """Simple compile time context implementation."""
     undefined = object()
@@ -206,10 +213,3 @@
     visit_Not = visit_Compare = visit_Subscript = visit_Call = \
     visit_Filter = visit_Test = fold
     del fold
-
-
-def optimize(node, environment, context_hint=None):
-    """The context hint can be used to perform an static optimization
-    based on the context given."""
-    optimizer = Optimizer(environment)
-    return optimizer.visit(node, ContextStack(context_hint))
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 74cc421..8dc5d58 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -12,8 +12,6 @@
 from jinja2.exceptions import TemplateSyntaxError
 
 
-__all__ = ['Parser']
-
 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
                                  'macro', 'include'])
 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 097b9e4..82acd6c 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -18,9 +18,12 @@
            'TemplateContext', 'Macro', 'Undefined']
 
 
-def extends(template, context):
+def extends(template_name, context, environment):
     """This loads a template (and evaluates it) and replaces the blocks."""
-    context.stream_muted = True
+    template = environment.get_template(template_name, context.filename)
+    for name, block in template.blocks.iteritems():
+        context.blocks.setdefault(name, []).append(block)
+    return template.root_render_func
 
 
 def subscribe(obj, argument):
diff --git a/test.py b/test.py
deleted file mode 100644
index 4b0b218..0000000
--- a/test.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import sys
-from jinja2 import Environment
-from jinja2.compiler import generate
-
-
-env = Environment()
-ast = env.parse("""
-{% extends master_layout or "foo.html" %}
-
-{% baz = [1, 2, 3, 4] %}
-{% macro foo() %}
-    blah
-{% endmacro %}
-{% blah = 42 %}
-
-{% block body %}
-    Das ist ein Test
-{% endblock %}
-""")
-source = generate(ast, env, "foo.html")
-print source
diff --git a/test_optimizer.py b/test_optimizer.py
deleted file mode 100644
index d0d8551..0000000
--- a/test_optimizer.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from jinja2 import Environment
-from jinja2.compiler import generate
-from jinja2.optimizer import optimize
-
-
-env = Environment()
-forums = [
-    {'id': 1, 'name': u'Example'},
-    {'id': 2, 'name': u'Foobar'},
-    {'id': 3, 'name': u'<42>'}
-]
-ast = env.parse("""
-    Hi {{ "<blub>"|e }},
-    how are you?
-
-    {% for forum in forums %}
-        {{ readstatus(forum.id) }} {{ forum.id|e }} {{ forum.name|e }}
-    {% endfor %}
-
-    {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar'), ('#baz', 42 * 2 + 23)] %}
-    <ul>
-    {% for key, value in navigation %}
-        <li>{{ loop.index }}: <a href="{{ key[1:].upper()|e }}">{{ value|e }}</a></li>
-    {% endfor %}
-    </ul>
-""")
-print ast
-print
-print generate(ast, env, "foo.html")
-print
-ast = optimize(ast, env, context_hint={'forums': forums})
-print ast
-print
-print generate(ast, env, "foo.html")