blob: 2a64ce577d8786f531c3cc1981b56d8e4f81880b [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
Armin Ronacherccae0552008-10-05 23:08:58 +020012import errno
Armin Ronacher814f6c22008-04-17 15:52:23 +020013from collections import deque
Armin Ronacherf15b8142013-05-20 17:06:41 +010014from threading import Lock
Armin Ronacherce779a52013-05-20 02:11:16 +010015from jinja2._compat import text_type, string_types, implements_iterator, \
Armin Ronacherf15b8142013-05-20 17:06:41 +010016 url_quote
Armin Ronachere9098672013-05-19 14:16:13 +010017
Armin Ronacher8edbe492008-04-10 20:43:43 +020018
Armin Ronacherbe4ae242008-04-18 09:49:08 +020019_word_split_re = re.compile(r'(\s+)')
20_punctuation_re = re.compile(
21 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
Thomas Waldmann7d295622013-05-18 00:06:22 +020022 '|'.join(map(re.escape, ('(', '<', '&lt;'))),
23 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
Armin Ronacherbe4ae242008-04-18 09:49:08 +020024 )
25)
26_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
Armin Ronacher76c280b2008-05-04 12:31:48 +020027_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
28_entity_re = re.compile(r'&([^;]+);')
Armin Ronacher9a0078d2008-08-13 18:24:17 +020029_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
30_digits = '0123456789'
Armin Ronacherbe4ae242008-04-18 09:49:08 +020031
Armin Ronacher7259c762008-04-30 13:03:59 +020032# special singleton representing missing values for the runtime
33missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
34
Armin Ronacherd416a972009-02-24 22:58:00 +010035# internal code
36internal_code = set()
37
Thomas Waldmann07a7d552013-05-18 00:47:41 +020038concat = u''.join
Armin Ronacher7ceced52008-05-03 10:15:31 +020039
40
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020041def contextfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +020042 """This decorator can be used to mark a function or method context callable.
43 A context callable is passed the active :class:`Context` as first argument when
44 called from the template. This is useful if a function wants to get access
45 to the context or functions provided on the context object. For example
46 a function that returns a sorted list of template variables the current
47 template exports could look like this::
48
Armin Ronacher58f351d2008-05-28 21:30:14 +020049 @contextfunction
Armin Ronacher9bb7e472008-05-28 11:26:59 +020050 def get_exported_names(context):
51 return sorted(context.exported_vars)
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020052 """
53 f.contextfunction = True
54 return f
55
56
Armin Ronacher8346bd72010-03-14 19:43:47 +010057def evalcontextfunction(f):
Florent Xicluna0ec4f762012-02-05 13:09:15 +010058 """This decorator can be used to mark a function or method as an eval
Armin Ronacher8346bd72010-03-14 19:43:47 +010059 context callable. This is similar to the :func:`contextfunction`
60 but instead of passing the context, an evaluation context object is
Armin Ronacherfe150f32010-03-15 02:42:41 +010061 passed. For more information about the eval context, see
62 :ref:`eval-context`.
Armin Ronacher8346bd72010-03-14 19:43:47 +010063
64 .. versionadded:: 2.4
65 """
66 f.evalcontextfunction = True
67 return f
68
69
Armin Ronacher203bfcb2008-04-24 21:54:44 +020070def environmentfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +020071 """This decorator can be used to mark a function or method as environment
72 callable. This decorator works exactly like the :func:`contextfunction`
73 decorator just that the first argument is the active :class:`Environment`
74 and not context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +020075 """
76 f.environmentfunction = True
77 return f
78
79
Armin Ronacherd416a972009-02-24 22:58:00 +010080def internalcode(f):
81 """Marks the function as internally used"""
Thomas Waldmanne0003552013-05-17 23:52:14 +020082 internal_code.add(f.__code__)
Armin Ronacherd416a972009-02-24 22:58:00 +010083 return f
84
85
Armin Ronacher9bb7e472008-05-28 11:26:59 +020086def is_undefined(obj):
87 """Check if the object passed is undefined. This does nothing more than
88 performing an instance check against :class:`Undefined` but looks nicer.
89 This can be used for custom filters or tests that want to react to
90 undefined variables. For example a custom default filter can look like
91 this::
92
93 def default(var, default=''):
94 if is_undefined(var):
95 return default
96 return var
97 """
98 from jinja2.runtime import Undefined
99 return isinstance(obj, Undefined)
100
101
Armin Ronacherba6e25a2008-11-02 15:58:14 +0100102def consume(iterable):
103 """Consumes an iterable without doing anything with it."""
104 for event in iterable:
105 pass
106
107
Armin Ronacher187bde12008-05-01 18:19:16 +0200108def clear_caches():
109 """Jinja2 keeps internal caches for environments and lexers. These are
110 used so that Jinja2 doesn't have to recreate environments and lexers all
111 the time. Normally you don't have to care about that but if you are
112 messuring memory consumption you may want to clean the caches.
113 """
114 from jinja2.environment import _spontaneous_environments
115 from jinja2.lexer import _lexer_cache
116 _spontaneous_environments.clear()
117 _lexer_cache.clear()
118
119
Armin Ronacherf59bac22008-04-20 13:11:43 +0200120def import_string(import_name, silent=False):
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100121 """Imports an object based on a string. This is useful if you want to
Armin Ronacherf59bac22008-04-20 13:11:43 +0200122 use import paths as endpoints or something similar. An import path can
123 be specified either in dotted notation (``xml.sax.saxutils.escape``)
124 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
125
126 If the `silent` is True the return value will be `None` if the import
127 fails.
128
129 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200130 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200131 try:
132 if ':' in import_name:
133 module, obj = import_name.split(':', 1)
134 elif '.' in import_name:
135 items = import_name.split('.')
136 module = '.'.join(items[:-1])
137 obj = items[-1]
138 else:
139 return __import__(import_name)
140 return getattr(__import__(module, None, None, [obj]), obj)
141 except (ImportError, AttributeError):
142 if not silent:
143 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200144
145
Armin Ronacher0faa8612010-02-09 15:04:51 +0100146def open_if_exists(filename, mode='rb'):
Armin Ronacherccae0552008-10-05 23:08:58 +0200147 """Returns a file descriptor for the filename if that file exists,
148 otherwise `None`.
149 """
150 try:
Armin Ronacher790b8a82010-02-10 00:05:46 +0100151 return open(filename, mode)
Thomas Waldmanne0003552013-05-17 23:52:14 +0200152 except IOError as e:
Konstantin Zemlyak0b74a4f2014-09-28 21:08:28 +0600153 if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL):
Armin Ronacherccae0552008-10-05 23:08:58 +0200154 raise
155
156
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200157def object_type_repr(obj):
158 """Returns the name of the object's type. For some recognized
159 singletons the name of the object is returned instead. (For
160 example for `None` and `Ellipsis`).
161 """
162 if obj is None:
163 return 'None'
164 elif obj is Ellipsis:
165 return 'Ellipsis'
Armin Ronacher802f4722010-04-20 19:48:46 +0200166 # __builtin__ in 2.x, builtins in 3.x
167 if obj.__class__.__module__ in ('__builtin__', 'builtins'):
Armin Ronacher800ac7f2010-04-20 13:45:11 +0200168 name = obj.__class__.__name__
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200169 else:
Armin Ronacher800ac7f2010-04-20 13:45:11 +0200170 name = obj.__class__.__module__ + '.' + obj.__class__.__name__
Armin Ronacher98dbf5f2010-04-12 15:49:59 +0200171 return '%s object' % name
172
173
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200174def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200175 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200176 builtin `pprint`.
177 """
178 try:
179 from pretty import pretty
180 return pretty(obj, verbose=verbose)
181 except ImportError:
182 from pprint import pformat
183 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200184
185
Berker Peksagd55e1e52013-08-21 13:13:48 +0300186def urlize(text, trim_url_limit=None, nofollow=False, target=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200187 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200188 https:// and www. links. Links can have trailing punctuation (periods,
189 commas, close-parens) and leading punctuation (opening parens) and
190 it'll still do the right thing.
191
192 If trim_url_limit is not None, the URLs in link text will be limited
193 to trim_url_limit characters.
194
195 If nofollow is True, the URLs in link text will get a rel="nofollow"
196 attribute.
Berker Peksagd55e1e52013-08-21 13:13:48 +0300197
198 If target is not None, a target attribute will be added to the link.
Christoph Hack80909862008-04-14 01:35:10 +0200199 """
200 trim_url = lambda x, limit=trim_url_limit: limit is not None \
201 and (x[:limit] + (len(x) >=limit and '...'
202 or '')) or x
Armin Ronachere9098672013-05-19 14:16:13 +0100203 words = _word_split_re.split(text_type(escape(text)))
Christoph Hack80909862008-04-14 01:35:10 +0200204 nofollow_attr = nofollow and ' rel="nofollow"' or ''
Berker Peksagd55e1e52013-08-21 13:13:48 +0300205 if target is not None and isinstance(target, string_types):
Sambhav Satija75685ec2016-04-10 21:10:38 +0530206 target_attr = ' target="%s"' % escape(target)
Berker Peksagd55e1e52013-08-21 13:13:48 +0300207 else:
208 target_attr = ''
Christoph Hack80909862008-04-14 01:35:10 +0200209 for i, word in enumerate(words):
210 match = _punctuation_re.match(word)
211 if match:
212 lead, middle, trail = match.groups()
213 if middle.startswith('www.') or (
214 '@' not in middle and
215 not middle.startswith('http://') and
mozillazg66448932013-03-18 14:27:54 +0800216 not middle.startswith('https://') and
Christoph Hack80909862008-04-14 01:35:10 +0200217 len(middle) > 0 and
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200218 middle[0] in _letters + _digits and (
Christoph Hack80909862008-04-14 01:35:10 +0200219 middle.endswith('.org') or
220 middle.endswith('.net') or
221 middle.endswith('.com')
222 )):
Berker Peksagd55e1e52013-08-21 13:13:48 +0300223 middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
224 nofollow_attr, target_attr, trim_url(middle))
Christoph Hack80909862008-04-14 01:35:10 +0200225 if middle.startswith('http://') or \
226 middle.startswith('https://'):
Berker Peksagd55e1e52013-08-21 13:13:48 +0300227 middle = '<a href="%s"%s%s>%s</a>' % (middle,
228 nofollow_attr, target_attr, trim_url(middle))
Christoph Hack80909862008-04-14 01:35:10 +0200229 if '@' in middle and not middle.startswith('www.') and \
230 not ':' in middle and _simple_email_re.match(middle):
231 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
232 if lead + middle + trail != word:
233 words[i] = lead + middle + trail
234 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200235
236
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200237def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
Fabio Utzigc0734702015-01-18 17:29:18 -0200238 """Generate some lorem ipsum for the template."""
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200239 from jinja2.constants import LOREM_IPSUM_WORDS
Georg Brandl95632c42009-11-22 18:35:18 +0100240 from random import choice, randrange
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200241 words = LOREM_IPSUM_WORDS.split()
242 result = []
243
Thomas Waldmanne0003552013-05-17 23:52:14 +0200244 for _ in range(n):
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200245 next_capitalized = True
246 last_comma = last_fullstop = 0
247 word = None
248 last = None
249 p = []
250
251 # each paragraph contains out of 20 to 100 words.
Thomas Waldmanne0003552013-05-17 23:52:14 +0200252 for idx, _ in enumerate(range(randrange(min, max))):
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200253 while True:
254 word = choice(words)
255 if word != last:
256 last = word
257 break
258 if next_capitalized:
259 word = word.capitalize()
260 next_capitalized = False
261 # add commas
262 if idx - randrange(3, 8) > last_comma:
263 last_comma = idx
264 last_fullstop += 2
265 word += ','
266 # add end of sentences
267 if idx - randrange(10, 20) > last_fullstop:
268 last_comma = last_fullstop = idx
269 word += '.'
270 next_capitalized = True
271 p.append(word)
272
273 # ensure that the paragraph ends with a dot.
274 p = u' '.join(p)
275 if p.endswith(','):
276 p = p[:-1] + '.'
277 elif not p.endswith('.'):
278 p += '.'
279 result.append(p)
280
281 if not html:
282 return u'\n\n'.join(result)
283 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
284
285
Armin Ronacher46acbf02015-05-25 13:40:47 +0200286def unicode_urlencode(obj, charset='utf-8', for_qs=False):
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100287 """URL escapes a single bytestring or unicode string with the
288 given charset if applicable to URL safe quoting under all rules
289 that need to be considered under all supported Python versions.
290
291 If non strings are provided they are converted to their unicode
292 representation first.
293 """
Armin Ronachere9098672013-05-19 14:16:13 +0100294 if not isinstance(obj, string_types):
295 obj = text_type(obj)
296 if isinstance(obj, text_type):
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100297 obj = obj.encode(charset)
Armin Ronacher8189d212015-11-20 10:05:12 +0100298 safe = not for_qs and b'/' or b''
Armin Ronacher46acbf02015-05-25 13:40:47 +0200299 rv = text_type(url_quote(obj, safe))
300 if for_qs:
301 rv = rv.replace('%20', '+')
302 return rv
Armin Ronacher1d4c6382012-01-07 17:46:40 +0100303
304
Armin Ronacher814f6c22008-04-17 15:52:23 +0200305class LRUCache(object):
306 """A simple LRU Cache implementation."""
Armin Ronacher58f351d2008-05-28 21:30:14 +0200307
308 # this is fast for small capacities (something below 1000) but doesn't
309 # scale. But as long as it's only used as storage for templates this
310 # won't do any harm.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200311
312 def __init__(self, capacity):
313 self.capacity = capacity
314 self._mapping = {}
315 self._queue = deque()
Armin Ronacher7962ce72008-05-20 17:52:52 +0200316 self._postinit()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200317
Armin Ronacher7962ce72008-05-20 17:52:52 +0200318 def _postinit(self):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200319 # alias all queue methods for faster lookup
320 self._popleft = self._queue.popleft
321 self._pop = self._queue.pop
Thomas Waldmann07a7d552013-05-18 00:47:41 +0200322 self._remove = self._queue.remove
Armin Ronacherf15b8142013-05-20 17:06:41 +0100323 self._wlock = Lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200324 self._append = self._queue.append
325
Armin Ronacher7962ce72008-05-20 17:52:52 +0200326 def __getstate__(self):
327 return {
328 'capacity': self.capacity,
329 '_mapping': self._mapping,
330 '_queue': self._queue
331 }
332
333 def __setstate__(self, d):
334 self.__dict__.update(d)
335 self._postinit()
336
337 def __getnewargs__(self):
338 return (self.capacity,)
339
Armin Ronacher814f6c22008-04-17 15:52:23 +0200340 def copy(self):
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100341 """Return a shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200342 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200343 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200344 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200345 return rv
346
347 def get(self, key, default=None):
348 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200349 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200350 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200351 except KeyError:
352 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200353
354 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200355 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200356 leave unchanged. Return the value of this key.
357 """
Armin Ronacherd4e54382013-04-13 00:38:27 +0100358 self._wlock.acquire()
Armin Ronacher000b4912008-05-01 18:40:15 +0200359 try:
Armin Ronacherd4e54382013-04-13 00:38:27 +0100360 try:
361 return self[key]
362 except KeyError:
363 self[key] = default
364 return default
365 finally:
366 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200367
368 def clear(self):
369 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200370 self._wlock.acquire()
371 try:
372 self._mapping.clear()
373 self._queue.clear()
374 finally:
375 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200376
377 def __contains__(self, key):
378 """Check if a key exists in this cache."""
379 return key in self._mapping
380
381 def __len__(self):
382 """Return the current size of the cache."""
383 return len(self._mapping)
384
385 def __repr__(self):
386 return '<%s %r>' % (
387 self.__class__.__name__,
388 self._mapping
389 )
390
391 def __getitem__(self, key):
392 """Get an item from the cache. Moves the item up so that it has the
393 highest priority then.
394
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100395 Raise a `KeyError` if it does not exist.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200396 """
Armin Ronacherd4e54382013-04-13 00:38:27 +0100397 self._wlock.acquire()
398 try:
399 rv = self._mapping[key]
400 if self._queue[-1] != key:
401 try:
402 self._remove(key)
403 except ValueError:
404 # if something removed the key from the container
405 # when we read, ignore the ValueError that we would
406 # get otherwise.
407 pass
408 self._append(key)
409 return rv
410 finally:
411 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200412
413 def __setitem__(self, key, value):
414 """Sets the value for an item. Moves the item up so that it
415 has the highest priority then.
416 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200417 self._wlock.acquire()
418 try:
419 if key in self._mapping:
Armin Ronacherd4e54382013-04-13 00:38:27 +0100420 self._remove(key)
Armin Ronacher000b4912008-05-01 18:40:15 +0200421 elif len(self._mapping) == self.capacity:
422 del self._mapping[self._popleft()]
423 self._append(key)
424 self._mapping[key] = value
425 finally:
426 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200427
428 def __delitem__(self, key):
429 """Remove an item from the cache dict.
Florent Xicluna0ec4f762012-02-05 13:09:15 +0100430 Raise a `KeyError` if it does not exist.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200431 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200432 self._wlock.acquire()
433 try:
434 del self._mapping[key]
Armin Ronachere7c72bc2009-09-14 12:20:33 -0700435 try:
436 self._remove(key)
437 except ValueError:
438 # __getitem__ is not locked, it might happen
439 pass
Armin Ronacher000b4912008-05-01 18:40:15 +0200440 finally:
441 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200442
Armin Ronachere25f24d2008-05-19 11:20:41 +0200443 def items(self):
444 """Return a list of items."""
445 result = [(key, self._mapping[key]) for key in list(self._queue)]
446 result.reverse()
447 return result
448
449 def iteritems(self):
450 """Iterate over all items."""
451 return iter(self.items())
452
453 def values(self):
454 """Return a list of all values."""
455 return [x[1] for x in self.items()]
456
457 def itervalue(self):
458 """Iterate over all values."""
459 return iter(self.values())
460
461 def keys(self):
462 """Return a list of all keys ordered by most recent usage."""
463 return list(self)
464
465 def iterkeys(self):
466 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200467 the most recent usage.
468 """
Armin Ronachere2244882008-05-19 09:25:57 +0200469 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200470
Armin Ronachere25f24d2008-05-19 11:20:41 +0200471 __iter__ = iterkeys
472
Armin Ronacher814f6c22008-04-17 15:52:23 +0200473 def __reversed__(self):
474 """Iterate over the values in the cache dict, oldest items
475 coming first.
476 """
Armin Ronachere2244882008-05-19 09:25:57 +0200477 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200478
479 __copy__ = copy
480
Armin Ronacherbd33f112008-04-18 09:17:32 +0200481
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200482# register the LRU cache as mutable mapping if possible
483try:
484 from collections import MutableMapping
485 MutableMapping.register(LRUCache)
486except ImportError:
487 pass
488
489
Armin Ronacher28c74882013-05-20 01:51:26 +0100490@implements_iterator
491class Cycler(object):
Armin Ronacherccae0552008-10-05 23:08:58 +0200492 """A cycle helper for templates."""
493
494 def __init__(self, *items):
495 if not items:
496 raise RuntimeError('at least one item has to be provided')
497 self.items = items
498 self.reset()
499
500 def reset(self):
501 """Resets the cycle."""
502 self.pos = 0
503
504 @property
505 def current(self):
506 """Returns the current item."""
507 return self.items[self.pos]
508
Thomas Waldmann7d295622013-05-18 00:06:22 +0200509 def __next__(self):
Armin Ronacherccae0552008-10-05 23:08:58 +0200510 """Goes one item ahead and returns it."""
511 rv = self.current
512 self.pos = (self.pos + 1) % len(self.items)
513 return rv
514
515
Armin Ronacherd34eb122008-10-13 23:47:51 +0200516class Joiner(object):
517 """A joining helper for templates."""
518
519 def __init__(self, sep=u', '):
520 self.sep = sep
521 self.used = False
522
523 def __call__(self):
524 if not self.used:
525 self.used = True
526 return u''
527 return self.sep
528
529
Armin Ronacher294f2eb2013-05-19 13:49:12 +0100530# Imported here because that's where it was in the past
531from markupsafe import Markup, escape, soft_unicode