blob: 1bc196e4e9c8252f6de35140bd923e364efac270 [file] [log] [blame]
Armin Ronachere791c2a2008-04-07 18:39:54 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.runtime
4 ~~~~~~~~~~~~~~
5
6 Runtime helpers.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: GNU GPL.
10"""
11try:
12 from collections import defaultdict
13except ImportError:
14 defaultdict = None
15
16
Armin Ronacherf059ec12008-04-11 22:21:00 +020017__all__ = ['subscribe', 'LoopContext', 'StaticLoopContext', 'TemplateContext',
18 'Macro', 'IncludedTemplate', 'Undefined']
Armin Ronachere791c2a2008-04-07 18:39:54 +020019
20
Armin Ronacher4dfc9752008-04-09 15:03:29 +020021def subscribe(obj, argument):
Armin Ronacher8efc5222008-04-08 14:47:40 +020022 """Get an item or attribute of an object."""
23 try:
Armin Ronacher81b88172008-04-09 00:40:05 +020024 return getattr(obj, str(argument))
25 except (AttributeError, UnicodeError):
Armin Ronacher8efc5222008-04-08 14:47:40 +020026 try:
27 return obj[argument]
28 except LookupError:
Armin Ronacher4dfc9752008-04-09 15:03:29 +020029 return Undefined(obj, argument)
Armin Ronachere791c2a2008-04-07 18:39:54 +020030
31
Armin Ronacher8edbe492008-04-10 20:43:43 +020032class 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
35 used."""
36
37 def __html__(self):
38 return self
39
40
Armin Ronachere791c2a2008-04-07 18:39:54 +020041class TemplateContext(dict):
Armin Ronacher8edbe492008-04-10 20:43:43 +020042 """Holds the variables of the local template or of the global one. It's
Armin Ronacher9706fab2008-04-08 18:49:56 +020043 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).
46 """
Armin Ronachere791c2a2008-04-07 18:39:54 +020047
Armin Ronacherf059ec12008-04-11 22:21:00 +020048 def __init__(self, globals, filename, blocks, standalone):
Armin Ronacher9706fab2008-04-08 18:49:56 +020049 dict.__init__(self, globals)
50 self.exported = set()
Armin Ronachere791c2a2008-04-07 18:39:54 +020051 self.filename = filename
Armin Ronacher75cfb862008-04-11 13:47:22 +020052 self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
Armin Ronacherf059ec12008-04-11 22:21:00 +020053
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)
Armin Ronachere791c2a2008-04-07 18:39:54 +020061
Armin Ronacher9706fab2008-04-08 18:49:56 +020062 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)
67
68 def __delitem__(self, key):
69 """On delete we no longer export it."""
70 dict.__delitem__(self, key)
71 self.exported.dicard(key)
72
73 def get_exported(self):
74 """Get a dict of all exported variables."""
75 return dict((k, self[k]) for k in self.exported)
76
Armin Ronachere791c2a2008-04-07 18:39:54 +020077 # if there is a default dict, dict has a __missing__ method we can use.
78 if defaultdict is None:
79 def __getitem__(self, name):
80 if name in self:
81 return self[name]
Armin Ronacher4dfc9752008-04-09 15:03:29 +020082 return Undefined(name)
Armin Ronachere791c2a2008-04-07 18:39:54 +020083 else:
84 def __missing__(self, key):
Armin Ronacher4dfc9752008-04-09 15:03:29 +020085 return Undefined(key)
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020086
Armin Ronacherf059ec12008-04-11 22:21:00 +020087 def __repr__(self):
88 return '<%s %s of %r>' % (
89 self.__class__.__name__,
90 dict.__repr__(self),
91 self.filename
92 )
93
94
95class IncludedTemplate(object):
96 """Represents an included template."""
97
98 def __init__(self, environment, context, template):
Armin Ronacher7c0116f2008-04-12 00:06:19 +020099 template = environment.get_template(template)
100 gen = template.root_render_func(context, standalone=True)
101 context = gen.next()
102 self._filename = template.name
Armin Ronacherf059ec12008-04-11 22:21:00 +0200103 self._rendered_body = u''.join(gen)
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200104 self._context = context.get_exported()
Armin Ronacherf059ec12008-04-11 22:21:00 +0200105
106 def __getitem__(self, name):
107 return self._context[name]
108
109 def __unicode__(self):
110 return self._context
111
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200112 def __repr__(self):
113 return '<%s %r>' % (
114 self.__class__.__name__,
115 self._filename
116 )
117
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200118
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200119class LoopContextBase(object):
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200120 """Helper for extended iteration."""
121
122 def __init__(self, iterable, parent=None):
123 self._iterable = iterable
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200124 self._length = None
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200125 self.index0 = 0
126 self.parent = parent
127
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200128 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)
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200133 length = property(lambda x: len(x))
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200134
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200135
136class LoopContext(LoopContextBase):
Armin Ronacherf059ec12008-04-11 22:21:00 +0200137 """A loop context for dynamic iteration."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200138
139 def __init__(self, iterable, parent=None, enforce_length=False):
140 self._iterable = iterable
141 self._length = None
142 self.index0 = 0
143 self.parent = parent
144 if enforce_length:
145 len(self)
146
147 def make_static(self):
148 """Return a static loop context for the optimizer."""
149 parent = None
150 if self.parent is not None:
151 parent = self.parent.make_static()
152 return StaticLoopContext(self.index0, self.length, parent)
153
154 def __iter__(self):
155 for item in self._iterable:
156 yield self, item
157 self.index0 += 1
158
159 def __len__(self):
160 if self._length is None:
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200161 try:
162 length = len(self._iterable)
163 except TypeError:
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200164 self._iterable = tuple(self._iterable)
165 length = self.index0 + len(tuple(self._iterable))
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200166 self._length = length
167 return self._length
168
169
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200170class StaticLoopContext(LoopContextBase):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200171 """The static loop context is used in the optimizer to "freeze" the
172 status of an iteration. The only reason for this object is if the
173 loop object is accessed in a non static way (eg: becomes part of a
174 function call)."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200175
176 def __init__(self, index0, length, parent):
177 self.index0 = index0
178 self.parent = parent
179 self._length = length
180
181 def __repr__(self):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200182 """The repr is used by the optimizer to dump the object."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200183 return 'StaticLoopContext(%r, %r, %r)' % (
184 self.index0,
185 self._length,
186 self.parent
187 )
188
189 def make_static(self):
190 return self
191
192
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200193class Macro(object):
Armin Ronacherd55ab532008-04-09 16:13:39 +0200194 """Wraps a macro."""
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200195
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200196 def __init__(self, func, name, arguments, defaults, catch_all):
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200197 self.func = func
198 self.name = name
199 self.arguments = arguments
200 self.defaults = defaults
201 self.catch_all = catch_all
202
203 def __call__(self, *args, **kwargs):
Armin Ronacher9706fab2008-04-08 18:49:56 +0200204 arg_count = len(self.arguments)
205 if len(args) > arg_count:
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200206 raise TypeError('macro %r takes not more than %d argument(s).' %
207 (self.name, len(self.arguments)))
208 arguments = {}
Armin Ronacher9706fab2008-04-08 18:49:56 +0200209 for idx, name in enumerate(self.arguments):
210 try:
211 value = args[idx]
212 except IndexError:
213 try:
214 value = kwargs.pop(name)
215 except KeyError:
216 try:
217 value = self.defaults[idx - arg_count]
218 except IndexError:
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200219 value = Undefined(name)
Christoph Hackf9f029c2008-04-09 15:08:11 +0200220 arguments['l_' + name] = value
Armin Ronacher9706fab2008-04-08 18:49:56 +0200221 if self.catch_all:
222 arguments['l_arguments'] = kwargs
Armin Ronacher8edbe492008-04-10 20:43:43 +0200223 return TemplateData(u''.join(self.func(**arguments)))
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200224
225
226class Undefined(object):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200227 """The object for undefined values."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200228
229 def __init__(self, name=None, attr=None):
230 if attr is None:
231 self._undefined_hint = '%r is undefined' % attr
232 elif name is None:
233 self._undefined_hint = 'attribute %r is undefined' % name
234 else:
235 self._undefined_hint = 'attribute %r of %r is undefined' \
236 % (attr, name)
237
238 def fail(self, *args, **kwargs):
239 raise TypeError(self._undefined_hint)
240 __getattr__ = __getitem__ = __add__ = __mul__ = __div__ = \
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200241 __realdiv__ = __floordiv__ = __mod__ = __pos__ = __neg__ = \
242 __call__ = fail
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200243 del fail
244
245 def __unicode__(self):
246 return ''
247
248 def __repr__(self):
249 return 'Undefined'
250
251 def __len__(self):
252 return 0
253
254 def __iter__(self):
255 if 0:
256 yield None