blob: 529abd2794ad1b018ee606be598a30b852a6e710 [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.utils
4 ~~~~~~~~~~~~
5
6 Utility functions.
7
8 :copyright: 2008 by Armin Ronacher.
9 :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
Christoph Hack80909862008-04-14 01:35:10 +020013import string
Armin Ronacher000b4912008-05-01 18:40:15 +020014try:
15 from thread import allocate_lock
16except ImportError:
17 from dummy_thread import allocate_lock
Armin Ronacher76c280b2008-05-04 12:31:48 +020018from htmlentitydefs import name2codepoint
Armin Ronacher814f6c22008-04-17 15:52:23 +020019from collections import deque
20from copy import deepcopy
Armin Ronacher18c6ca02008-04-17 10:03:29 +020021from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020022
23
Armin Ronacherbe4ae242008-04-18 09:49:08 +020024_word_split_re = re.compile(r'(\s+)')
25_punctuation_re = re.compile(
26 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
27 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
28 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
29 )
30)
31_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
Armin Ronacher76c280b2008-05-04 12:31:48 +020032_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
33_entity_re = re.compile(r'&([^;]+);')
34_entities = name2codepoint.copy()
35_entities['apos'] = 39
Armin Ronacherbe4ae242008-04-18 09:49:08 +020036
Armin Ronacher7259c762008-04-30 13:03:59 +020037# special singleton representing missing values for the runtime
38missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
39
40
Armin Ronacher7ceced52008-05-03 10:15:31 +020041# concatenate a list of strings and convert them to unicode.
42# unfortunately there is a bug in python 2.4 and lower that causes
43# unicode.join trash the traceback.
Armin Ronachercda43df2008-05-03 17:10:05 +020044_concat = u''.join
Armin Ronacher7ceced52008-05-03 10:15:31 +020045try:
46 def _test_gen_bug():
47 raise TypeError(_test_gen_bug)
48 yield None
Armin Ronachercda43df2008-05-03 17:10:05 +020049 _concat(_test_gen_bug())
Armin Ronacher7ceced52008-05-03 10:15:31 +020050except TypeError, _error:
Armin Ronachercda43df2008-05-03 17:10:05 +020051 if not _error.args or _error.args[0] is not _test_gen_bug:
Armin Ronacher7ceced52008-05-03 10:15:31 +020052 def concat(gen):
53 try:
Armin Ronachercda43df2008-05-03 17:10:05 +020054 return _concat(list(gen))
Armin Ronacher7ceced52008-05-03 10:15:31 +020055 except:
56 # this hack is needed so that the current frame
57 # does not show up in the traceback.
58 exc_type, exc_value, tb = sys.exc_info()
59 raise exc_type, exc_value, tb.tb_next
Armin Ronachercda43df2008-05-03 17:10:05 +020060 else:
61 concat = _concat
Armin Ronacher7ceced52008-05-03 10:15:31 +020062 del _test_gen_bug, _error
63
64
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020065def contextfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020066 """This decorator can be used to mark a callable as context callable. A
67 context callable is passed the active context as first argument if it
68 was directly stored in the context.
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020069 """
70 f.contextfunction = True
71 return f
72
73
Armin Ronacher203bfcb2008-04-24 21:54:44 +020074def environmentfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020075 """This decorator can be used to mark a callable as environment callable.
76 A environment callable is passed the current environment as first argument
77 if it was directly stored in the context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +020078 """
79 f.environmentfunction = True
80 return f
81
82
Armin Ronacher187bde12008-05-01 18:19:16 +020083def clear_caches():
84 """Jinja2 keeps internal caches for environments and lexers. These are
85 used so that Jinja2 doesn't have to recreate environments and lexers all
86 the time. Normally you don't have to care about that but if you are
87 messuring memory consumption you may want to clean the caches.
88 """
89 from jinja2.environment import _spontaneous_environments
90 from jinja2.lexer import _lexer_cache
91 _spontaneous_environments.clear()
92 _lexer_cache.clear()
93
94
Armin Ronacherf59bac22008-04-20 13:11:43 +020095def import_string(import_name, silent=False):
96 """Imports an object based on a string. This use useful if you want to
97 use import paths as endpoints or something similar. An import path can
98 be specified either in dotted notation (``xml.sax.saxutils.escape``)
99 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
100
101 If the `silent` is True the return value will be `None` if the import
102 fails.
103
104 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200105 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200106 try:
107 if ':' in import_name:
108 module, obj = import_name.split(':', 1)
109 elif '.' in import_name:
110 items = import_name.split('.')
111 module = '.'.join(items[:-1])
112 obj = items[-1]
113 else:
114 return __import__(import_name)
115 return getattr(__import__(module, None, None, [obj]), obj)
116 except (ImportError, AttributeError):
117 if not silent:
118 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200119
120
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200121def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200122 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200123 builtin `pprint`.
124 """
125 try:
126 from pretty import pretty
127 return pretty(obj, verbose=verbose)
128 except ImportError:
129 from pprint import pformat
130 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200131
132
Christoph Hack80909862008-04-14 01:35:10 +0200133def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200134 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200135 https:// and www. links. Links can have trailing punctuation (periods,
136 commas, close-parens) and leading punctuation (opening parens) and
137 it'll still do the right thing.
138
139 If trim_url_limit is not None, the URLs in link text will be limited
140 to trim_url_limit characters.
141
142 If nofollow is True, the URLs in link text will get a rel="nofollow"
143 attribute.
144 """
145 trim_url = lambda x, limit=trim_url_limit: limit is not None \
146 and (x[:limit] + (len(x) >=limit and '...'
147 or '')) or x
148 words = _word_split_re.split(text)
149 nofollow_attr = nofollow and ' rel="nofollow"' or ''
150 for i, word in enumerate(words):
151 match = _punctuation_re.match(word)
152 if match:
153 lead, middle, trail = match.groups()
154 if middle.startswith('www.') or (
155 '@' not in middle and
156 not middle.startswith('http://') and
157 len(middle) > 0 and
158 middle[0] in string.letters + string.digits and (
159 middle.endswith('.org') or
160 middle.endswith('.net') or
161 middle.endswith('.com')
162 )):
163 middle = '<a href="http://%s"%s>%s</a>' % (middle,
164 nofollow_attr, trim_url(middle))
165 if middle.startswith('http://') or \
166 middle.startswith('https://'):
167 middle = '<a href="%s"%s>%s</a>' % (middle,
168 nofollow_attr, trim_url(middle))
169 if '@' in middle and not middle.startswith('www.') and \
170 not ':' in middle and _simple_email_re.match(middle):
171 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
172 if lead + middle + trail != word:
173 words[i] = lead + middle + trail
174 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200175
176
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200177def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
178 """Generate some lorem impsum for the template."""
179 from jinja2.constants import LOREM_IPSUM_WORDS
180 from random import choice, random, randrange
181 words = LOREM_IPSUM_WORDS.split()
182 result = []
183
184 for _ in xrange(n):
185 next_capitalized = True
186 last_comma = last_fullstop = 0
187 word = None
188 last = None
189 p = []
190
191 # each paragraph contains out of 20 to 100 words.
192 for idx, _ in enumerate(xrange(randrange(min, max))):
193 while True:
194 word = choice(words)
195 if word != last:
196 last = word
197 break
198 if next_capitalized:
199 word = word.capitalize()
200 next_capitalized = False
201 # add commas
202 if idx - randrange(3, 8) > last_comma:
203 last_comma = idx
204 last_fullstop += 2
205 word += ','
206 # add end of sentences
207 if idx - randrange(10, 20) > last_fullstop:
208 last_comma = last_fullstop = idx
209 word += '.'
210 next_capitalized = True
211 p.append(word)
212
213 # ensure that the paragraph ends with a dot.
214 p = u' '.join(p)
215 if p.endswith(','):
216 p = p[:-1] + '.'
217 elif not p.endswith('.'):
218 p += '.'
219 result.append(p)
220
221 if not html:
222 return u'\n\n'.join(result)
223 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
224
225
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200226class Markup(unicode):
227 """Marks a string as being safe for inclusion in HTML/XML output without
228 needing to be escaped. This implements the `__html__` interface a couple
229 of frameworks and web applications use.
230
231 The `escape` function returns markup objects so that double escaping can't
232 happen. If you want to use autoescaping in Jinja just set the finalizer
233 of the environment to `escape`.
234 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200235 __slots__ = ()
236
237 def __html__(self):
238 return self
239
240 def __add__(self, other):
241 if hasattr(other, '__html__') or isinstance(other, basestring):
242 return self.__class__(unicode(self) + unicode(escape(other)))
243 return NotImplemented
244
245 def __radd__(self, other):
246 if hasattr(other, '__html__') or isinstance(other, basestring):
247 return self.__class__(unicode(escape(other)) + unicode(self))
248 return NotImplemented
249
250 def __mul__(self, num):
251 if not isinstance(num, (int, long)):
252 return NotImplemented
253 return self.__class__(unicode.__mul__(self, num))
254 __rmul__ = __mul__
255
256 def __mod__(self, arg):
257 if isinstance(arg, tuple):
258 arg = tuple(imap(_MarkupEscapeHelper, arg))
259 else:
260 arg = _MarkupEscapeHelper(arg)
261 return self.__class__(unicode.__mod__(self, arg))
262
263 def __repr__(self):
264 return '%s(%s)' % (
265 self.__class__.__name__,
266 unicode.__repr__(self)
267 )
268
269 def join(self, seq):
270 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200271 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200272
273 def split(self, *args, **kwargs):
274 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200275 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200276
277 def rsplit(self, *args, **kwargs):
278 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200279 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200280
281 def splitlines(self, *args, **kwargs):
282 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200283 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200284
Armin Ronacher76c280b2008-05-04 12:31:48 +0200285 def unescape(self):
286 """Unescape markup."""
287 def handle_match(m):
288 name = m.group(1)
289 if name in _entities:
290 return unichr(_entities[name])
291 try:
292 if name[:2] in ('#x', '#X'):
293 return unichr(int(name[2:], 16))
294 elif name.startswith('#'):
295 return unichr(int(name[1:]))
296 except ValueError:
297 pass
298 return u''
299 return _entity_re.sub(handle_match, unicode(self))
300
301 def striptags(self):
302 """Strip tags and resolve enities."""
303 stripped = u' '.join(_striptags_re.sub('', self).split())
304 return Markup(stripped).unescape()
305
Armin Ronacherf35e2812008-05-06 16:04:10 +0200306 @classmethod
307 def escape(cls, s):
308 """Escape the string. Works like :func:`escape`."""
309 rv = escape(s)
310 if rv.__class__ is not cls:
311 return cls(rv)
312 return rv
313
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200314 def make_wrapper(name):
315 orig = getattr(unicode, name)
316 def func(self, *args, **kwargs):
317 args = list(args)
318 for idx, arg in enumerate(args):
319 if hasattr(arg, '__html__') or isinstance(arg, basestring):
320 args[idx] = escape(arg)
321 for name, arg in kwargs.iteritems():
322 if hasattr(arg, '__html__') or isinstance(arg, basestring):
323 kwargs[name] = escape(arg)
324 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200325 func.__name__ = orig.__name__
326 func.__doc__ = orig.__doc__
327 return func
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200328 for method in '__getitem__', '__getslice__', 'capitalize', \
329 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200330 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200331 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200332 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200333
334 # new in python 2.5
335 if hasattr(unicode, 'partition'):
Armin Ronacher316157d2008-04-28 18:30:27 +0200336 locals().update(
337 partition=make_wrapper('partition'),
338 rpartition=make_wrapper('rpartition')
339 )
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200340 del method, make_wrapper
341
342
343class _MarkupEscapeHelper(object):
344 """Helper for Markup.__mod__"""
345
346 def __init__(self, obj):
347 self.obj = obj
348
349 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
350 __unicode__ = lambda s: unicode(escape(s.obj))
351 __str__ = lambda s: str(escape(s.obj))
352 __repr__ = lambda s: str(repr(escape(s.obj)))
353 __int__ = lambda s: int(s.obj)
354 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200355
356
357class LRUCache(object):
358 """A simple LRU Cache implementation."""
359 # this is fast for small capacities (something around 200) but doesn't
360 # scale. But as long as it's only used for the database connections in
361 # a non request fallback it's fine.
362
363 def __init__(self, capacity):
364 self.capacity = capacity
365 self._mapping = {}
366 self._queue = deque()
367
368 # alias all queue methods for faster lookup
369 self._popleft = self._queue.popleft
370 self._pop = self._queue.pop
371 if hasattr(self._queue, 'remove'):
372 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200373 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200374 self._append = self._queue.append
375
376 def _remove(self, obj):
377 """Python 2.4 compatibility."""
378 for idx, item in enumerate(self._queue):
379 if item == obj:
380 del self._queue[idx]
381 break
382
383 def copy(self):
384 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200385 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200386 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200387 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200388 return rv
389
390 def get(self, key, default=None):
391 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200392 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200393 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200394 except KeyError:
395 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200396
397 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200398 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200399 leave unchanged. Return the value of this key.
400 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200401 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200402 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200403 except KeyError:
404 self[key] = default
405 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200406
407 def clear(self):
408 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200409 self._wlock.acquire()
410 try:
411 self._mapping.clear()
412 self._queue.clear()
413 finally:
414 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200415
416 def __contains__(self, key):
417 """Check if a key exists in this cache."""
418 return key in self._mapping
419
420 def __len__(self):
421 """Return the current size of the cache."""
422 return len(self._mapping)
423
424 def __repr__(self):
425 return '<%s %r>' % (
426 self.__class__.__name__,
427 self._mapping
428 )
429
430 def __getitem__(self, key):
431 """Get an item from the cache. Moves the item up so that it has the
432 highest priority then.
433
434 Raise an `KeyError` if it does not exist.
435 """
436 rv = self._mapping[key]
437 if self._queue[-1] != key:
438 self._remove(key)
439 self._append(key)
440 return rv
441
442 def __setitem__(self, key, value):
443 """Sets the value for an item. Moves the item up so that it
444 has the highest priority then.
445 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200446 self._wlock.acquire()
447 try:
448 if key in self._mapping:
449 self._remove(key)
450 elif len(self._mapping) == self.capacity:
451 del self._mapping[self._popleft()]
452 self._append(key)
453 self._mapping[key] = value
454 finally:
455 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200456
457 def __delitem__(self, key):
458 """Remove an item from the cache dict.
459 Raise an `KeyError` if it does not exist.
460 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200461 self._wlock.acquire()
462 try:
463 del self._mapping[key]
464 self._remove(key)
465 finally:
466 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200467
Armin Ronachere25f24d2008-05-19 11:20:41 +0200468 def items(self):
469 """Return a list of items."""
470 result = [(key, self._mapping[key]) for key in list(self._queue)]
471 result.reverse()
472 return result
473
474 def iteritems(self):
475 """Iterate over all items."""
476 return iter(self.items())
477
478 def values(self):
479 """Return a list of all values."""
480 return [x[1] for x in self.items()]
481
482 def itervalue(self):
483 """Iterate over all values."""
484 return iter(self.values())
485
486 def keys(self):
487 """Return a list of all keys ordered by most recent usage."""
488 return list(self)
489
490 def iterkeys(self):
491 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200492 the most recent usage.
493 """
Armin Ronachere2244882008-05-19 09:25:57 +0200494 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200495
Armin Ronachere25f24d2008-05-19 11:20:41 +0200496 __iter__ = iterkeys
497
Armin Ronacher814f6c22008-04-17 15:52:23 +0200498 def __reversed__(self):
499 """Iterate over the values in the cache dict, oldest items
500 coming first.
501 """
Armin Ronachere2244882008-05-19 09:25:57 +0200502 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200503
504 __copy__ = copy
505
Armin Ronacherbd33f112008-04-18 09:17:32 +0200506
507# we have to import it down here as the speedups module imports the
508# markup type which is define above.
509try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200510 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200511except ImportError:
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200512 def escape(s):
Armin Ronacherf35e2812008-05-06 16:04:10 +0200513 """Convert the characters &, <, >, ' and " in string s to HTML-safe
514 sequences. Use this if you need to display text that might contain
Armin Ronacher9a1e33c2008-05-05 22:00:46 +0200515 such characters in HTML. Marks return value as markup string.
Armin Ronacherbd33f112008-04-18 09:17:32 +0200516 """
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200517 if hasattr(s, '__html__'):
518 return s.__html__()
519 return Markup(unicode(s)
Armin Ronacherbd33f112008-04-18 09:17:32 +0200520 .replace('&', '&amp;')
521 .replace('>', '&gt;')
522 .replace('<', '&lt;')
Armin Ronacherf35e2812008-05-06 16:04:10 +0200523 .replace("'", '&#39;')
Armin Ronacher9d42abf2008-05-14 18:10:41 +0200524 .replace('"', '&#34;')
Armin Ronacherbd33f112008-04-18 09:17:32 +0200525 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200526
527 def soft_unicode(s):
528 """Make a string unicode if it isn't already. That way a markup
529 string is not converted back to unicode.
530 """
531 if not isinstance(s, unicode):
532 s = unicode(s)
533 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200534
535
536# partials
537try:
538 from functools import partial
539except ImportError:
540 class partial(object):
541 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200542 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200543 self._args = args
544 self._kwargs = kwargs
545 def __call__(self, *args, **kwargs):
546 kwargs.update(self._kwargs)
547 return self._func(*(self._args + args), **kwargs)