1 # -*- coding: utf-8 -*-
6 Module that helds several data types used in the template engine.
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
12 # python2.3 compatibility. do not use this method for anything else
13 # then context reversing.
24 from sets import Set as set
27 class UndefinedType(object):
29 An object that does not exist.
39 raise TypeError('cannot create %r instances' %
40 self.__class__.__name__)
42 def __getitem__(self, arg):
48 def __getattr__(self, arg):
51 def __nonzero__(self):
60 def __unicode__(self):
70 Undefined = UndefinedType()
73 class Deferred(object):
75 Object marking an deferred value. Deferred objects are
76 objects that are called first access in the context.
79 def __init__(self, factory):
80 self.factory = factory
82 def __call__(self, context, name):
83 return self.factory(context, name)
86 class Markup(unicode):
88 Mark a string as safe for XML. If the environment uses the
89 auto_escape option values marked as `Markup` aren't escaped.
93 return 'Markup(%s)' % unicode.__repr__(self)
96 safe_types = set([Markup, int, long, float])
99 class Context(object):
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
109 # cache object used for filters and tests
113 if len(self._stack) <= 2:
114 raise ValueError('cannot pop initial layer')
115 rv = self._stack.pop()
116 self.current = self._stack[-1]
119 def push(self, data=None):
120 self._stack.append(data or {})
121 self.current = self._stack[-1]
125 Convert the context into a dict. This skips the globals.
128 for layer in self._stack[1:]:
129 for key, value in layer.iteritems():
133 def __getitem__(self, name):
134 # don't give access to jinja internal variables
135 if name.startswith('::'):
137 for d in _reversed(self._stack):
140 if isinstance(rv, Deferred):
142 # never tough the globals!
143 if d is self.globals:
144 self.initial[name] = rv
150 def __setitem__(self, name, value):
151 self._stack[-1][name] = value
153 def __delitem__(self, name):
154 if name in self._stack[-1]:
155 del self._stack[-1][name]
159 for d in self._stack:
160 for key, value in d.iteritems():
162 return 'Context(%s)' % repr(tmp)
165 class LoopContext(object):
167 Simple class that provides special loop variables.
168 Used by `Environment.iterate`.
171 jinja_allowed_attributes = ['index', 'index0', 'length', 'parent',
172 'even', 'odd', 'revindex0', 'revindex',
175 def __init__(self, seq, parent, loop_function):
176 self.loop_function = loop_function
190 return self._stack.pop()
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)
205 for idx, item in enumerate(s['seq']):
210 return self._stack[-1]['length']
212 def __call__(self, seq):
213 if self.loop_function is not None:
214 return self.loop_function(seq)
218 class CycleContext(object):
220 Helper class used for cycling.
223 def __init__(self, seq=None):
227 self.length = len(seq)
228 self.cycle = self.cycle_static
230 self.cycle = self.cycle_dynamic
232 def cycle_static(self):
233 self.lineno = (self.lineno + 1) % self.length
234 return self.seq[self.lineno]
236 def cycle_dynamic(self, seq):
237 self.lineno = (self.lineno + 1) % len(seq)
238 return seq[self.lineno]
241 class TokenStream(object):
243 A token stream works like a normal generator just that
244 it supports pushing tokens back to the stream.
247 def __init__(self, generator):
248 self._generator = generator
250 self.last = (1, 'initial', '')
255 def __nonzero__(self):
256 """Are we at the end of the tokenstream?"""
260 self.push(self.next())
261 except StopIteration:
265 eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
268 """Return the next token from the stream."""
270 rv = self._pushed.pop()
272 rv = self._generator.next()
277 """Pop and push a token, return it."""
282 def fetch_until(self, test, drop_needle=False):
283 """Fetch tokens until a function matches."""
293 except StopIteration:
294 raise IndexError('end of stream reached')
296 def push(self, lineno, token, data):
297 """Push an yielded token back to the stream."""
298 self._pushed.append((lineno, token, data))