Improved tests and template compilation.
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 12 Mar 2010 16:59:51 +0000 (17:59 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 12 Mar 2010 16:59:51 +0000 (17:59 +0100)
--HG--
branch : trunk

jinja2/environment.py
jinja2/testsuite/loader.py

index 742bc1c496493d18754571b1042695e15fdc2f8e..9145acb5e134ebcecd6bb0959ead94142221eaf8 100644 (file)
@@ -8,6 +8,7 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
+import os
 import sys
 from jinja2 import nodes
 from jinja2.defaults import *
@@ -501,24 +502,53 @@ class Environment(object):
         return TemplateExpression(template, undefined_to_none)
 
     def compile_templates(self, target, extensions=None, filter_func=None,
-                          zip=True, log_function=None):
+                          zip='deflated', log_function=None,
+                          ignore_errors=True, py_compile=False):
         """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.
+        and stores them in `target`.  If `zip` is `None`, instead of in a
+        zipfile, the templates will be will be stored in a directory.
+        By default a deflate zip algorithm is used, to switch to
+        the stored algorithm, `zip` can be set to ``'stored'``.
 
         `extensions` and `filter_func` are passed to :meth:`list_templates`.
         Each template returned will be compiled to the target folder or
         zipfile.
 
+        By default template compilation errors are ignored.  In case a
+        log function is provided, errors are logged.  If you want template
+        syntax errors to abort the compilation you can set `ignore_errors`
+        to `False` and you will get an exception on syntax errors.
+
+        If `py_compile` is set to `True` .pyc files will be written to the
+        target instead of standard .py files.
+
         .. 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)
+        if py_compile:
+            import imp, struct, marshal
+            py_header = imp.get_magic() + '\xff\xff\xff\xff'
+
+        def write_file(filename, data):
+            if zip:
+                info = ZipInfo(filename)
+                info.external_attr = 0755 << 16L
+                zip_file.writestr(info, data)
+            else:
+                f = open(os.path.join(target, filename), 'wb')
+                try:
+                    f.write(data)
+                finally:
+                    f.close()
+
+        if zip is not None:
+            from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
+            zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED,
+                                                 stored=ZIP_STORED)[zip])
             log_function('Compiling into Zip archive "%s"' % target)
         else:
             if not os.path.isdir(target):
@@ -531,23 +561,24 @@ class Environment(object):
                 try:
                     code = self.compile(source, name, filename, True, True)
                 except TemplateSyntaxError, e:
+                    if not ignore_errors:
+                        raise
                     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)
+
+                filename = ModuleLoader.get_module_filename(name)
+
+                if py_compile:
+                    c = compile(code, _encode_filename(filename), 'exec')
+                    write_file(filename + 'c', py_header + marshal.dumps(c))
+                    log_function('Byte-compiled "%s" as %s' %
+                                 (name, filename + 'c'))
                 else:
-                    f = open(filename, 'w')
-                    try:
-                        f.write(code)
-                    finally:
-                        f.close()
-                log_function('Compiled "%s" as %s' % (name, module))
+                    write_file(filename, code)
+                    log_function('Compiled "%s" as %s' % (name, filename))
         finally:
             if zip:
-                f.close()
+                zip_file.close()
 
         log_function('Finished compiling templates')
 
index e6a552a038c54354956199f25addaed546a1810c..0ff0d0471e46613573a98a76101b3f0bc997f382 100644 (file)
@@ -12,6 +12,7 @@ import os
 import sys
 import time
 import tempfile
+import shutil
 import unittest
 
 from jinja2.testsuite import JinjaTestCase, dict_loader, \
@@ -102,19 +103,38 @@ class LoaderTestCase(JinjaTestCase):
 class ModuleLoaderTestCase(JinjaTestCase):
     archive = None
 
-    def setup(self):
+    def compile_down(self, zip='deflated', py_compile=False):
         super(ModuleLoaderTestCase, self).setup()
+        log = []
         self.reg_env = Environment(loader=prefix_loader)
-        self.archive = tempfile.mkstemp(suffix='.zip')[1]
-        self.reg_env.compile_templates(self.archive)
+        if zip is not None:
+            self.archive = tempfile.mkstemp(suffix='.zip')[1]
+        else:
+            self.archive = tempfile.mkdtemp()
+        self.reg_env.compile_templates(self.archive, zip=zip,
+                                       log_function=log.append,
+                                       py_compile=py_compile)
         self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
+        return ''.join(log)
 
     def teardown(self):
         super(ModuleLoaderTestCase, self).teardown()
-        os.remove(self.archive)
-        self.archive = None
-
-    def test_module_loader(self):
+        if hasattr(self, 'mod_env'):
+            if os.path.isfile(self.archive):
+                os.remove(self.archive)
+            else:
+                shutil.rmtree(self.archive)
+            self.archive = None
+
+    def test_log(self):
+        log = self.compile_down()
+        assert 'Compiled "a/foo/test.html" as ' \
+               'tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a' in log
+        assert 'Finished compiling templates' in log
+        assert 'Could not compile "a/syntaxerror.html": ' \
+               'Encountered unknown tag \'endif\'' in log
+
+    def _test_common(self):
         tmpl1 = self.reg_env.get_template('a/test.html')
         tmpl2 = self.mod_env.get_template('a/test.html')
         assert tmpl1.render() == tmpl2.render()
@@ -123,7 +143,20 @@ class ModuleLoaderTestCase(JinjaTestCase):
         tmpl2 = self.mod_env.get_template('b/justdict.html')
         assert tmpl1.render() == tmpl2.render()
 
+    def test_deflated_zip_compile(self):
+        self.compile_down(zip='deflated')
+        self._test_common()
+
+    def test_stored_zip_compile(self):
+        self.compile_down(zip='stored')
+        self._test_common()
+
+    def test_filesystem_compile(self):
+        self.compile_down(zip=None)
+        self._test_common()
+
     def test_weak_references(self):
+        self.compile_down()
         tmpl = self.mod_env.get_template('a/test.html')
         key = loaders.ModuleLoader.get_template_key('a/test.html')
         name = self.mod_env.loader.module.__name__
@@ -142,6 +175,14 @@ class ModuleLoaderTestCase(JinjaTestCase):
 
         assert name not in sys.modules
 
+    def test_byte_compilation(self):
+        log = self.compile_down(py_compile=True)
+        assert 'Byte-compiled "a/test.html"' in log
+        tmpl1 = self.mod_env.get_template('a/test.html')
+        mod = self.mod_env.loader.module. \
+            tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
+        assert mod.__file__.endswith('.pyc')
+
 
 def suite():
     suite = unittest.TestSuite()