Skip to content
Snippets Groups Projects
template.py 9.91 KiB
Newer Older
Mike Bayer's avatar
Mike Bayer committed
# template.py
Mike Bayer's avatar
Mike Bayer committed
# Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
Mike Bayer's avatar
Mike Bayer committed
#
# 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."""
Mike Bayer's avatar
Mike Bayer committed

from mako.lexer import Lexer
from mako import runtime, util, exceptions
import imp, time, weakref, tempfile, shutil,  os, stat, sys, re
Mike Bayer's avatar
Mike Bayer committed

Mike Bayer's avatar
Mike Bayer committed

Mike Bayer's avatar
Mike Bayer committed
    
Mike Bayer's avatar
Mike Bayer committed
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, default_filters=['unicode'], 
        buffer_filters=[], imports=None, preprocessor=None):
Mike Bayer's avatar
Mike Bayer committed
        """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 
Mike Bayer's avatar
Mike Bayer committed
        full filename given, or "memory:(hex id of this Template)" if no filename
Mike Bayer's avatar
Mike Bayer committed
        
Mike Bayer's avatar
Mike Bayer committed
        filename - filename of the source template, if any
Mike Bayer's avatar
Mike Bayer committed
        
Mike Bayer's avatar
Mike Bayer committed
        format_exceptions - catch exceptions and format them into an error display template
Mike Bayer's avatar
Mike Bayer committed
        """
        if uri:
            self.module_id = re.sub(r'\W', "_", uri)
            self.uri = uri
Mike Bayer's avatar
Mike Bayer committed
        elif filename:
            self.module_id = re.sub(r'\W', "_", filename)
            self.uri = filename
Mike Bayer's avatar
Mike Bayer committed
        else:
            self.module_id = "memory:" + hex(id(self))
            self.uri = self.module_id
Mike Bayer's avatar
Mike Bayer committed
        
        self.default_filters = default_filters
        self.buffer_filters = buffer_filters
        self.input_encoding = input_encoding
        self.imports = imports
Mike Bayer's avatar
Mike Bayer committed
        # if plain text, compile code in memory only
Mike Bayer's avatar
Mike Bayer committed
        if text is not None:
            (code, module) = _compile_text(self, text, filename)
            self._code = code
Mike Bayer's avatar
Mike Bayer committed
            self._source = text
            ModuleInfo(module, None, self, filename, code, text)
        elif filename is not None:
Mike Bayer's avatar
Mike Bayer committed
            # 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:
                path = os.path.abspath(os.path.join(module_directory.replace('/', os.path.sep), u + ".py"))
                util.verify_directory(os.path.dirname(path))
                filemtime = os.stat(filename)[stat.ST_MTIME]
                if not os.access(path, os.F_OK) 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]
Mike Bayer's avatar
Mike Bayer committed
                if module._magic_number != codegen.MAGIC_NUMBER:
                    _compile_module_file(self, file(filename).read(), filename, path)
Mike Bayer's avatar
Mike Bayer committed
                    module = imp.load_source(self.module_id, path, file(path))
                    del sys.modules[self.module_id]
                ModuleInfo(module, path, self, filename, None, None)
Mike Bayer's avatar
Mike Bayer committed
                # 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)
Mike Bayer's avatar
Mike Bayer committed
        else:
            raise exceptions.RuntimeException("Template requires text or filename")

Mike Bayer's avatar
Mike Bayer committed
        self.module = module
Mike Bayer's avatar
Mike Bayer committed
        self.format_exceptions = format_exceptions
        self.error_handler = error_handler
Mike Bayer's avatar
Mike Bayer committed
        self.lookup = lookup
        self.output_encoding = output_encoding
        self.encoding_errors = encoding_errors
        self.cache_type = cache_type
        self.cache_dir = cache_dir
Mike Bayer's avatar
Mike Bayer committed
        self.cache_url = cache_url
Mike Bayer's avatar
Mike Bayer committed

    source = property(lambda self:_get_module_info_from_callable(self.callable_).source, doc="""return the template source code for this Template.""")
    code = property(lambda self:_get_module_info_from_callable(self.callable_).code, doc="""return the module source code for this Template""")
Mike Bayer's avatar
Mike Bayer committed
    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).
Mike Bayer's avatar
Mike Bayer committed
        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)
Mike Bayer's avatar
Mike Bayer committed
    
    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)
Mike Bayer's avatar
Mike Bayer committed
    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)
Mike Bayer's avatar
Mike Bayer committed
    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))
Mike Bayer's avatar
Mike Bayer committed
        
Mike Bayer's avatar
Mike Bayer committed
class DefTemplate(Template):
    """a Template which represents a callable def in a parent template."""
Mike Bayer's avatar
Mike Bayer committed
    def __init__(self, parent, callable_):
        self.parent = parent
Mike Bayer's avatar
Mike Bayer committed
        self.callable_ = callable_
        self.default_filters = parent.default_filters
        self.buffer_filters = parent.buffer_filters
        self.input_encoding = parent.input_encoding
        self.imports = parent.imports
        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
        self.module = parent.module
        self.filename = parent.filename
        self.cache_type = parent.cache_type
        self.cache_dir = parent.cache_dir
Mike Bayer's avatar
Mike Bayer committed
        self.cache_url = parent.cache_url
Mike Bayer's avatar
Mike Bayer committed
    def get_def(self, name):
        return self.parent.get_def(name)

class ModuleInfo(object):
Mike Bayer's avatar
Mike Bayer committed
    """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
            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, 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)
Mike Bayer's avatar
Mike Bayer committed
    cid = identifier
    module = imp.new_module(cid)
Mike Bayer's avatar
Mike Bayer committed
    code = compile(source, cid, 'exec')
Mike Bayer's avatar
Mike Bayer committed
    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, 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)
    (dest, name) = tempfile.mkstemp()
    os.write(dest, source)
    os.close(dest)
    shutil.move(name, outputpath)
Mike Bayer's avatar
Mike Bayer committed
def _get_module_info_from_callable(callable_):
    return _get_module_info(callable_.func_globals['__name__'])
    
def _get_module_info(filename):
    return ModuleInfo._modules[filename]
Mike Bayer's avatar
Mike Bayer committed