1 # -*- coding: utf-8 -*-
8 :copyright: Copyright 2008 by Armin Ronacher.
12 from collections import defaultdict
17 __all__ = ['subscribe', 'LoopContext', 'StaticLoopContext', 'TemplateContext',
18 'Macro', 'IncludedTemplate', 'Undefined']
21 def subscribe(obj, argument):
22 """Get an item or attribute of an object."""
24 return getattr(obj, str(argument))
25 except (AttributeError, UnicodeError):
28 except (TypeError, LookupError):
29 return Undefined(obj, argument)
32 class TemplateData(unicode):
33 """Marks data as "coming from the template". This is used to let the
34 system know that this data is already processed if a finalization is
41 class TemplateContext(dict):
42 """Holds the variables of the local template or of the global one. It's
43 not save to use this class outside of the compiled code. For example
44 update and other methods will not work as they seem (they don't update
45 the exported variables for example).
48 def __init__(self, globals, filename, blocks, standalone):
49 dict.__init__(self, globals)
51 self.filename = filename
52 self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
54 # if the template is in standalone mode we don't copy the blocks over.
55 # this is used for includes for example but otherwise, if the globals
56 # are a template context, this template is participating in a template
57 # inheritance chain and we have to copy the blocks over.
58 if not standalone and isinstance(globals, TemplateContext):
59 for name, parent_blocks in globals.blocks.iteritems():
60 self.blocks.setdefault(name, []).extend(parent_blocks)
62 def __setitem__(self, key, value):
63 """If we set items to the dict we track the variables set so
64 that includes can access the exported variables."""
65 dict.__setitem__(self, key, value)
66 self.exported.add(key)
68 def __delitem__(self, key):
69 """On delete we no longer export it."""
70 dict.__delitem__(self, key)
71 self.exported.dicard(key)
73 def get_exported(self):
74 """Get a dict of all exported variables."""
75 return dict((k, self[k]) for k in self.exported)
77 # if there is a default dict, dict has a __missing__ method we can use.
78 if defaultdict is None:
79 def __getitem__(self, name):
82 return Undefined(name)
84 def __missing__(self, key):
88 return '<%s %s of %r>' % (
89 self.__class__.__name__,
95 class IncludedTemplate(object):
96 """Represents an included template."""
98 def __init__(self, environment, context, template):
99 template = environment.get_template(template)
100 gen = template.root_render_func(context, standalone=True)
102 self._filename = template.name
103 self._rendered_body = u''.join(gen)
104 self._context = context.get_exported()
106 def __getitem__(self, name):
107 return self._context[name]
109 def __unicode__(self):
114 self.__class__.__name__,
119 class LoopContextBase(object):
120 """Helper for extended iteration."""
122 def __init__(self, iterable, parent=None):
123 self._iterable = iterable
128 first = property(lambda x: x.index0 == 0)
129 last = property(lambda x: x.revindex0 == 0)
130 index = property(lambda x: x.index0 + 1)
131 revindex = property(lambda x: x.length)
132 revindex0 = property(lambda x: x.length - 1)
133 length = property(lambda x: len(x))
136 class LoopContext(LoopContextBase):
137 """A loop context for dynamic iteration."""
139 def __init__(self, iterable, parent=None, enforce_length=False):
140 self._iterable = iterable
141 self._next = iter(iterable).next
148 def make_static(self):
149 """Return a static loop context for the optimizer."""
151 if self.parent is not None:
152 parent = self.parent.make_static()
153 return StaticLoopContext(self.index0, self.length, parent)
160 return self._next(), self
163 if self._length is None:
165 length = len(self._iterable)
167 self._iterable = tuple(self._iterable)
168 self._next = iter(self._iterable).next
169 length = len(tuple(self._iterable)) + self.index0 + 1
170 self._length = length
174 class StaticLoopContext(LoopContextBase):
175 """The static loop context is used in the optimizer to "freeze" the
176 status of an iteration. The only reason for this object is if the
177 loop object is accessed in a non static way (eg: becomes part of a
180 def __init__(self, index0, length, parent):
183 self._length = length
186 """The repr is used by the optimizer to dump the object."""
187 return 'StaticLoopContext(%r, %r, %r)' % (
193 def make_static(self):
200 def __init__(self, func, name, arguments, defaults, catch_all, caller):
203 self.arguments = arguments
204 self.defaults = defaults
205 self.catch_all = catch_all
208 def __call__(self, *args, **kwargs):
209 arg_count = len(self.arguments)
210 if len(args) > arg_count:
211 raise TypeError('macro %r takes not more than %d argument(s).' %
212 (self.name, len(self.arguments)))
214 for idx, name in enumerate(self.arguments):
219 value = kwargs.pop(name)
222 value = self.defaults[idx - arg_count]
224 value = Undefined(name, extra='parameter not provided')
225 arguments['l_' + name] = value
227 caller = kwargs.pop('caller', None)
229 caller = Undefined('caller', extra='The macro was called '
230 'from an expression and not a call block.')
231 arguments['l_caller'] = caller
233 arguments['l_arguments'] = kwargs
234 return TemplateData(u''.join(self._func(**arguments)))
238 self.__class__.__name__,
239 self.name is None and 'anonymous' or repr(self.name)
243 class Undefined(object):
244 """The object for undefined values."""
246 def __init__(self, name=None, attr=None, extra=None):
248 self._undefined_hint = '%r is undefined' % name
250 self._undefined_hint = 'attribute %r of %r is undefined' \
252 if extra is not None:
253 self._undefined_hint += ' (' + extra + ')'
255 def fail(self, *args, **kwargs):
256 raise TypeError(self._undefined_hint)
257 __getattr__ = __getitem__ = __add__ = __mul__ = __div__ = \
258 __realdiv__ = __floordiv__ = __mod__ = __pos__ = __neg__ = \
262 def __unicode__(self):