diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-02-05 00:15:54 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-02-05 00:15:54 +0000 |
commit | 1575e1e4e117cf127ddeade810c3dccafe1f218b (patch) | |
tree | 4b66991b170127051dd3c250f1a0c03943477832 /mako/runtime.py | |
parent | 86411ebded5f62970eaa2b119a215a71212cb492 (diff) | |
download | external_python_mako-1575e1e4e117cf127ddeade810c3dccafe1f218b.tar.gz external_python_mako-1575e1e4e117cf127ddeade810c3dccafe1f218b.tar.bz2 external_python_mako-1575e1e4e117cf127ddeade810c3dccafe1f218b.zip |
move lib/mako to mako
Diffstat (limited to 'mako/runtime.py')
-rw-r--r-- | mako/runtime.py | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/mako/runtime.py b/mako/runtime.py new file mode 100644 index 0000000..a475b71 --- /dev/null +++ b/mako/runtime.py @@ -0,0 +1,419 @@ +# runtime.py +# 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 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_callable(key) + 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_callable(key) + 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 AttributeError("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 _decorate_toplevel(fn): + def decorate_render(render_fn): + def go(context, *args, **kw): + def y(*args, **kw): + return render_fn(context, *args, **kw) + try: + y.__name__ = render_fn.__name__[7:] + except TypeError: + # < Python 2.4 + pass + return fn(y)(context, *args, **kw) + return go + return decorate_render + +def _decorate_inline(context, fn): + def decorate_render(render_fn): + dec = fn(render_fn) + def go(*args, **kw): + return dec(context, *args, **kw) + return go + return decorate_render + +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) |