[svn] doc update, setup.py update, added turbogears plugin
[jinja2.git] / jinja / datastructure.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja.datastructure
4     ~~~~~~~~~~~~~~~~~~~
5
6     Module that helds several data types used in the template engine.
7
8     :copyright: 2007 by Armin Ronacher.
9     :license: BSD, see LICENSE for more details.
10 """
11
12 # python2.3 compatibility. do not use this method for anything else
13 # then context reversing.
14 try:
15     _reversed = reversed
16 except NameError:
17     def _reversed(c):
18         return c[::-1]
19
20 # sets
21 try:
22     set
23 except NameError:
24     from sets import Set as set
25
26
27 class UndefinedType(object):
28     """
29     An object that does not exist.
30     """
31     __slots__ = ()
32
33     def __init__(self):
34         try:
35             Undefined
36         except NameError:
37             pass
38         else:
39             raise TypeError('cannot create %r instances' %
40                             self.__class__.__name__)
41
42     def __getitem__(self, arg):
43         return self
44
45     def __iter__(self):
46         return iter(int, 0)
47
48     def __getattr__(self, arg):
49         return self
50
51     def __nonzero__(self):
52         return False
53
54     def __len__(self):
55         return 0
56
57     def __str__(self):
58         return ''
59
60     def __unicode__(self):
61         return u''
62
63     def __int__(self):
64         return 0
65
66     def __float__(self):
67         return 1
68
69
70 Undefined = UndefinedType()
71
72
73 class Deferred(object):
74     """
75     Object marking an deferred value. Deferred objects are
76     objects that are called first access in the context.
77     """
78
79     def __init__(self, factory):
80         self.factory = factory
81
82     def __call__(self, context, name):
83         return self.factory(context, name)
84
85
86 class Markup(unicode):
87     """
88     Mark a string as safe for XML. If the environment uses the
89     auto_escape option values marked as `Markup` aren't escaped.
90     """
91
92     def __repr__(self):
93         return 'Markup(%s)' % unicode.__repr__(self)
94
95
96 safe_types = set([Markup, int, long, float])
97
98
99 class Context(object):
100     """
101     Dict like object.
102     """
103
104     def __init__(self, _environment_, *args, **kwargs):
105         self.environment = _environment_
106         self._stack = [self.environment.globals, dict(*args, **kwargs), {}]
107         self.globals, self.initial, self.current = self._stack
108
109         # cache object used for filters and tests
110         self.cache = {}
111
112     def pop(self):
113         if len(self._stack) <= 2:
114             raise ValueError('cannot pop initial layer')
115         rv = self._stack.pop()
116         self.current = self._stack[-1]
117         return rv
118
119     def push(self, data=None):
120         self._stack.append(data or {})
121         self.current = self._stack[-1]
122
123     def to_dict(self):
124         """
125         Convert the context into a dict. This skips the globals.
126         """
127         result = {}
128         for layer in self._stack[1:]:
129             for key, value in layer.iteritems():
130                 result[key] = value
131         return result
132
133     def __getitem__(self, name):
134         # don't give access to jinja internal variables
135         if name.startswith('::'):
136             return Undefined
137         for d in _reversed(self._stack):
138             if name in d:
139                 rv = d[name]
140                 if isinstance(rv, Deferred):
141                     rv = rv(self, name)
142                     # never tough the globals!
143                     if d is self.globals:
144                         self.initial[name] = rv
145                     else:
146                         d[name] = rv
147                 return rv
148         return Undefined
149
150     def __setitem__(self, name, value):
151         self._stack[-1][name] = value
152
153     def __delitem__(self, name):
154         if name in self._stack[-1]:
155             del self._stack[-1][name]
156
157     def __repr__(self):
158         tmp = {}
159         for d in self._stack:
160             for key, value in d.iteritems():
161                 tmp[key] = value
162         return 'Context(%s)' % repr(tmp)
163
164
165 class LoopContext(object):
166     """
167     Simple class that provides special loop variables.
168     Used by `Environment.iterate`.
169     """
170
171     jinja_allowed_attributes = ['index', 'index0', 'length', 'parent',
172                                 'even', 'odd', 'revindex0', 'revindex',
173                                 'first', 'last']
174
175     def __init__(self, seq, parent, loop_function):
176         self.loop_function = loop_function
177         self.parent = parent
178         self._stack = []
179         if seq is not None:
180             self.push(seq)
181
182     def push(self, seq):
183         self._stack.append({
184             'index':            -1,
185             'seq':              seq,
186             'length':           len(seq)
187         })
188
189     def pop(self):
190         return self._stack.pop()
191
192     iterated = property(lambda s: s._stack[-1]['index'] > -1)
193     index0 = property(lambda s: s._stack[-1]['index'])
194     index = property(lambda s: s._stack[-1]['index'] + 1)
195     revindex0 = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index'] - 1)
196     revindex = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index'])
197     length = property(lambda s: s._stack[-1]['length'])
198     even = property(lambda s: s._stack[-1]['index'] % 2 == 0)
199     odd = property(lambda s: s._stack[-1]['index'] % 2 == 1)
200     first = property(lambda s: s._stack[-1]['index'] == 0)
201     last = property(lambda s: s._stack[-1]['index'] == s._stack[-1]['length'] - 1)
202
203     def __iter__(self):
204         s = self._stack[-1]
205         for idx, item in enumerate(s['seq']):
206             s['index'] = idx
207             yield item
208
209     def __len__(self):
210         return self._stack[-1]['length']
211
212     def __call__(self, seq):
213         if self.loop_function is not None:
214             return self.loop_function(seq)
215         return Undefined
216
217
218 class CycleContext(object):
219     """
220     Helper class used for cycling.
221     """
222
223     def __init__(self, seq=None):
224         self.lineno = -1
225         if seq is not None:
226             self.seq = seq
227             self.length = len(seq)
228             self.cycle = self.cycle_static
229         else:
230             self.cycle = self.cycle_dynamic
231
232     def cycle_static(self):
233         self.lineno = (self.lineno + 1) % self.length
234         return self.seq[self.lineno]
235
236     def cycle_dynamic(self, seq):
237         self.lineno = (self.lineno + 1) % len(seq)
238         return seq[self.lineno]
239
240
241 class TokenStream(object):
242     """
243     A token stream works like a normal generator just that
244     it supports pushing tokens back to the stream.
245     """
246
247     def __init__(self, generator):
248         self._generator = generator
249         self._pushed = []
250         self.last = (1, 'initial', '')
251
252     def __iter__(self):
253         return self
254
255     def __nonzero__(self):
256         """Are we at the end of the tokenstream?"""
257         if self._pushed:
258             return True
259         try:
260             self.push(self.next())
261         except StopIteration:
262             return False
263         return True
264
265     eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
266
267     def next(self):
268         """Return the next token from the stream."""
269         if self._pushed:
270             rv = self._pushed.pop()
271         else:
272             rv = self._generator.next()
273         self.last = rv
274         return rv
275
276     def look(self):
277         """Pop and push a token, return it."""
278         token = self.next()
279         self.push(*token)
280         return token
281
282     def fetch_until(self, test, drop_needle=False):
283         """Fetch tokens until a function matches."""
284         try:
285             while True:
286                 token = self.next()
287                 if test(*token):
288                     if not drop_needle:
289                         self.push(*token)
290                     return
291                 else:
292                     yield token
293         except StopIteration:
294             raise IndexError('end of stream reached')
295
296     def push(self, lineno, token, data):
297         """Push an yielded token back to the stream."""
298         self._pushed.append((lineno, token, data))