blob: ba78148073b8ae7b3821975c70b4be051ff77e32 [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.utils
4 ~~~~~~~~~~~~
5
6 Utility functions.
7
Armin Ronacher55494e42010-01-22 09:41:48 +01008 :copyright: (c) 2010 by the Jinja Team.
Armin Ronacher07bc6842008-03-31 14:18:49 +02009 :license: BSD, see LICENSE for more details.
10"""
Christoph Hack80909862008-04-14 01:35:10 +020011import re
Benjamin Wiegand96828552008-05-03 22:27:29 +020012import sys
Armin Ronacherccae0552008-10-05 23:08:58 +020013import errno
Armin Ronacher000b4912008-05-01 18:40:15 +020014try:
Armin Ronacher1d4c6382012-01-07 17:46:40 +010015 from urllib.parse import quote_from_bytes as url_quote
16except ImportError:
17 from urllib import quote as url_quote
18try:
Armin Ronacher000b4912008-05-01 18:40:15 +020019 from thread import allocate_lock
20except ImportError:
Thomas Waldmann7d295622013-05-18 00:06:22 +020021 try:
22 from _thread import allocate_lock # py 3
23 except ImportError:
24 from dummy_thread import allocate_lock
Armin Ronacher814f6c22008-04-17 15:52:23 +020025from collections import deque
Armin Ronacher28c74882013-05-20 01:51:26 +010026from jinja2._compat import text_type, string_types, implements_iterator, PY2
Armin Ronachere9098672013-05-19 14:16:13 +010027
Armin Ronacher8edbe492008-04-10 20:43:43 +020028
Armin Ronacherbe4ae242008-04-18 09:49:08 +020029_word_split_re = re.compile(r'(\s+)')
30_punctuation_re = re.compile(
31 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
Thomas Waldmann7d295622013-05-18 00:06:22 +020032 '|'.join(map(re.escape, ('(', '<', '&lt;'))),
33 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
Armin Ronacherbe4ae242008-04-18 09:49:08 +020034 )
35)
36_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
Armin Ronacher76c280b2008-05-04 12:31:48 +020037_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
38_entity_re = re.compile(r'&([^;]+);')
Armin Ronacher9a0078d2008-08-13 18:24:17 +020039_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
40_digits = '0123456789'
Armin Ronacherbe4ae242008-04-18 09:49:08 +020041
Armin Ronacher7259c762008-04-30 13:03:59 +020042# special singleton representing missing values for the runtime
43missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
44
Armin Ronacherd416a972009-02-24 22:58:00 +010045# internal code
46internal_code = set()
47
Thomas Waldmann07a7d552013-05-18 00:47:41 +020048concat = u''.join
Armin Ronacher7ceced52008-05-03 10:15:31 +020049
50
Armin Ronacher0d242be2010-02-10 01:35:13 +010051# if this python version is unable to deal with unicode filenames
52# when passed to encode we let this function encode it properly.
53# This is used in a couple of places. As far as Jinja is concerned
54# filenames are unicode *or* bytestrings in 2.x and unicode only in
55# 3.x because compile cannot handle bytes
Armin Ronacher7e245e22013-05-19 14:34:54 +010056if PY2:
Armin Ronacher0d242be2010-02-10 01:35:13 +010057 def _encode_filename(filename):
58 if isinstance(filename, unicode):
59 return filename.encode('utf-8')
60 return filename
61else:
62 def _encode_filename(filename):
63 assert filename is None or isinstance(filename, str), \
64 'filenames must be strings'
65 return filename
66
67from keyword import iskeyword as is_python_keyword
Armin Ronacher9a0078d2008-08-13 18:24:17 +020068
69
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020070def contextfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +020071 """This decorator can be used to mark a function or method context callable.
72 A context callable is passed the active :class:`Context` as first argument when
73 called from the template. This is useful if a function wants to get access
74 to the context or functions provided on the context object. For example
75 a function that returns a sorted list of template variables the current
76 template exports could look like this::
77
Armin Ronacher58f351d2008-05-28 21:30:14 +020078 @contextfunction
Armin Ronacher9bb7e472008-05-28 11:26:59 +020079 def get_exported_names(context):
80 return sorted(context.exported_vars)
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020081 """
82 f.contextfunction = True
83 return f
84
85
Armin Ronacher8346bd72010-03-14 19:43:47 +010086def evalcontextfunction(f):
Florent Xicluna0ec4f762012-02-05 13:09:15 +010087 """This decorator can be used to mark a function or method as an eval
Armin Ronacher8346bd72010-03-14 19:43:47 +010088 context callable. This is similar to the :func:`contextfunction`
89 but instead of passing the context, an evaluation context object is
Armin Ronacherfe150f32010-03-15 02:42:41 +010090 passed. For more information about the eval context, see
91 :ref:`eval-context`.
Armin Ronacher8346bd72010-03-14 19:43:47 +010092
93 .. versionadded:: 2.4
94 """
95 f.evalcontextfunction = True
96 return f
97
98
Armin Ronacher203bfcb2008-04-24 21:54:44 +020099def environmentfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200100 """This decorator can be used to mark a function or method as environment
101 callable. This decorator works exactly like the :func:`contextfunction`
102 decorator just that the first argument is the active :class:`Environment`
103 and not context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200104 """
105 f.environmentfunction = True
106 return f
107
108
Armin Ronacherd416a972009-02-24 22:58:00 +0100109def internalcode(f):
110 """Marks the function as internally used"""
Thomas Waldmanne0003552013-05-17 23:52:14 +0200111 internal_code.add(f.__code__)
Armin Ronacherd416a972009-02-24 22:58:00 +0100112 return f
113
114
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200115def is_undefined(obj):
116 """Check if the object passed is undefined. This does nothing more than
117 performing an instance check against :class:`Undefined` but looks nicer.
118 This can be used for custom filters or tests that want to react to
119 undefined variables. For example a custom default filter can look like
120 this::
121
122 def default(var, default=''):
123 if is_undefined(var):
124 return default
125 return var
126 """
127 from jinja2.runtime import Undefined
128 return isinstance(obj, Undefined)
129
130
Armin Ronacherba6e25a2008-11-02 15:58:14 +0100131def consume(iterable):
132 """Consumes an iterable without doing anything with it."""
133 for event in iterable:
134 pass
135
136
Armin Ronacher187bde12008-05-01 18:19:16 +0200137def clear_caches():
138 """Jinja2 keeps internal caches for environments and lexers. These are
139 used so that Jinja2 doesn't have to recreate environments and lexers all
140 the time. Normally you don't have to care about that but if you are
141 messuring memory consumption you may want to clean the caches.
142 """
143 from jinja2.environment import _spontaneous_environments
144 from jinja2.lexer import _lexer_cache
145 _spontaneous_environments.clear()
146 _lexer_cache.clear()
147
148
Armin Ronacherf59bac22008-04-20 13:11:43 +0200149def import_string(import_name, silent=False):
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100150 """Imports an object based on a string. This is useful if you want to
Armin Ronacherf59bac22008-04-20 13:11:43 +0200151 use import paths as endpoints or something similar. An import path can
152 be specified either in dotted notation (``xml.sax.saxutils.escape``)
153 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
154
155 If the `silent` is True the return value will be `None` if the import
156 fails.
157
158 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200159 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200160 try:
161 if ':' in import_name:
162 module, obj = import_name.split(':', 1)
163 elif '.' in import_name:
164 items = import_name.split('.')
165 module = '.'.join(items[:-1])
166 obj = items[-1]
167 else:
168 return __import__(import_name)
169 return getattr(__import__(module, None, None, [obj]), obj)
170 except (ImportError, AttributeError):
171 if not silent:
172 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200173
174
Armin Ronacher0faa8612010-02-09 15:04:51 +0100175def open_if_exists(filename, mode='rb'):
Armin Ronacherccae0552008-10-05 23:08:58 +0200176 """Returns a file descriptor for the filename if that file exists,
177 otherwise `None`.
178 """
179 try:
Armin Ronacher790b8a82010-02-10 00:05:46 +0100180 return open(filename, mode)
Thomas Waldmanne0003552013-05-17 23:52:14 +0200181 except IOError as e:
Armin Ronacherccae0552008-10-05 23:08:58 +0200182 if e.errno not in (errno.ENOENT, errno.EISDIR):
183 raise
184
185
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200186def object_type_repr(obj):
187 """Returns the name of the object's type. For some recognized
188 singletons the name of the object is returned instead. (For
189 example for `None` and `Ellipsis`).
190 """
191 if obj is None:
192 return 'None'
193 elif obj is Ellipsis:
194 return 'Ellipsis'
Armin Ronacher802f4722010-04-20 19:48:46 +0200195 # __builtin__ in 2.x, builtins in 3.x
196 if obj.__class__.__module__ in ('__builtin__', 'builtins'):
Armin Ronacher800ac7f2010-04-20 13:45:11 +0200197 name = obj.__class__.__name__
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200198 else:
Armin Ronacher800ac7f2010-04-20 13:45:11 +0200199 name = obj.__class__.__module__ + '.' + obj.__class__.__name__
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200200 return '%s object' % name
201
202
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200203def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200204 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200205 builtin `pprint`.
206 """
207 try:
208 from pretty import pretty
209 return pretty(obj, verbose=verbose)
210 except ImportError:
211 from pprint import pformat
212 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200213
214
Christoph Hack80909862008-04-14 01:35:10 +0200215def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200216 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200217 https:// and www. links. Links can have trailing punctuation (periods,
218 commas, close-parens) and leading punctuation (opening parens) and
219 it'll still do the right thing.
220
221 If trim_url_limit is not None, the URLs in link text will be limited
222 to trim_url_limit characters.
223
224 If nofollow is True, the URLs in link text will get a rel="nofollow"
225 attribute.
226 """
227 trim_url = lambda x, limit=trim_url_limit: limit is not None \
228 and (x[:limit] + (len(x) >=limit and '...'
229 or '')) or x
Armin Ronachere9098672013-05-19 14:16:13 +0100230 words = _word_split_re.split(text_type(escape(text)))
Christoph Hack80909862008-04-14 01:35:10 +0200231 nofollow_attr = nofollow and ' rel="nofollow"' or ''
232 for i, word in enumerate(words):
233 match = _punctuation_re.match(word)
234 if match:
235 lead, middle, trail = match.groups()
236 if middle.startswith('www.') or (
237 '@' not in middle and
238 not middle.startswith('http://') and
mozillazg66448932013-03-18 14:27:54 +0800239 not middle.startswith('https://') and
Christoph Hack80909862008-04-14 01:35:10 +0200240 len(middle) > 0 and
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200241 middle[0] in _letters + _digits and (
Christoph Hack80909862008-04-14 01:35:10 +0200242 middle.endswith('.org') or
243 middle.endswith('.net') or
244 middle.endswith('.com')
245 )):
246 middle = '<a href="http://%s"%s>%s</a>' % (middle,
247 nofollow_attr, trim_url(middle))
248 if middle.startswith('http://') or \
249 middle.startswith('https://'):
250 middle = '<a href="%s"%s>%s</a>' % (middle,
251 nofollow_attr, trim_url(middle))
252 if '@' in middle and not middle.startswith('www.') and \
253 not ':' in middle and _simple_email_re.match(middle):
254 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
255 if lead + middle + trail != word:
256 words[i] = lead + middle + trail
257 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200258
259
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200260def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
261 """Generate some lorem impsum for the template."""
262 from jinja2.constants import LOREM_IPSUM_WORDS
Georg Brandl95632c42009-11-22 18:35:18 +0100263 from random import choice, randrange
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200264 words = LOREM_IPSUM_WORDS.split()
265 result = []
266
Thomas Waldmanne0003552013-05-17 23:52:14 +0200267 for _ in range(n):
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200268 next_capitalized = True
269 last_comma = last_fullstop = 0
270 word = None
271 last = None
272 p = []
273
274 # each paragraph contains out of 20 to 100 words.
Thomas Waldmanne0003552013-05-17 23:52:14 +0200275 for idx, _ in enumerate(range(randrange(min, max))):
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200276 while True:
277 word = choice(words)
278 if word != last:
279 last = word
280 break
281 if next_capitalized:
282 word = word.capitalize()
283 next_capitalized = False
284 # add commas
285 if idx - randrange(3, 8) > last_comma:
286 last_comma = idx
287 last_fullstop += 2
288 word += ','
289 # add end of sentences
290 if idx - randrange(10, 20) > last_fullstop:
291 last_comma = last_fullstop = idx
292 word += '.'
293 next_capitalized = True
294 p.append(word)
295
296 # ensure that the paragraph ends with a dot.
297 p = u' '.join(p)
298 if p.endswith(','):
299 p = p[:-1] + '.'
300 elif not p.endswith('.'):
301 p += '.'
302 result.append(p)
303
304 if not html:
305 return u'\n\n'.join(result)
306 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
307
308
Armin Ronacher51454012012-01-07 17:47:56 +0100309def unicode_urlencode(obj, charset='utf-8'):
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100310 """URL escapes a single bytestring or unicode string with the
311 given charset if applicable to URL safe quoting under all rules
312 that need to be considered under all supported Python versions.
313
314 If non strings are provided they are converted to their unicode
315 representation first.
316 """
Armin Ronachere9098672013-05-19 14:16:13 +0100317 if not isinstance(obj, string_types):
318 obj = text_type(obj)
319 if isinstance(obj, text_type):
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100320 obj = obj.encode(charset)
Armin Ronachere9098672013-05-19 14:16:13 +0100321 return text_type(url_quote(obj))
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100322
323
Armin Ronacher814f6c22008-04-17 15:52:23 +0200324class LRUCache(object):
325 """A simple LRU Cache implementation."""
Armin Ronacher58f351d2008-05-28 21:30:14 +0200326
327 # this is fast for small capacities (something below 1000) but doesn't
328 # scale. But as long as it's only used as storage for templates this
329 # won't do any harm.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200330
331 def __init__(self, capacity):
332 self.capacity = capacity
333 self._mapping = {}
334 self._queue = deque()
Armin Ronacher7962ce72008-05-20 17:52:52 +0200335 self._postinit()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200336
Armin Ronacher7962ce72008-05-20 17:52:52 +0200337 def _postinit(self):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200338 # alias all queue methods for faster lookup
339 self._popleft = self._queue.popleft
340 self._pop = self._queue.pop
Thomas Waldmann07a7d552013-05-18 00:47:41 +0200341 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200342 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200343 self._append = self._queue.append
344
Armin Ronacher7962ce72008-05-20 17:52:52 +0200345 def __getstate__(self):
346 return {
347 'capacity': self.capacity,
348 '_mapping': self._mapping,
349 '_queue': self._queue
350 }
351
352 def __setstate__(self, d):
353 self.__dict__.update(d)
354 self._postinit()
355
356 def __getnewargs__(self):
357 return (self.capacity,)
358
Armin Ronacher814f6c22008-04-17 15:52:23 +0200359 def copy(self):
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100360 """Return a shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200361 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200362 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200363 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200364 return rv
365
366 def get(self, key, default=None):
367 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200368 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200369 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200370 except KeyError:
371 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200372
373 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200374 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200375 leave unchanged. Return the value of this key.
376 """
Armin Ronacherd4e54382013-04-13 00:38:27 +0100377 self._wlock.acquire()
Armin Ronacher000b4912008-05-01 18:40:15 +0200378 try:
Armin Ronacherd4e54382013-04-13 00:38:27 +0100379 try:
380 return self[key]
381 except KeyError:
382 self[key] = default
383 return default
384 finally:
385 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200386
387 def clear(self):
388 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200389 self._wlock.acquire()
390 try:
391 self._mapping.clear()
392 self._queue.clear()
393 finally:
394 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200395
396 def __contains__(self, key):
397 """Check if a key exists in this cache."""
398 return key in self._mapping
399
400 def __len__(self):
401 """Return the current size of the cache."""
402 return len(self._mapping)
403
404 def __repr__(self):
405 return '<%s %r>' % (
406 self.__class__.__name__,
407 self._mapping
408 )
409
410 def __getitem__(self, key):
411 """Get an item from the cache. Moves the item up so that it has the
412 highest priority then.
413
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100414 Raise a `KeyError` if it does not exist.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200415 """
Armin Ronacherd4e54382013-04-13 00:38:27 +0100416 self._wlock.acquire()
417 try:
418 rv = self._mapping[key]
419 if self._queue[-1] != key:
420 try:
421 self._remove(key)
422 except ValueError:
423 # if something removed the key from the container
424 # when we read, ignore the ValueError that we would
425 # get otherwise.
426 pass
427 self._append(key)
428 return rv
429 finally:
430 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200431
432 def __setitem__(self, key, value):
433 """Sets the value for an item. Moves the item up so that it
434 has the highest priority then.
435 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200436 self._wlock.acquire()
437 try:
438 if key in self._mapping:
Armin Ronacherd4e54382013-04-13 00:38:27 +0100439 self._remove(key)
Armin Ronacher000b4912008-05-01 18:40:15 +0200440 elif len(self._mapping) == self.capacity:
441 del self._mapping[self._popleft()]
442 self._append(key)
443 self._mapping[key] = value
444 finally:
445 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200446
447 def __delitem__(self, key):
448 """Remove an item from the cache dict.
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100449 Raise a `KeyError` if it does not exist.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200450 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200451 self._wlock.acquire()
452 try:
453 del self._mapping[key]
Armin Ronachere7c72bc2009-09-14 12:20:33 -0700454 try:
455 self._remove(key)
456 except ValueError:
457 # __getitem__ is not locked, it might happen
458 pass
Armin Ronacher000b4912008-05-01 18:40:15 +0200459 finally:
460 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200461
Armin Ronachere25f24d2008-05-19 11:20:41 +0200462 def items(self):
463 """Return a list of items."""
464 result = [(key, self._mapping[key]) for key in list(self._queue)]
465 result.reverse()
466 return result
467
468 def iteritems(self):
469 """Iterate over all items."""
470 return iter(self.items())
471
472 def values(self):
473 """Return a list of all values."""
474 return [x[1] for x in self.items()]
475
476 def itervalue(self):
477 """Iterate over all values."""
478 return iter(self.values())
479
480 def keys(self):
481 """Return a list of all keys ordered by most recent usage."""
482 return list(self)
483
484 def iterkeys(self):
485 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200486 the most recent usage.
487 """
Armin Ronachere2244882008-05-19 09:25:57 +0200488 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200489
Armin Ronachere25f24d2008-05-19 11:20:41 +0200490 __iter__ = iterkeys
491
Armin Ronacher814f6c22008-04-17 15:52:23 +0200492 def __reversed__(self):
493 """Iterate over the values in the cache dict, oldest items
494 coming first.
495 """
Armin Ronachere2244882008-05-19 09:25:57 +0200496 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200497
498 __copy__ = copy
499
Armin Ronacherbd33f112008-04-18 09:17:32 +0200500
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200501# register the LRU cache as mutable mapping if possible
502try:
503 from collections import MutableMapping
504 MutableMapping.register(LRUCache)
505except ImportError:
506 pass
507
508
Armin Ronacher28c74882013-05-20 01:51:26 +0100509@implements_iterator
510class Cycler(object):
Armin Ronacherccae0552008-10-05 23:08:58 +0200511 """A cycle helper for templates."""
512
513 def __init__(self, *items):
514 if not items:
515 raise RuntimeError('at least one item has to be provided')
516 self.items = items
517 self.reset()
518
519 def reset(self):
520 """Resets the cycle."""
521 self.pos = 0
522
523 @property
524 def current(self):
525 """Returns the current item."""
526 return self.items[self.pos]
527
Thomas Waldmann7d295622013-05-18 00:06:22 +0200528 def __next__(self):
Armin Ronacherccae0552008-10-05 23:08:58 +0200529 """Goes one item ahead and returns it."""
530 rv = self.current
531 self.pos = (self.pos + 1) % len(self.items)
532 return rv
533
534
Armin Ronacherd34eb122008-10-13 23:47:51 +0200535class Joiner(object):
536 """A joining helper for templates."""
537
538 def __init__(self, sep=u', '):
539 self.sep = sep
540 self.used = False
541
542 def __call__(self):
543 if not self.used:
544 self.used = True
545 return u''
546 return self.sep
547
548
Armin Ronacher294f2eb2013-05-19 13:49:12 +0100549# Imported here because that's where it was in the past
550from markupsafe import Markup, escape, soft_unicode