blob: d9a45a8188a4aba131279b9b678d4afff0d10512 [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
12import string
Armin Ronacher000b4912008-05-01 18:40:15 +020013try:
14 from thread import allocate_lock
15except ImportError:
16 from dummy_thread import allocate_lock
Armin Ronacher814f6c22008-04-17 15:52:23 +020017from collections import deque
18from copy import deepcopy
Armin Ronacher18c6ca02008-04-17 10:03:29 +020019from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020020
21
Armin Ronacherbe4ae242008-04-18 09:49:08 +020022_word_split_re = re.compile(r'(\s+)')
23_punctuation_re = re.compile(
24 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
25 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
26 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
27 )
28)
29_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
30
31
Armin Ronacher7259c762008-04-30 13:03:59 +020032# special singleton representing missing values for the runtime
33missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
34
35
Armin Ronacher7ceced52008-05-03 10:15:31 +020036# concatenate a list of strings and convert them to unicode.
37# unfortunately there is a bug in python 2.4 and lower that causes
38# unicode.join trash the traceback.
39try:
40 def _test_gen_bug():
41 raise TypeError(_test_gen_bug)
42 yield None
43 u''.join(_test_gen_bug())
44except TypeError, _error:
45 if _error.args and _error.args[0] is _test_gen_bug:
46 concat = u''.join
47 else:
48 def concat(gen):
49 try:
50 return u''.join(list(gen))
51 except:
52 # this hack is needed so that the current frame
53 # does not show up in the traceback.
54 exc_type, exc_value, tb = sys.exc_info()
55 raise exc_type, exc_value, tb.tb_next
56 del _test_gen_bug, _error
57
58
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020059def contextfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020060 """This decorator can be used to mark a callable as context callable. A
61 context callable is passed the active context as first argument if it
62 was directly stored in the context.
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020063 """
64 f.contextfunction = True
65 return f
66
67
Armin Ronacher203bfcb2008-04-24 21:54:44 +020068def environmentfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020069 """This decorator can be used to mark a callable as environment callable.
70 A environment callable is passed the current environment as first argument
71 if it was directly stored in the context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +020072 """
73 f.environmentfunction = True
74 return f
75
76
Armin Ronacher187bde12008-05-01 18:19:16 +020077def clear_caches():
78 """Jinja2 keeps internal caches for environments and lexers. These are
79 used so that Jinja2 doesn't have to recreate environments and lexers all
80 the time. Normally you don't have to care about that but if you are
81 messuring memory consumption you may want to clean the caches.
82 """
83 from jinja2.environment import _spontaneous_environments
84 from jinja2.lexer import _lexer_cache
85 _spontaneous_environments.clear()
86 _lexer_cache.clear()
87
88
Armin Ronacherf59bac22008-04-20 13:11:43 +020089def import_string(import_name, silent=False):
90 """Imports an object based on a string. This use useful if you want to
91 use import paths as endpoints or something similar. An import path can
92 be specified either in dotted notation (``xml.sax.saxutils.escape``)
93 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
94
95 If the `silent` is True the return value will be `None` if the import
96 fails.
97
98 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +020099 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200100 try:
101 if ':' in import_name:
102 module, obj = import_name.split(':', 1)
103 elif '.' in import_name:
104 items = import_name.split('.')
105 module = '.'.join(items[:-1])
106 obj = items[-1]
107 else:
108 return __import__(import_name)
109 return getattr(__import__(module, None, None, [obj]), obj)
110 except (ImportError, AttributeError):
111 if not silent:
112 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200113
114
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200115def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200116 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200117 builtin `pprint`.
118 """
119 try:
120 from pretty import pretty
121 return pretty(obj, verbose=verbose)
122 except ImportError:
123 from pprint import pformat
124 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200125
126
Christoph Hack80909862008-04-14 01:35:10 +0200127def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200128 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200129 https:// and www. links. Links can have trailing punctuation (periods,
130 commas, close-parens) and leading punctuation (opening parens) and
131 it'll still do the right thing.
132
133 If trim_url_limit is not None, the URLs in link text will be limited
134 to trim_url_limit characters.
135
136 If nofollow is True, the URLs in link text will get a rel="nofollow"
137 attribute.
138 """
139 trim_url = lambda x, limit=trim_url_limit: limit is not None \
140 and (x[:limit] + (len(x) >=limit and '...'
141 or '')) or x
142 words = _word_split_re.split(text)
143 nofollow_attr = nofollow and ' rel="nofollow"' or ''
144 for i, word in enumerate(words):
145 match = _punctuation_re.match(word)
146 if match:
147 lead, middle, trail = match.groups()
148 if middle.startswith('www.') or (
149 '@' not in middle and
150 not middle.startswith('http://') and
151 len(middle) > 0 and
152 middle[0] in string.letters + string.digits and (
153 middle.endswith('.org') or
154 middle.endswith('.net') or
155 middle.endswith('.com')
156 )):
157 middle = '<a href="http://%s"%s>%s</a>' % (middle,
158 nofollow_attr, trim_url(middle))
159 if middle.startswith('http://') or \
160 middle.startswith('https://'):
161 middle = '<a href="%s"%s>%s</a>' % (middle,
162 nofollow_attr, trim_url(middle))
163 if '@' in middle and not middle.startswith('www.') and \
164 not ':' in middle and _simple_email_re.match(middle):
165 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
166 if lead + middle + trail != word:
167 words[i] = lead + middle + trail
168 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200169
170
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200171def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
172 """Generate some lorem impsum for the template."""
173 from jinja2.constants import LOREM_IPSUM_WORDS
174 from random import choice, random, randrange
175 words = LOREM_IPSUM_WORDS.split()
176 result = []
177
178 for _ in xrange(n):
179 next_capitalized = True
180 last_comma = last_fullstop = 0
181 word = None
182 last = None
183 p = []
184
185 # each paragraph contains out of 20 to 100 words.
186 for idx, _ in enumerate(xrange(randrange(min, max))):
187 while True:
188 word = choice(words)
189 if word != last:
190 last = word
191 break
192 if next_capitalized:
193 word = word.capitalize()
194 next_capitalized = False
195 # add commas
196 if idx - randrange(3, 8) > last_comma:
197 last_comma = idx
198 last_fullstop += 2
199 word += ','
200 # add end of sentences
201 if idx - randrange(10, 20) > last_fullstop:
202 last_comma = last_fullstop = idx
203 word += '.'
204 next_capitalized = True
205 p.append(word)
206
207 # ensure that the paragraph ends with a dot.
208 p = u' '.join(p)
209 if p.endswith(','):
210 p = p[:-1] + '.'
211 elif not p.endswith('.'):
212 p += '.'
213 result.append(p)
214
215 if not html:
216 return u'\n\n'.join(result)
217 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
218
219
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200220class Markup(unicode):
221 """Marks a string as being safe for inclusion in HTML/XML output without
222 needing to be escaped. This implements the `__html__` interface a couple
223 of frameworks and web applications use.
224
225 The `escape` function returns markup objects so that double escaping can't
226 happen. If you want to use autoescaping in Jinja just set the finalizer
227 of the environment to `escape`.
228 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200229 __slots__ = ()
230
231 def __html__(self):
232 return self
233
234 def __add__(self, other):
235 if hasattr(other, '__html__') or isinstance(other, basestring):
236 return self.__class__(unicode(self) + unicode(escape(other)))
237 return NotImplemented
238
239 def __radd__(self, other):
240 if hasattr(other, '__html__') or isinstance(other, basestring):
241 return self.__class__(unicode(escape(other)) + unicode(self))
242 return NotImplemented
243
244 def __mul__(self, num):
245 if not isinstance(num, (int, long)):
246 return NotImplemented
247 return self.__class__(unicode.__mul__(self, num))
248 __rmul__ = __mul__
249
250 def __mod__(self, arg):
251 if isinstance(arg, tuple):
252 arg = tuple(imap(_MarkupEscapeHelper, arg))
253 else:
254 arg = _MarkupEscapeHelper(arg)
255 return self.__class__(unicode.__mod__(self, arg))
256
257 def __repr__(self):
258 return '%s(%s)' % (
259 self.__class__.__name__,
260 unicode.__repr__(self)
261 )
262
263 def join(self, seq):
264 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200265 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200266
267 def split(self, *args, **kwargs):
268 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200269 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200270
271 def rsplit(self, *args, **kwargs):
272 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200273 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200274
275 def splitlines(self, *args, **kwargs):
276 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200277 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200278
279 def make_wrapper(name):
280 orig = getattr(unicode, name)
281 def func(self, *args, **kwargs):
282 args = list(args)
283 for idx, arg in enumerate(args):
284 if hasattr(arg, '__html__') or isinstance(arg, basestring):
285 args[idx] = escape(arg)
286 for name, arg in kwargs.iteritems():
287 if hasattr(arg, '__html__') or isinstance(arg, basestring):
288 kwargs[name] = escape(arg)
289 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200290 func.__name__ = orig.__name__
291 func.__doc__ = orig.__doc__
292 return func
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200293 for method in '__getitem__', '__getslice__', 'capitalize', \
294 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200295 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200296 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200297 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200298
299 # new in python 2.5
300 if hasattr(unicode, 'partition'):
Armin Ronacher316157d2008-04-28 18:30:27 +0200301 locals().update(
302 partition=make_wrapper('partition'),
303 rpartition=make_wrapper('rpartition')
304 )
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200305 del method, make_wrapper
306
307
308class _MarkupEscapeHelper(object):
309 """Helper for Markup.__mod__"""
310
311 def __init__(self, obj):
312 self.obj = obj
313
314 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
315 __unicode__ = lambda s: unicode(escape(s.obj))
316 __str__ = lambda s: str(escape(s.obj))
317 __repr__ = lambda s: str(repr(escape(s.obj)))
318 __int__ = lambda s: int(s.obj)
319 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200320
321
322class LRUCache(object):
323 """A simple LRU Cache implementation."""
324 # this is fast for small capacities (something around 200) but doesn't
325 # scale. But as long as it's only used for the database connections in
326 # a non request fallback it's fine.
327
328 def __init__(self, capacity):
329 self.capacity = capacity
330 self._mapping = {}
331 self._queue = deque()
332
333 # alias all queue methods for faster lookup
334 self._popleft = self._queue.popleft
335 self._pop = self._queue.pop
336 if hasattr(self._queue, 'remove'):
337 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200338 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200339 self._append = self._queue.append
340
341 def _remove(self, obj):
342 """Python 2.4 compatibility."""
343 for idx, item in enumerate(self._queue):
344 if item == obj:
345 del self._queue[idx]
346 break
347
348 def copy(self):
349 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200350 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200351 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200352 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200353 return rv
354
355 def get(self, key, default=None):
356 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200357 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200358 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200359 except KeyError:
360 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200361
362 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200363 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200364 leave unchanged. Return the value of this key.
365 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200366 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200367 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200368 except KeyError:
369 self[key] = default
370 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200371
372 def clear(self):
373 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200374 self._wlock.acquire()
375 try:
376 self._mapping.clear()
377 self._queue.clear()
378 finally:
379 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200380
381 def __contains__(self, key):
382 """Check if a key exists in this cache."""
383 return key in self._mapping
384
385 def __len__(self):
386 """Return the current size of the cache."""
387 return len(self._mapping)
388
389 def __repr__(self):
390 return '<%s %r>' % (
391 self.__class__.__name__,
392 self._mapping
393 )
394
395 def __getitem__(self, key):
396 """Get an item from the cache. Moves the item up so that it has the
397 highest priority then.
398
399 Raise an `KeyError` if it does not exist.
400 """
401 rv = self._mapping[key]
402 if self._queue[-1] != key:
403 self._remove(key)
404 self._append(key)
405 return rv
406
407 def __setitem__(self, key, value):
408 """Sets the value for an item. Moves the item up so that it
409 has the highest priority then.
410 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200411 self._wlock.acquire()
412 try:
413 if key in self._mapping:
414 self._remove(key)
415 elif len(self._mapping) == self.capacity:
416 del self._mapping[self._popleft()]
417 self._append(key)
418 self._mapping[key] = value
419 finally:
420 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200421
422 def __delitem__(self, key):
423 """Remove an item from the cache dict.
424 Raise an `KeyError` if it does not exist.
425 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200426 self._wlock.acquire()
427 try:
428 del self._mapping[key]
429 self._remove(key)
430 finally:
431 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200432
433 def __iter__(self):
434 """Iterate over all values in the cache dict, ordered by
435 the most recent usage.
436 """
437 return reversed(self._queue)
438
439 def __reversed__(self):
440 """Iterate over the values in the cache dict, oldest items
441 coming first.
442 """
443 return iter(self._queue)
444
445 __copy__ = copy
446
Armin Ronacherbd33f112008-04-18 09:17:32 +0200447
448# we have to import it down here as the speedups module imports the
449# markup type which is define above.
450try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200451 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200452except ImportError:
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200453 def escape(s):
Armin Ronacherbd33f112008-04-18 09:17:32 +0200454 """Convert the characters &, <, >, and " in string s to HTML-safe
455 sequences. Use this if you need to display text that might contain
456 such characters in HTML.
457 """
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200458 if hasattr(s, '__html__'):
459 return s.__html__()
460 return Markup(unicode(s)
Armin Ronacherbd33f112008-04-18 09:17:32 +0200461 .replace('&', '&amp;')
462 .replace('>', '&gt;')
463 .replace('<', '&lt;')
464 .replace('"', '&quot;')
465 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200466
467 def soft_unicode(s):
468 """Make a string unicode if it isn't already. That way a markup
469 string is not converted back to unicode.
470 """
471 if not isinstance(s, unicode):
472 s = unicode(s)
473 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200474
475
476# partials
477try:
478 from functools import partial
479except ImportError:
480 class partial(object):
481 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200482 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200483 self._args = args
484 self._kwargs = kwargs
485 def __call__(self, *args, **kwargs):
486 kwargs.update(self._kwargs)
487 return self._func(*(self._args + args), **kwargs)