blob: 0e4200500d1f6879a435025c9bd858dc5c1baf72 [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 Ronacherd1d2f3d2008-04-09 14:02:55 +020017__all__ = ['extends', 'subscribe', 'LoopContext', 'StaticLoopContext',
Armin Ronacher4dfc9752008-04-09 15:03:29 +020018 'TemplateContext', 'Macro', 'Undefined']
Armin Ronachere791c2a2008-04-07 18:39:54 +020019
20
21def extends(template, namespace):
Armin Ronacher8efc5222008-04-08 14:47:40 +020022 """This loads a template (and evaluates it) and replaces the blocks."""
23
24
Armin Ronacher4dfc9752008-04-09 15:03:29 +020025def subscribe(obj, argument):
Armin Ronacher8efc5222008-04-08 14:47:40 +020026 """Get an item or attribute of an object."""
27 try:
Armin Ronacher81b88172008-04-09 00:40:05 +020028 return getattr(obj, str(argument))
29 except (AttributeError, UnicodeError):
Armin Ronacher8efc5222008-04-08 14:47:40 +020030 try:
31 return obj[argument]
32 except LookupError:
Armin Ronacher4dfc9752008-04-09 15:03:29 +020033 return Undefined(obj, argument)
Armin Ronachere791c2a2008-04-07 18:39:54 +020034
35
36class TemplateContext(dict):
Armin Ronacher9706fab2008-04-08 18:49:56 +020037 """
38 Holds the variables of the local template or of the global one. It's
39 not save to use this class outside of the compiled code. For example
40 update and other methods will not work as they seem (they don't update
41 the exported variables for example).
42 """
Armin Ronachere791c2a2008-04-07 18:39:54 +020043
Armin Ronacher4dfc9752008-04-09 15:03:29 +020044 def __init__(self, globals, filename):
Armin Ronacher9706fab2008-04-08 18:49:56 +020045 dict.__init__(self, globals)
46 self.exported = set()
Armin Ronachere791c2a2008-04-07 18:39:54 +020047 self.filename = filename
Armin Ronacher8efc5222008-04-08 14:47:40 +020048 self.filters = {}
49 self.tests = {}
Armin Ronachere791c2a2008-04-07 18:39:54 +020050
Armin Ronacher9706fab2008-04-08 18:49:56 +020051 def __setitem__(self, key, value):
52 """If we set items to the dict we track the variables set so
53 that includes can access the exported variables."""
54 dict.__setitem__(self, key, value)
55 self.exported.add(key)
56
57 def __delitem__(self, key):
58 """On delete we no longer export it."""
59 dict.__delitem__(self, key)
60 self.exported.dicard(key)
61
62 def get_exported(self):
63 """Get a dict of all exported variables."""
64 return dict((k, self[k]) for k in self.exported)
65
Armin Ronachere791c2a2008-04-07 18:39:54 +020066 # if there is a default dict, dict has a __missing__ method we can use.
67 if defaultdict is None:
68 def __getitem__(self, name):
69 if name in self:
70 return self[name]
Armin Ronacher4dfc9752008-04-09 15:03:29 +020071 return Undefined(name)
Armin Ronachere791c2a2008-04-07 18:39:54 +020072 else:
73 def __missing__(self, key):
Armin Ronacher4dfc9752008-04-09 15:03:29 +020074 return Undefined(key)
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020075
76
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +020077class LoopContextBase(object):
Armin Ronacher180a1bd2008-04-09 12:14:24 +020078 """Helper for extended iteration."""
79
80 def __init__(self, iterable, parent=None):
81 self._iterable = iterable
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +020082 self._length = None
Armin Ronacher180a1bd2008-04-09 12:14:24 +020083 self.index0 = 0
84 self.parent = parent
85
Armin Ronacher180a1bd2008-04-09 12:14:24 +020086 first = property(lambda x: x.index0 == 0)
87 last = property(lambda x: x.revindex0 == 0)
88 index = property(lambda x: x.index0 + 1)
89 revindex = property(lambda x: x.length)
90 revindex0 = property(lambda x: x.length - 1)
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +020091 length = property(lambda x: len(x))
Armin Ronacher180a1bd2008-04-09 12:14:24 +020092
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +020093
94class LoopContext(LoopContextBase):
95
96 def __init__(self, iterable, parent=None, enforce_length=False):
97 self._iterable = iterable
98 self._length = None
99 self.index0 = 0
100 self.parent = parent
101 if enforce_length:
102 len(self)
103
104 def make_static(self):
105 """Return a static loop context for the optimizer."""
106 parent = None
107 if self.parent is not None:
108 parent = self.parent.make_static()
109 return StaticLoopContext(self.index0, self.length, parent)
110
111 def __iter__(self):
112 for item in self._iterable:
113 yield self, item
114 self.index0 += 1
115
116 def __len__(self):
117 if self._length is None:
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200118 try:
119 length = len(self._iterable)
120 except TypeError:
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200121 self._iterable = tuple(self._iterable)
122 length = self.index0 + len(tuple(self._iterable))
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200123 self._length = length
124 return self._length
125
126
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200127class StaticLoopContext(LoopContextBase):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200128 """The static loop context is used in the optimizer to "freeze" the
129 status of an iteration. The only reason for this object is if the
130 loop object is accessed in a non static way (eg: becomes part of a
131 function call)."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200132
133 def __init__(self, index0, length, parent):
134 self.index0 = index0
135 self.parent = parent
136 self._length = length
137
138 def __repr__(self):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200139 """The repr is used by the optimizer to dump the object."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200140 return 'StaticLoopContext(%r, %r, %r)' % (
141 self.index0,
142 self._length,
143 self.parent
144 )
145
146 def make_static(self):
147 return self
148
149
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200150class Macro(object):
Armin Ronacher9706fab2008-04-08 18:49:56 +0200151 """
152 Wraps a macor
153 """
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200154
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200155 def __init__(self, func, name, arguments, defaults, catch_all):
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200156 self.func = func
157 self.name = name
158 self.arguments = arguments
159 self.defaults = defaults
160 self.catch_all = catch_all
161
162 def __call__(self, *args, **kwargs):
Armin Ronacher9706fab2008-04-08 18:49:56 +0200163 arg_count = len(self.arguments)
164 if len(args) > arg_count:
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200165 raise TypeError('macro %r takes not more than %d argument(s).' %
166 (self.name, len(self.arguments)))
167 arguments = {}
Armin Ronacher9706fab2008-04-08 18:49:56 +0200168 for idx, name in enumerate(self.arguments):
169 try:
170 value = args[idx]
171 except IndexError:
172 try:
173 value = kwargs.pop(name)
174 except KeyError:
175 try:
176 value = self.defaults[idx - arg_count]
177 except IndexError:
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200178 value = Undefined(name)
Armin Ronacher9706fab2008-04-08 18:49:56 +0200179 arguments['l_' + name] = arg
180 if self.catch_all:
181 arguments['l_arguments'] = kwargs
182 return u''.join(self.func(**arguments))
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200183
184
185class Undefined(object):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200186 """The object for undefined values."""
Armin Ronacherd1d2f3d2008-04-09 14:02:55 +0200187
188 def __init__(self, name=None, attr=None):
189 if attr is None:
190 self._undefined_hint = '%r is undefined' % attr
191 elif name is None:
192 self._undefined_hint = 'attribute %r is undefined' % name
193 else:
194 self._undefined_hint = 'attribute %r of %r is undefined' \
195 % (attr, name)
196
197 def fail(self, *args, **kwargs):
198 raise TypeError(self._undefined_hint)
199 __getattr__ = __getitem__ = __add__ = __mul__ = __div__ = \
200 __realdiv__ = __floordiv__ = __mod__ = __pos__ = __neg__ = fail
201 del fail
202
203 def __unicode__(self):
204 return ''
205
206 def __repr__(self):
207 return 'Undefined'
208
209 def __len__(self):
210 return 0
211
212 def __iter__(self):
213 if 0:
214 yield None