blob: 4f1792c20fdf94cfa2ca9283ea4bacf4c91b9e52 [file]
# runtime.py
# Copyright (C) 2006 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 runtime services for templates, including Context, Namespace, and various helper functions."""
from mako import exceptions, util
import inspect, sys
class Context(object):
"""provides runtime namespace, output buffer, and various callstacks for templates."""
def __init__(self, buffer, **data):
self._buffer_stack = [buffer]
self._data = data
self._with_template = None
self.namespaces = {}
# "capture" function which proxies to the generic "capture" function
data['capture'] = lambda x, *args, **kwargs: capture(self, x, *args, **kwargs)
# "caller" stack used by def calls with content
self.caller_stack = [Undefined]
data['caller'] = _StackFacade(self.caller_stack)
lookup = property(lambda self:self._with_template.lookup)
def keys(self):
return self._data.keys()
def __getitem__(self, key):
return self._data[key]
def _put(self, key, value):
self._data[key] = value
def push_buffer(self):
"""push a capturing buffer onto this Context."""
self._buffer_stack.append(util.FastEncodingBuffer())
def pop_buffer(self):
"""pop the most recent capturing buffer from this Context."""
return self._buffer_stack.pop()
def get(self, key, default=None):
return self._data.get(key, default)
def write(self, string):
"""write a string to this Context's underlying output buffer."""
self._buffer_stack[-1].write(string)
def _copy(self):
c = Context.__new__(Context)
c._buffer_stack = self._buffer_stack
c._data = self._data.copy()
c._with_template = self._with_template
c.namespaces = self.namespaces
c.caller_stack = self.caller_stack
return c
def locals_(self, d):
"""create a new Context with a copy of this Context's current state, updated with the given dictionary."""
if len(d) == 0:
return self
c = self._copy()
c._data.update(d)
return c
def _clean_inheritance_tokens(self):
"""create a new copy of this Context with tokens related to inheritance state removed."""
c = self._copy()
x = c._data
x.pop('self', None)
x.pop('parent', None)
x.pop('next', None)
return c
class _StackFacade(object):
def __init__(self, stack):
self.target = stack
def __getattr__(self, key):
return getattr(self.target[-1], key)
class Undefined(object):
"""represents an undefined value in a template."""
def __str__(self):
raise NameError("Undefined")
UNDEFINED = Undefined()
class Namespace(object):
"""provides access to collections of rendering methods, which can be local, from other templates, or from imported modules"""
def __init__(self, name, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True, calling_uri=None):
self.name = name
self._module = module
if templateuri is not None:
self.template = _lookup_template(context, templateuri, calling_uri)
else:
self.template = template
self._templateuri = templateuri
self.context = context
self.inherits = inherits
if callables is not None:
self.callables = dict([(c.func_name, c) for c in callables])
else:
self.callables = None
if populate_self and self.template is not None:
(lclcallable, self.context) = _populate_self_namespace(context, self.template, self_ns=self)
module = property(lambda s:s._module or s.template.module)
filename = property(lambda s:s._module and s._module.__file__ or s.template.filename)
uri = property(lambda s:s.template.uri)
def get_namespace(self, uri):
"""return a namespace corresponding to the given template uri.
if a relative uri, it is adjusted to that of the template of this namespace"""
key = (self, uri)
if self.context.namespaces.has_key(key):
return context.namespaces[key]
else:
ns = Namespace(uri, self.context, templateuri=uri, calling_uri=self._templateuri)
self.context.namespaces[key] = ns
return ns
def get_cached(self, key, **kwargs):
if self.template:
if self.template.cache_dir:
kwargs.setdefault('data_dir', self.template.cache_dir)
if self.template.cache_type:
kwargs.setdefault('type', self.template.cache_type)
return self.template.module._template_cache.get(key, **kwargs)
def include_file(self, uri):
"""include a file at the given uri"""
_include_file(self.context, uri, self._templateuri)
def _populate(self, d, l):
for ident in l:
if ident == '*':
for (k, v) in self._get_star():
d[k] = v
else:
d[ident] = getattr(self, ident)
def _get_star(self):
if self.callables is not None:
for k in self.callables:
yield (k, self.callables[key])
if self.template is not None:
def get(key):
callable_ = self.template.get_def(key).callable_
return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
for k in self.template.module._exports:
yield (k, get(k))
if self.module is not None:
for k in dir(self.module):
if k[0] != '_':
yield (k, getattr(self.module, k))
def __getattr__(self, key):
if self.callables is not None:
try:
return self.callables[key]
except KeyError:
pass
if self.template is not None:
if key == 'body':
callable_ = self.template.module.render
else:
try:
callable_ = self.template.get_def(key).callable_
except AttributeError:
callable_ = None
if callable_ is not None:
return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
if self._module is not None:
try:
callable_ = getattr(self._module, key)
return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
except AttributeError:
pass
if self.inherits is not None:
return getattr(self.inherits, key)
raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key))
def capture(context, callable_, *args, **kwargs):
"""execute the given template def, capturing the output into a buffer."""
if not callable(callable_):
raise exceptions.RuntimeException("capture() function expects a callable as its argument (i.e. capture(func, *args, **kwargs))")
context.push_buffer()
try:
callable_(*args, **kwargs)
finally:
buf = context.pop_buffer()
return buf.getvalue()
def _include_file(context, uri, calling_uri):
"""locate the template from the given uri and include it in the current output."""
template = _lookup_template(context, uri, calling_uri)
(callable_, ctx) = _populate_self_namespace(context._clean_inheritance_tokens(), template)
callable_(ctx)
def _inherit_from(context, uri, calling_uri):
"""called by the _inherit method in template modules to set up the inheritance chain at the start
of a template's execution."""
if uri is None:
return None
template = _lookup_template(context, uri, calling_uri)
self_ns = context['self']
ih = self_ns
while ih.inherits is not None:
ih = ih.inherits
lclcontext = context.locals_({'next':ih})
ih.inherits = Namespace("self:%s" % template.uri, lclcontext, template = template, populate_self=False)
context._data['parent'] = lclcontext._data['local'] = ih.inherits
callable_ = getattr(template.module, '_mako_inherit', None)
if callable_ is not None:
ret = callable_(template, lclcontext)
if ret:
return ret
gen_ns = getattr(template.module, '_mako_generate_namespaces', None)
if gen_ns is not None:
gen_ns(context)
return (template.callable_, lclcontext)
def _lookup_template(context, uri, relativeto):
lookup = context._with_template.lookup
uri = lookup.adjust_uri(uri, relativeto)
try:
return lookup.get_template(uri)
except exceptions.TopLevelLookupException, e:
raise exceptions.TemplateLookupException(str(e))
def _populate_self_namespace(context, template, self_ns=None):
if self_ns is None:
self_ns = Namespace('self:%s' % template.uri, context, template=template, populate_self=False)
context._data['self'] = context._data['local'] = self_ns
if hasattr(template.module, '_mako_inherit'):
ret = template.module._mako_inherit(template, context)
if ret:
return ret
return (template.callable_, context)
def _render(template, callable_, args, data, as_unicode=False):
"""create a Context and return the string output of the given template and template callable."""
if as_unicode:
buf = util.FastEncodingBuffer()
elif template.output_encoding:
buf = util.FastEncodingBuffer(template.output_encoding)
else:
buf = util.StringIO()
context = Context(buf, **data)
context._with_template = template
kwargs = {}
argspec = inspect.getargspec(callable_)
namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
for arg in namedargs:
if arg != 'context' and arg in data:
kwargs[arg] = data[arg]
_render_context(template, callable_, context, *args, **kwargs)
return context.pop_buffer().getvalue()
def _render_context(template, callable_, context, *args, **kwargs):
# create polymorphic 'self' namespace for this template with possibly updated context
(inherit, lclcontext) = _populate_self_namespace(context, template)
if callable_.__name__ == 'render':
# if main render method, call from the base of the inheritance stack
_exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
else:
# otherwise, call the actual rendering method specified
_exec_template(callable_, context, args=args, kwargs=kwargs)
def _exec_template(callable_, context, args=None, kwargs=None):
"""execute a rendering callable given the callable, a Context, and optional explicit arguments
the contextual Template will be located if it exists, and the error handling options specified
on that Template will be interpreted here.
"""
template = context._with_template
if template is not None and (template.format_exceptions or template.error_handler):
error = None
try:
callable_(context, *args, **kwargs)
except Exception, e:
error = e
except:
e = sys.exc_info()[0]
error = e
if error:
if template.error_handler:
result = template.error_handler(context, error)
if not result:
raise error
else:
context._buffer_stack = [util.StringIO()]
error_template = exceptions.html_error_template()
context._with_template = error_template
error_template.render_context(context, error=error)
else:
callable_(context, *args, **kwargs)