Added Mako report library
[odoo/odoo.git] / bin / mako / runtime.py
1 # runtime.py
2 # Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
3 #
4 # This module is part of Mako and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7 """provides runtime services for templates, including Context, Namespace, and various helper functions."""
8
9 from mako import exceptions, util
10 import __builtin__, inspect, sys
11
12 class Context(object):
13     """provides runtime namespace, output buffer, and various callstacks for templates."""
14     def __init__(self, buffer, **data):
15         self._buffer_stack = [buffer]
16         self._orig = data  # original data, minus the builtins
17         self._data = __builtin__.__dict__.copy() # the context data which includes builtins
18         self._data.update(data)
19         self._kwargs = data.copy()
20         self._with_template = None
21         self.namespaces = {}
22
23         # "capture" function which proxies to the generic "capture" function
24         self._data['capture'] = lambda x, *args, **kwargs: capture(self, x, *args, **kwargs)
25
26         # "caller" stack used by def calls with content
27         self.caller_stack = self._data['caller'] = CallerStack()
28
29     lookup = property(lambda self:self._with_template.lookup)
30     kwargs = property(lambda self:self._kwargs.copy())
31
32     def push_caller(self, caller):
33         self.caller_stack.append(caller)
34
35     def pop_caller(self):
36         del self.caller_stack[-1]
37
38     def keys(self):
39         return self._data.keys()
40
41     def __getitem__(self, key):
42         return self._data[key]
43
44     def _push_writer(self):
45         """push a capturing buffer onto this Context and return the new Writer function."""
46
47         buf = util.FastEncodingBuffer()
48         self._buffer_stack.append(buf)
49         return buf.write
50
51     def _pop_buffer_and_writer(self):
52         """pop the most recent capturing buffer from this Context
53         and return the current writer after the pop.
54
55         """
56
57         buf = self._buffer_stack.pop()
58         return buf, self._buffer_stack[-1].write
59
60     def _push_buffer(self):
61         """push a capturing buffer onto this Context."""
62
63         self._push_writer()
64
65     def _pop_buffer(self):
66         """pop the most recent capturing buffer from this Context."""
67
68         return self._buffer_stack.pop()
69
70     def get(self, key, default=None):
71         return self._data.get(key, default)
72
73     def write(self, string):
74         """write a string to this Context's underlying output buffer."""
75
76         self._buffer_stack[-1].write(string)
77
78     def writer(self):
79         """return the current writer function"""
80
81         return self._buffer_stack[-1].write
82
83     def _copy(self):
84         c = Context.__new__(Context)
85         c._buffer_stack = self._buffer_stack
86         c._data = self._data.copy()
87         c._orig = self._orig
88         c._kwargs = self._kwargs
89         c._with_template = self._with_template
90         c.namespaces = self.namespaces
91         c.caller_stack = self.caller_stack
92         return c
93     def locals_(self, d):
94         """create a new Context with a copy of this Context's current state, updated with the given dictionary."""
95         if len(d) == 0:
96             return self
97         c = self._copy()
98         c._data.update(d)
99         return c
100     def _clean_inheritance_tokens(self):
101         """create a new copy of this Context with tokens related to inheritance state removed."""
102         c = self._copy()
103         x = c._data
104         x.pop('self', None)
105         x.pop('parent', None)
106         x.pop('next', None)
107         return c
108
109 class CallerStack(list):
110     def __init__(self):
111         self.nextcaller = None
112     def __nonzero__(self):
113         return self._get_caller() and True or False
114     def _get_caller(self):
115         return self[-1]
116     def __getattr__(self, key):
117         return getattr(self._get_caller(), key)
118     def _push_frame(self):
119         self.append(self.nextcaller or None)
120         self.nextcaller = None
121     def _pop_frame(self):
122         self.nextcaller = self.pop()
123
124
125 class Undefined(object):
126     """represents an undefined value in a template."""
127     def __str__(self):
128         raise NameError("Undefined")
129     def __nonzero__(self):
130         return False
131
132 UNDEFINED = Undefined()
133
134 class _NSAttr(object):
135     def __init__(self, parent):
136         self.__parent = parent
137     def __getattr__(self, key):
138         ns = self.__parent
139         while ns:
140             if hasattr(ns.module, key):
141                 return getattr(ns.module, key)
142             else:
143                 ns = ns.inherits
144         raise AttributeError(key)
145
146 class Namespace(object):
147     """provides access to collections of rendering methods, which can be local, from other templates, or from imported modules"""
148     def __init__(self, name, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True, calling_uri=None):
149         self.name = name
150         if module is not None:
151             mod = __import__(module)
152             for token in module.split('.')[1:]:
153                 mod = getattr(mod, token)
154             self._module = mod
155         else:
156             self._module = None
157         if templateuri is not None:
158             self.template = _lookup_template(context, templateuri, calling_uri)
159             self._templateuri = self.template.module._template_uri
160         else:
161             self.template = template
162             if self.template is not None:
163                 self._templateuri = self.template.module._template_uri
164         self.context = context
165         self.inherits = inherits
166         if callables is not None:
167             self.callables = dict([(c.func_name, c) for c in callables])
168         else:
169             self.callables = None
170         if populate_self and self.template is not None:
171             (lclcallable, lclcontext) = _populate_self_namespace(context, self.template, self_ns=self)
172
173     module = property(lambda s:s._module or s.template.module)
174     filename = property(lambda s:s._module and s._module.__file__ or s.template.filename)
175     uri = property(lambda s:s.template.uri)
176
177     def attr(self):
178         if not hasattr(self, '_attr'):
179             self._attr = _NSAttr(self)
180         return self._attr
181     attr = property(attr)
182
183     def get_namespace(self, uri):
184         """return a namespace corresponding to the given template uri.
185
186         if a relative uri, it is adjusted to that of the template of this namespace"""
187         key = (self, uri)
188         if self.context.namespaces.has_key(key):
189             return self.context.namespaces[key]
190         else:
191             ns = Namespace(uri, self.context._copy(), templateuri=uri, calling_uri=self._templateuri)
192             self.context.namespaces[key] = ns
193             return ns
194
195     def get_template(self, uri):
196         return _lookup_template(self.context, uri, self._templateuri)
197
198     def get_cached(self, key, **kwargs):
199         if self.template:
200             if not self.template.cache_enabled:
201                 createfunc = kwargs.get('createfunc', None)
202                 if createfunc:
203                     return createfunc()
204                 else:
205                     return None
206
207             if self.template.cache_dir:
208                 kwargs.setdefault('data_dir', self.template.cache_dir)
209             if self.template.cache_type:
210                 kwargs.setdefault('type', self.template.cache_type)
211             if self.template.cache_url:
212                 kwargs.setdefault('url', self.template.cache_url)
213         return self.cache.get(key, **kwargs)
214
215     def cache(self):
216         return self.template.cache
217     cache = property(cache)
218
219     def include_file(self, uri, **kwargs):
220         """include a file at the given uri"""
221         _include_file(self.context, uri, self._templateuri, **kwargs)
222
223     def _populate(self, d, l):
224         for ident in l:
225             if ident == '*':
226                 for (k, v) in self._get_star():
227                     d[k] = v
228             else:
229                 d[ident] = getattr(self, ident)
230
231     def _get_star(self):
232         if self.callables:
233             for key in self.callables:
234                 yield (key, self.callables[key])
235         if self.template:
236             def get(key):
237                 callable_ = self.template.get_def(key).callable_
238                 return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
239             for k in self.template.module._exports:
240                 yield (k, get(k))
241         if self._module:
242             def get(key):
243                 callable_ = getattr(self._module, key)
244                 return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
245             for k in dir(self._module):
246                 if k[0] != '_':
247                     yield (k, get(k))
248
249     def __getattr__(self, key):
250         if self.callables and key in self.callables:
251             return self.callables[key]
252
253         if self.template and self.template.has_def(key):
254             callable_ = self.template.get_def(key).callable_
255             return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
256
257         if self._module and hasattr(self._module, key):
258             callable_ = getattr(self._module, key)
259             return lambda *args, **kwargs:callable_(self.context, *args, **kwargs)
260
261         if self.inherits is not None:
262             return getattr(self.inherits, key)
263         raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key))
264
265 def supports_caller(func):
266     """apply a caller_stack compatibility decorator to a plain Python function."""
267     def wrap_stackframe(context,  *args, **kwargs):
268         context.caller_stack._push_frame()
269         try:
270             return func(context, *args, **kwargs)
271         finally:
272             context.caller_stack._pop_frame()
273     return wrap_stackframe
274
275 def capture(context, callable_, *args, **kwargs):
276     """execute the given template def, capturing the output into a buffer."""
277     if not callable(callable_):
278         raise exceptions.RuntimeException("capture() function expects a callable as its argument (i.e. capture(func, *args, **kwargs))")
279     context._push_buffer()
280     try:
281         callable_(*args, **kwargs)
282     finally:
283         buf = context._pop_buffer()
284     return buf.getvalue()
285
286 def _include_file(context, uri, calling_uri, **kwargs):
287     """locate the template from the given uri and include it in the current output."""
288     template = _lookup_template(context, uri, calling_uri)
289     (callable_, ctx) = _populate_self_namespace(context._clean_inheritance_tokens(), template)
290     callable_(ctx, **_kwargs_for_callable(callable_, context._orig, **kwargs))
291
292 def _inherit_from(context, uri, calling_uri):
293     """called by the _inherit method in template modules to set up the inheritance chain at the start
294     of a template's execution."""
295     if uri is None:
296         return None
297     template = _lookup_template(context, uri, calling_uri)
298     self_ns = context['self']
299     ih = self_ns
300     while ih.inherits is not None:
301         ih = ih.inherits
302     lclcontext = context.locals_({'next':ih})
303     ih.inherits = Namespace("self:%s" % template.uri, lclcontext, template = template, populate_self=False)
304     context._data['parent'] = lclcontext._data['local'] = ih.inherits
305     callable_ = getattr(template.module, '_mako_inherit', None)
306     if callable_ is not None:
307         ret = callable_(template, lclcontext)
308         if ret:
309             return ret
310
311     gen_ns = getattr(template.module, '_mako_generate_namespaces', None)
312     if gen_ns is not None:
313         gen_ns(context)
314     return (template.callable_, lclcontext)
315
316 def _lookup_template(context, uri, relativeto):
317     lookup = context._with_template.lookup
318     if lookup is None:
319         raise exceptions.TemplateLookupException("Template '%s' has no TemplateLookup associated" % context._with_template.uri)
320     uri = lookup.adjust_uri(uri, relativeto)
321     try:
322         return lookup.get_template(uri)
323     except exceptions.TopLevelLookupException, e:
324         raise exceptions.TemplateLookupException(str(e))
325
326 def _populate_self_namespace(context, template, self_ns=None):
327     if self_ns is None:
328         self_ns = Namespace('self:%s' % template.uri, context, template=template, populate_self=False)
329     context._data['self'] = context._data['local'] = self_ns
330     if hasattr(template.module, '_mako_inherit'):
331         ret = template.module._mako_inherit(template, context)
332         if ret:
333             return ret
334     return (template.callable_, context)
335
336 def _render(template, callable_, args, data, as_unicode=False):
337     """create a Context and return the string output of the given template and template callable."""
338
339     if as_unicode:
340         buf = util.FastEncodingBuffer(unicode=True)
341     elif template.output_encoding:
342         buf = util.FastEncodingBuffer(unicode=as_unicode, encoding=template.output_encoding, errors=template.encoding_errors)
343     else:
344         buf = util.StringIO()
345     context = Context(buf, **data)
346     context._with_template = template
347     _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
348     return context._pop_buffer().getvalue()
349
350 def _kwargs_for_callable(callable_, data, **kwargs):
351     argspec = inspect.getargspec(callable_)
352     namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
353     for arg in namedargs:
354         if arg != 'context' and arg in data and arg not in kwargs:
355             kwargs[arg] = data[arg]
356     return kwargs
357
358 def _render_context(tmpl, callable_, context, *args, **kwargs):
359     import mako.template as template
360     # create polymorphic 'self' namespace for this template with possibly updated context
361     if not isinstance(tmpl, template.DefTemplate):
362         # if main render method, call from the base of the inheritance stack
363         (inherit, lclcontext) = _populate_self_namespace(context, tmpl)
364         _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
365     else:
366         # otherwise, call the actual rendering method specified
367         (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent)
368         _exec_template(callable_, context, args=args, kwargs=kwargs)
369
370 def _exec_template(callable_, context, args=None, kwargs=None):
371     """execute a rendering callable given the callable, a Context, and optional explicit arguments
372
373     the contextual Template will be located if it exists, and the error handling options specified
374     on that Template will be interpreted here.
375     """
376     template = context._with_template
377     if template is not None and (template.format_exceptions or template.error_handler):
378         error = None
379         try:
380             callable_(context, *args, **kwargs)
381         except Exception, e:
382             error = e
383         except:
384             e = sys.exc_info()[0]
385             error = e
386         if error:
387             if template.error_handler:
388                 result = template.error_handler(context, error)
389                 if not result:
390                     raise error
391             else:
392                 error_template = exceptions.html_error_template()
393                 context._buffer_stack[:] = [util.FastEncodingBuffer(error_template.output_encoding, error_template.encoding_errors)]
394                 context._with_template = error_template
395                 error_template.render_context(context, error=error)
396     else:
397         callable_(context, *args, **kwargs)