added a :class:`ModuleLoader` that can load templates from
precompiled sources.  The environment now features a method
to compile the templates from a configured loader into a zip
file or folder.

--HG--
branch : trunk
extra : rebase_source : 4824f663e4ff58ca3d2176c65fc1b7494e1f0c43
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 6404ed0..742bc1c 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -415,7 +415,8 @@
         return stream
 
     @internalcode
-    def compile(self, source, name=None, filename=None, raw=False):
+    def compile(self, source, name=None, filename=None, raw=False,
+                defer_init=False):
         """Compile a node or template source code.  The `name` parameter is
         the load name of the template after it was joined using
         :meth:`join_path` if necessary, not the filename on the file system.
@@ -427,6 +428,13 @@
         parameter is `True` the return value will be a string with python
         code equivalent to the bytecode returned otherwise.  This method is
         mainly used internally.
+
+        `defer_init` is use internally to aid the module code generator.  This
+        causes the generated code to be able to import without the global
+        environment variable to be set.
+
+        .. versionadded:: 2.4
+           `defer_init` parameter added.
         """
         source_hint = None
         try:
@@ -435,7 +443,8 @@
                 source = self._parse(source, name, filename)
             if self.optimized:
                 source = optimize(source, self)
-            source = generate(source, self, name, filename)
+            source = generate(source, self, name, filename,
+                              defer_init=defer_init)
             if raw:
                 return source
             if filename is None:
@@ -491,6 +500,82 @@
         template = self.from_string(nodes.Template(body, lineno=1))
         return TemplateExpression(template, undefined_to_none)
 
+    def compile_templates(self, target, extensions=None, filter_func=None,
+                          zip=True, log_function=None):
+        """Compiles all the templates the loader can find, compiles them
+        and stores them in `target`.  If `zip` is true, a zipfile will be
+        written, otherwise the templates are stored in a directory.
+
+        `extensions` and `filter_func` are passed to :meth:`list_templates`.
+        Each template returned will be compiled to the target folder or
+        zipfile.
+
+        .. versionadded:: 2.4
+        """
+        from jinja2.loaders import ModuleLoader
+        if log_function is None:
+            log_function = lambda x: None
+
+        if zip:
+            from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
+            f = ZipFile(target, 'w', ZIP_DEFLATED)
+            log_function('Compiling into Zip archive "%s"' % target)
+        else:
+            if not os.path.isdir(target):
+                os.makedirs(target)
+            log_function('Compiling into folder "%s"' % target)
+
+        try:
+            for name in self.list_templates(extensions, filter_func):
+                source, filename, _ = self.loader.get_source(self, name)
+                try:
+                    code = self.compile(source, name, filename, True, True)
+                except TemplateSyntaxError, e:
+                    log_function('Could not compile "%s": %s' % (name, e))
+                    continue
+                module = ModuleLoader.get_module_filename(name)
+                if zip:
+                    info = ZipInfo(module)
+                    info.external_attr = 0755 << 16L
+                    f.writestr(info, code)
+                else:
+                    f = open(filename, 'w')
+                    try:
+                        f.write(code)
+                    finally:
+                        f.close()
+                log_function('Compiled "%s" as %s' % (name, module))
+        finally:
+            if zip:
+                f.close()
+
+        log_function('Finished compiling templates')
+
+    def list_templates(self, extensions=None, filter_func=None):
+        """Returns a list of templates for this environment.  This requires
+        that the loader supports the loader's
+        :meth:`~BaseLoader.list_templates` method.
+
+        If there are other files in the template folder besides the
+        actual templates, the returned list can be filtered.  There are two
+        ways: either `extensions` is set to a list of file extensions for
+        templates, or a `filter_func` can be provided which is a callable that
+        is passed a template name and should return `True` if it should end up
+        in the result list.
+
+        If the loader does not support that, a :exc:`TypeError` is raised.
+        """
+        x = self.loader.list_templates()
+        if extensions is not None:
+            if filter_func is not None:
+                raise TypeError('either extensions or filter_func '
+                                'can be passed, but not both')
+            filter_func = lambda x: '.' in x and \
+                                    x.rsplit('.', 1)[1] in extensions
+        if filter_func is not None:
+            x = filter(filter_func, x)
+        return x
+
     def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
         """Exception handling helper.  This is used internally to either raise
         rewritten exceptions or return a rendered traceback for the template.
@@ -679,16 +764,31 @@
         """Creates a template object from compiled code and the globals.  This
         is used by the loaders and environment to create a template object.
         """
-        t = object.__new__(cls)
         namespace = {
-            'environment':          environment,
-            '__jinja_template__':   t
+            'environment':  environment,
+            '__file__':     code.co_filename
         }
         exec code in namespace
+        rv = cls._from_namespace(environment, namespace, globals)
+        rv._uptodate = uptodate
+        return rv
+
+    @classmethod
+    def from_module_dict(cls, environment, module_dict, globals):
+        """Creates a template object from a module.  This is used by the
+        module loader to create a template object.
+
+        .. versionadded:: 2.4
+        """
+        return cls._from_namespace(environment, module_dict, globals)
+
+    @classmethod
+    def _from_namespace(cls, environment, namespace, globals):
+        t = object.__new__(cls)
         t.environment = environment
         t.globals = globals
         t.name = namespace['name']
-        t.filename = code.co_filename
+        t.filename = namespace['__file__']
         t.blocks = namespace['blocks']
 
         # render function and module
@@ -697,7 +797,11 @@
 
         # debug and loader helpers
         t._debug_info = namespace['debug_info']
-        t._uptodate = uptodate
+        t._uptodate = None
+
+        # store the reference
+        namespace['environment'] = environment
+        namespace['__jinja_template__'] = t
 
         return t