# -*- coding: utf-8 -*- # runtime.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 runtime services for templates, including Context, Namespace, and various helper functions.""" from mako import exceptions, util import __builtin__, 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._orig = data # original data, minus the builtins self._data = __builtin__.__dict__.copy() # the context data which includes builtins self._data.update(data) self._kwargs = data.copy() self._with_template = None self.namespaces = {} # "capture" function which proxies to the generic "capture" function self._data['capture'] = lambda x, *args, **kwargs: capture(self, x, *args, **kwargs) # "caller" stack used by def calls with content self.caller_stack = self._data['caller'] = CallerStack() lookup = property(lambda self:self._with_template.lookup) kwargs = property(lambda self:self._kwargs.copy()) def push_caller(self, caller): self.caller_stack.append(caller) def pop_caller(self): del self.caller_stack[-1] def keys(self): return self._data.keys() def __getitem__(self, key): return self._data[key] def _push_writer(self): """push a capturing buffer onto this Context and return the new Writer function.""" buf = util.FastEncodingBuffer() self._buffer_stack.append(buf) return buf.write def _pop_buffer_and_writer(self): """pop the most recent capturing buffer from this Context and return the current writer after the pop. """ buf = self._buffer_stack.pop() return buf, self._buffer_stack[-1].write def _push_buffer(self): """push a capturing buffer onto this Context.""" self._push_writer() 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 writer(self): """return the current writer function""" return self._buffer_stack[-1].write def _copy(self): c = Context.__new__(Context) c._buffer_stack = self._buffer_stack c._data = self._data.copy() c._orig = self._orig c._kwargs = self._kwargs 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 CallerStack(list): def __init__(self): self.nextcaller = None def __nonzero__(self): return self._get_caller() and True or False def _get_caller(self): return self[-1] def __getattr__(self, key): return getattr(self._get_caller(), key) def _push_frame(self): self.append(self.nextcaller or None) self.nextcaller = None def _pop_frame(self): self.nextcaller = self.pop() class Undefined(object): """represents an undefined value in a template.""" def __str__(self): raise NameError("Undefined") def __nonzero__(self): return False UNDEFINED = Undefined() class _NSAttr(object): def __init__(self, parent): self.__parent = parent def __getattr__(self, key): ns = self.__parent while ns: if hasattr(ns.module, key): return getattr(ns.module, key) else: ns = ns.inherits raise AttributeError(key) 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 if module is not None: mod = __import__(module) for token in module.split('.')[1:]: mod = getattr(mod, token) self._module = mod else: self._module = None if templateuri is not None: self.template = _lookup_template(context, templateuri, calling_uri) self._templateuri = self.template.module._template_uri else: self.template = template if self.template is not None: self._templateuri = self.template.module._template_uri 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, lclcontext) = _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 attr(self): if not hasattr(self, '_attr'): self._attr = _NSAttr(self) return self._attr attr = property(attr) 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 self.context.namespaces[key] else: ns = Namespace(uri, self.context._copy(), templateuri=uri, calling_uri=self._templateuri) self.context.namespaces[key] = ns return ns def get_template(self, uri): return _lookup_template(self.context, uri, self._templateuri) def get_cached(self, key, **kwargs): if self.template: if not self.template.cache_enabled: createfunc = kwargs.get('createfunc', None) if createfunc: return createfunc() else: return None 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) if self.template.cache_url: kwargs.setdefault('url', self.template.cache_url) return self.cache.get(key, **kwargs) def cache(self): return self.template.cache cache = property(cache) def include_file(self, uri, **kwargs): """include a file at the given uri""" _include_file(self.context, uri, self._templateuri, **kwargs) 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: for key in self.callables: yield (key, self.callables[key]) if self.template: 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: def get(key): callable_ = getattr(self._module, key) return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) for k in dir(self._module): if k[0] != '_': yield (k, get(k)) def __getattr__(self, key): if self.callables and key in self.callables: return self.callables[key] if self.template and self.template.has_def(key): callable_ = self.template.get_def(key).callable_ return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) if self._module and hasattr(self._module, key): callable_ = getattr(self._module, key) return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) if self.inherits is not None: return getattr(self.inherits, key) raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key)) def supports_caller(func): """apply a caller_stack compatibility decorator to a plain Python function.""" def wrap_stackframe(context, *args, **kwargs): context.caller_stack._push_frame() try: return func(context, *args, **kwargs) finally: context.caller_stack._pop_frame() return wrap_stackframe 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, **kwargs): """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, **_kwargs_for_callable(callable_, context._orig, **kwargs)) 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 if lookup is None: raise exceptions.TemplateLookupException("Template '%s' has no TemplateLookup associated" % context._with_template.uri) 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(unicode=True) elif template.output_encoding: buf = util.FastEncodingBuffer(unicode=as_unicode, encoding=template.output_encoding, errors=template.encoding_errors) else: buf = util.StringIO() context = Context(buf, **data) context._with_template = template _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data)) return context._pop_buffer().getvalue() def _kwargs_for_callable(callable_, data, **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 and arg not in kwargs: kwargs[arg] = data[arg] return kwargs def _render_context(tmpl, callable_, context, *args, **kwargs): import mako.template as template # create polymorphic 'self' namespace for this template with possibly updated context if not isinstance(tmpl, template.DefTemplate): # if main render method, call from the base of the inheritance stack (inherit, lclcontext) = _populate_self_namespace(context, tmpl) _exec_template(inherit, lclcontext, args=args, kwargs=kwargs) else: # otherwise, call the actual rendering method specified (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent) _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: error_template = exceptions.html_error_template() context._buffer_stack[:] = [util.FastEncodingBuffer(error_template.output_encoding, error_template.encoding_errors)] context._with_template = error_template error_template.render_context(context, error=error) else: callable_(context, *args, **kwargs)