Newer
Older
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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 import runtime, util, exceptions, codegen
Philip Jenvey
committed
import imp, os, re, shutil, stat, sys, tempfile, time, types, weakref
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
:param text: textual template source, or None if a module is to be
provided
:param 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
:param filename: filename of the source template, if any
:param format_exceptions: catch exceptions and format them into an
error display template
Mike Bayer
committed
if uri:
self.module_id = re.sub(r'\W', "_", uri)
self.uri = uri
Mike Bayer
committed
self.module_id = re.sub(r'\W', "_", filename)
self.uri = filename
Mike Bayer
committed
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 util.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not "
"support disabling Unicode")
if default_filters is None:
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
(code, module) = _compile_text(self, text, filename)
ModuleInfo(module, None, self, filename, code, text)
# if template filename and a module directory, load
# a filesystem-based module file, generating if needed
Mike Bayer
committed
if module_filename is not None:
path = module_filename
elif module_directory is not None:
Mike Bayer
committed
u = self.uri
if u[0] == '/':
u = u[1:]
path = os.path.abspath(
os.path.join(
module_directory.replace('/', os.path.sep),
u + ".py"
)
)
Mike Bayer
committed
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,
module = imp.load_source(self.module_id, path, open(path, 'rb'))
Mike Bayer
committed
del sys.modules[self.module_id]
module = imp.load_source(self.module_id, path, open(path, 'rb'))
ModuleInfo(module, path, self, filename, None, None)
# template filename and no module directory, compile code
# in memory
self._source = None
self._code = code
ModuleInfo(module, None, self, filename, code, None)
raise exceptions.RuntimeException(
"Template requires text or filename")
self.filename = filename
Mike Bayer
committed
self.callable_ = self.module.render_body
self.format_exceptions = format_exceptions
self.error_handler = error_handler
Mike Bayer
committed
self.cache_type = cache_type
self.cache_dir = cache_dir
Mike Bayer
committed
def source(self):
"""return the template source code for this Template."""
Mike Bayer
committed
return _get_module_info_from_callable(self.callable_).source
Mike Bayer
committed
def code(self):
"""return the module source code for this Template"""
Mike Bayer
committed
return _get_module_info_from_callable(self.callable_).code
def cache(self):
return self.module._template_cache
"""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)
"""Return a def of this template as a DefTemplate."""
return DefTemplate(self, getattr(self.module, "render_%s" % name))
def _get_def_callable(self, name):
return getattr(self.module, "render_%s" % name)
Mike Bayer
committed
def last_modified(self):
return self.module._modified_time
last_modified = property(last_modified)
Mike Bayer
committed
class ModuleTemplate(Template):
"""A Template which is constructed given an existing Python module.
e.g.::
Mike Bayer
committed
t = Template("this is a template")
Mike Bayer
committed
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
Mike Bayer
committed
):
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)
Mike Bayer
committed
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
class DefTemplate(Template):
"""a Template which represents a callable def in a parent template."""
def __init__(self, parent, callable_):
self.parent = parent
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
_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
if self.module_source is not None:
return self.module_source
else:
return open(self.module_filename).read()
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 open(self.template_filename, 'rb').read().\
return open(self.template_filename).read()
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_magic_comment=template.disable_unicode)
if not util.py3k and isinstance(cid, unicode):
Philip Jenvey
committed
cid = cid.encode()
module = types.ModuleType(cid)
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,
(dest, name) = tempfile.mkstemp()
if isinstance(source, unicode):
source = source.encode(lexer.encoding or 'ascii')
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]