278 lines
12 KiB
Python
Executable File
278 lines
12 KiB
Python
Executable File
# -*- 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]
|
|
|