From 12a316bd5cbcbeda48013258447529f59bc8ac12 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 12 Mar 2010 17:59:51 +0100 Subject: [PATCH] Improved tests and template compilation. --HG-- branch : trunk --- jinja2/environment.py | 67 ++++++++++++++++++++++++++++---------- jinja2/testsuite/loader.py | 55 +++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 25 deletions(-) diff --git a/jinja2/environment.py b/jinja2/environment.py index 742bc1c..9145acb 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -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') diff --git a/jinja2/testsuite/loader.py b/jinja2/testsuite/loader.py index e6a552a..0ff0d04 100644 --- a/jinja2/testsuite/loader.py +++ b/jinja2/testsuite/loader.py @@ -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() -- 2.26.2