# -*- coding: utf-8 -*- # template.py # Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """provides the Template class, a facade for parsing, generating and executing template strings, as well as template runtime operations.""" from mako.lexer import Lexer from mako import codegen from mako import runtime, util, exceptions import imp, os, re, shutil, stat, sys, tempfile, time, types, weakref class Template(object): """a compiled template""" def __init__(self, text=None, filename=None, uri=None, format_exceptions=False, error_handler=None, lookup=None, output_encoding=None, encoding_errors='strict', module_directory=None, cache_type=None, cache_dir=None, cache_url=None, module_filename=None, input_encoding=None, disable_unicode=False, default_filters=None, buffer_filters=[], imports=None, preprocessor=None, cache_enabled=True): """construct a new Template instance using either literal template text, or a previously loaded template module text - textual template source, or None if a module is to be provided uri - the uri of this template, or some identifying string. defaults to the full filename given, or "memory:(hex id of this Template)" if no filename filename - filename of the source template, if any format_exceptions - catch exceptions and format them into an error display template """ if uri: self.module_id = re.sub(r'\W', "_", uri) self.uri = uri elif filename: self.module_id = re.sub(r'\W', "_", filename) self.uri = filename else: self.module_id = "memory:" + hex(id(self)) self.uri = self.module_id self.input_encoding = input_encoding self.output_encoding = output_encoding self.encoding_errors = encoding_errors self.disable_unicode = disable_unicode if default_filters is None: if self.disable_unicode: self.default_filters = ['str'] else: self.default_filters = ['unicode'] else: self.default_filters = default_filters self.buffer_filters = buffer_filters self.imports = imports self.preprocessor = preprocessor # if plain text, compile code in memory only if text is not None: (code, module) = _compile_text(self, text, filename) self._code = code self._source = text ModuleInfo(module, None, self, filename, code, text) elif filename is not None: # if template filename and a module directory, load # a filesystem-based module file, generating if needed if module_filename is not None: path = module_filename elif module_directory is not None: u = self.uri if u[0] == '/': u = u[1:] path = os.path.abspath(os.path.join(module_directory.replace('/', os.path.sep), u + ".py")) else: path = None if path is not None: util.verify_directory(os.path.dirname(path)) filemtime = os.stat(filename)[stat.ST_MTIME] if not os.path.exists(path) or os.stat(path)[stat.ST_MTIME] < filemtime: _compile_module_file(self, file(filename).read(), filename, path) module = imp.load_source(self.module_id, path, file(path)) del sys.modules[self.module_id] if module._magic_number != codegen.MAGIC_NUMBER: _compile_module_file(self, file(filename).read(), filename, path) module = imp.load_source(self.module_id, path, file(path)) del sys.modules[self.module_id] ModuleInfo(module, path, self, filename, None, None) else: # template filename and no module directory, compile code # in memory (code, module) = _compile_text(self, file(filename).read(), filename) self._source = None self._code = code ModuleInfo(module, None, self, filename, code, None) else: raise exceptions.RuntimeException("Template requires text or filename") self.module = module self.filename = filename self.callable_ = self.module.render_body self.format_exceptions = format_exceptions self.error_handler = error_handler self.lookup = lookup self.cache_type = cache_type self.cache_dir = cache_dir self.cache_url = cache_url self.cache_enabled = cache_enabled def source(self): """return the template source code for this Template.""" return _get_module_info_from_callable(self.callable_).source source = property(source) def code(self): """return the module source code for this Template""" return _get_module_info_from_callable(self.callable_).code code = property(code) def cache(self): return self.module._template_cache cache = property(cache) def render(self, *args, **data): """render the output of this template as a string. if the template specifies an output encoding, the string will be encoded accordingly, else the output is raw (raw output uses cStringIO and can't handle multibyte characters). a Context object is created corresponding to the given data. Arguments that are explictly declared by this template's internal rendering method are also pulled from the given *args, **data members.""" return runtime._render(self, self.callable_, args, data) def render_unicode(self, *args, **data): """render the output of this template as a unicode object.""" return runtime._render(self, self.callable_, args, data, as_unicode=True) def render_context(self, context, *args, **kwargs): """render this Template with the given context. the data is written to the context's buffer.""" if getattr(context, '_with_template', None) is None: context._with_template = self runtime._render_context(self, self.callable_, context, *args, **kwargs) def has_def(self, name): return hasattr(self.module, "render_%s" % name) def get_def(self, name): """return a def of this template as an individual Template of its own.""" return DefTemplate(self, getattr(self.module, "render_%s" % name)) class ModuleTemplate(Template): """A Template which is constructed given an existing Python module. e.g.:: t = Template("this is a template") f = file("mymodule.py", "w") f.write(t.code) f.close() import mymodule t = ModuleTemplate(mymodule) print t.render() """ def __init__(self, module, module_filename=None, template=None, template_filename=None, module_source=None, template_source=None, output_encoding=None, encoding_errors='strict', disable_unicode=False, format_exceptions=False, error_handler=None, lookup=None, cache_type=None, cache_dir=None, cache_url=None, cache_enabled=True ): self.module_id = re.sub(r'\W', "_", module._template_uri) self.uri = module._template_uri self.input_encoding = module._source_encoding self.output_encoding = output_encoding self.encoding_errors = encoding_errors self.disable_unicode = disable_unicode self.module = module self.filename = template_filename ModuleInfo(module, module_filename, self, template_filename, module_source, template_source) self.callable_ = self.module.render_body self.format_exceptions = format_exceptions self.error_handler = error_handler self.lookup = lookup self.cache_type = cache_type self.cache_dir = cache_dir self.cache_url = cache_url self.cache_enabled = cache_enabled class DefTemplate(Template): """a Template which represents a callable def in a parent template.""" def __init__(self, parent, callable_): self.parent = parent self.callable_ = callable_ self.output_encoding = parent.output_encoding self.encoding_errors = parent.encoding_errors self.format_exceptions = parent.format_exceptions self.error_handler = parent.error_handler self.lookup = parent.lookup def get_def(self, name): return self.parent.get_def(name) class ModuleInfo(object): """stores information about a module currently loaded into memory, provides reverse lookups of template source, module source code based on a module's identifier.""" _modules = weakref.WeakValueDictionary() def __init__(self, module, module_filename, template, template_filename, module_source, template_source): self.module = module self.module_filename = module_filename self.template_filename = template_filename self.module_source = module_source self.template_source = template_source self._modules[module.__name__] = template._mmarker = self if module_filename: self._modules[module_filename] = self def _get_code(self): if self.module_source is not None: return self.module_source else: return file(self.module_filename).read() code = property(_get_code) def _get_source(self): if self.template_source is not None: if self.module._source_encoding and not isinstance(self.template_source, unicode): return self.template_source.decode(self.module._source_encoding) else: return self.template_source else: if self.module._source_encoding: return file(self.template_filename).read().decode(self.module._source_encoding) else: return file(self.template_filename).read() source = property(_get_source) def _compile_text(template, text, filename): identifier = template.module_id lexer = Lexer(text, filename, disable_unicode=template.disable_unicode, input_encoding=template.input_encoding, preprocessor=template.preprocessor) node = lexer.parse() source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding, generate_unicode=not template.disable_unicode) #print source cid = identifier if isinstance(cid, unicode): cid = cid.encode() module = types.ModuleType(cid) code = compile(source, cid, 'exec') exec code in module.__dict__, module.__dict__ return (source, module) def _compile_module_file(template, text, filename, outputpath): identifier = template.module_id lexer = Lexer(text, filename, disable_unicode=template.disable_unicode, input_encoding=template.input_encoding, preprocessor=template.preprocessor) node = lexer.parse() source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding, generate_unicode=not template.disable_unicode) (dest, name) = tempfile.mkstemp() os.write(dest, source) os.close(dest) shutil.move(name, outputpath) def _get_module_info_from_callable(callable_): return _get_module_info(callable_.func_globals['__name__']) def _get_module_info(filename): return ModuleInfo._modules[filename]