blob: e8f6fd75ff3f5787257f081291f18057737b1a5e [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 Ronacher814f6c22008-04-17 15:52:23 +020013from collections import deque
14from copy import deepcopy
Armin Ronacher18c6ca02008-04-17 10:03:29 +020015from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020016
17
Armin Ronacherbe4ae242008-04-18 09:49:08 +020018_word_split_re = re.compile(r'(\s+)')
19_punctuation_re = re.compile(
20 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
21 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
22 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
23 )
24)
25_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
26
27
Armin Ronacher7259c762008-04-30 13:03:59 +020028# special singleton representing missing values for the runtime
29missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
30
31
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020032def contextfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020033 """This decorator can be used to mark a callable as context callable. A
34 context callable is passed the active context as first argument if it
35 was directly stored in the context.
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020036 """
37 f.contextfunction = True
38 return f
39
40
Armin Ronacher203bfcb2008-04-24 21:54:44 +020041def environmentfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020042 """This decorator can be used to mark a callable as environment callable.
43 A environment callable is passed the current environment as first argument
44 if it was directly stored in the context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +020045 """
46 f.environmentfunction = True
47 return f
48
49
Armin Ronacher187bde12008-05-01 18:19:16 +020050def clear_caches():
51 """Jinja2 keeps internal caches for environments and lexers. These are
52 used so that Jinja2 doesn't have to recreate environments and lexers all
53 the time. Normally you don't have to care about that but if you are
54 messuring memory consumption you may want to clean the caches.
55 """
56 from jinja2.environment import _spontaneous_environments
57 from jinja2.lexer import _lexer_cache
58 _spontaneous_environments.clear()
59 _lexer_cache.clear()
60
61
Armin Ronacherf59bac22008-04-20 13:11:43 +020062def import_string(import_name, silent=False):
63 """Imports an object based on a string. This use useful if you want to
64 use import paths as endpoints or something similar. An import path can
65 be specified either in dotted notation (``xml.sax.saxutils.escape``)
66 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
67
68 If the `silent` is True the return value will be `None` if the import
69 fails.
70
71 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +020072 """
Armin Ronacherf59bac22008-04-20 13:11:43 +020073 try:
74 if ':' in import_name:
75 module, obj = import_name.split(':', 1)
76 elif '.' in import_name:
77 items = import_name.split('.')
78 module = '.'.join(items[:-1])
79 obj = items[-1]
80 else:
81 return __import__(import_name)
82 return getattr(__import__(module, None, None, [obj]), obj)
83 except (ImportError, AttributeError):
84 if not silent:
85 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +020086
87
Christoph Hacke9e43bb2008-04-13 23:35:48 +020088def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +020089 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +020090 builtin `pprint`.
91 """
92 try:
93 from pretty import pretty
94 return pretty(obj, verbose=verbose)
95 except ImportError:
96 from pprint import pformat
97 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +020098
99
Christoph Hack80909862008-04-14 01:35:10 +0200100def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200101 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200102 https:// and www. links. Links can have trailing punctuation (periods,
103 commas, close-parens) and leading punctuation (opening parens) and
104 it'll still do the right thing.
105
106 If trim_url_limit is not None, the URLs in link text will be limited
107 to trim_url_limit characters.
108
109 If nofollow is True, the URLs in link text will get a rel="nofollow"
110 attribute.
111 """
112 trim_url = lambda x, limit=trim_url_limit: limit is not None \
113 and (x[:limit] + (len(x) >=limit and '...'
114 or '')) or x
115 words = _word_split_re.split(text)
116 nofollow_attr = nofollow and ' rel="nofollow"' or ''
117 for i, word in enumerate(words):
118 match = _punctuation_re.match(word)
119 if match:
120 lead, middle, trail = match.groups()
121 if middle.startswith('www.') or (
122 '@' not in middle and
123 not middle.startswith('http://') and
124 len(middle) > 0 and
125 middle[0] in string.letters + string.digits and (
126 middle.endswith('.org') or
127 middle.endswith('.net') or
128 middle.endswith('.com')
129 )):
130 middle = '<a href="http://%s"%s>%s</a>' % (middle,
131 nofollow_attr, trim_url(middle))
132 if middle.startswith('http://') or \
133 middle.startswith('https://'):
134 middle = '<a href="%s"%s>%s</a>' % (middle,
135 nofollow_attr, trim_url(middle))
136 if '@' in middle and not middle.startswith('www.') and \
137 not ':' in middle and _simple_email_re.match(middle):
138 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
139 if lead + middle + trail != word:
140 words[i] = lead + middle + trail
141 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200142
143
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200144def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
145 """Generate some lorem impsum for the template."""
146 from jinja2.constants import LOREM_IPSUM_WORDS
147 from random import choice, random, randrange
148 words = LOREM_IPSUM_WORDS.split()
149 result = []
150
151 for _ in xrange(n):
152 next_capitalized = True
153 last_comma = last_fullstop = 0
154 word = None
155 last = None
156 p = []
157
158 # each paragraph contains out of 20 to 100 words.
159 for idx, _ in enumerate(xrange(randrange(min, max))):
160 while True:
161 word = choice(words)
162 if word != last:
163 last = word
164 break
165 if next_capitalized:
166 word = word.capitalize()
167 next_capitalized = False
168 # add commas
169 if idx - randrange(3, 8) > last_comma:
170 last_comma = idx
171 last_fullstop += 2
172 word += ','
173 # add end of sentences
174 if idx - randrange(10, 20) > last_fullstop:
175 last_comma = last_fullstop = idx
176 word += '.'
177 next_capitalized = True
178 p.append(word)
179
180 # ensure that the paragraph ends with a dot.
181 p = u' '.join(p)
182 if p.endswith(','):
183 p = p[:-1] + '.'
184 elif not p.endswith('.'):
185 p += '.'
186 result.append(p)
187
188 if not html:
189 return u'\n\n'.join(result)
190 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
191
192
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200193class Markup(unicode):
194 """Marks a string as being safe for inclusion in HTML/XML output without
195 needing to be escaped. This implements the `__html__` interface a couple
196 of frameworks and web applications use.
197
198 The `escape` function returns markup objects so that double escaping can't
199 happen. If you want to use autoescaping in Jinja just set the finalizer
200 of the environment to `escape`.
201 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200202 __slots__ = ()
203
204 def __html__(self):
205 return self
206
207 def __add__(self, other):
208 if hasattr(other, '__html__') or isinstance(other, basestring):
209 return self.__class__(unicode(self) + unicode(escape(other)))
210 return NotImplemented
211
212 def __radd__(self, other):
213 if hasattr(other, '__html__') or isinstance(other, basestring):
214 return self.__class__(unicode(escape(other)) + unicode(self))
215 return NotImplemented
216
217 def __mul__(self, num):
218 if not isinstance(num, (int, long)):
219 return NotImplemented
220 return self.__class__(unicode.__mul__(self, num))
221 __rmul__ = __mul__
222
223 def __mod__(self, arg):
224 if isinstance(arg, tuple):
225 arg = tuple(imap(_MarkupEscapeHelper, arg))
226 else:
227 arg = _MarkupEscapeHelper(arg)
228 return self.__class__(unicode.__mod__(self, arg))
229
230 def __repr__(self):
231 return '%s(%s)' % (
232 self.__class__.__name__,
233 unicode.__repr__(self)
234 )
235
236 def join(self, seq):
237 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200238 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200239
240 def split(self, *args, **kwargs):
241 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200242 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200243
244 def rsplit(self, *args, **kwargs):
245 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200246 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200247
248 def splitlines(self, *args, **kwargs):
249 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200250 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200251
252 def make_wrapper(name):
253 orig = getattr(unicode, name)
254 def func(self, *args, **kwargs):
255 args = list(args)
256 for idx, arg in enumerate(args):
257 if hasattr(arg, '__html__') or isinstance(arg, basestring):
258 args[idx] = escape(arg)
259 for name, arg in kwargs.iteritems():
260 if hasattr(arg, '__html__') or isinstance(arg, basestring):
261 kwargs[name] = escape(arg)
262 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200263 func.__name__ = orig.__name__
264 func.__doc__ = orig.__doc__
265 return func
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200266 for method in '__getitem__', '__getslice__', 'capitalize', \
267 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200268 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200269 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200270 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200271
272 # new in python 2.5
273 if hasattr(unicode, 'partition'):
Armin Ronacher316157d2008-04-28 18:30:27 +0200274 locals().update(
275 partition=make_wrapper('partition'),
276 rpartition=make_wrapper('rpartition')
277 )
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200278 del method, make_wrapper
279
280
281class _MarkupEscapeHelper(object):
282 """Helper for Markup.__mod__"""
283
284 def __init__(self, obj):
285 self.obj = obj
286
287 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
288 __unicode__ = lambda s: unicode(escape(s.obj))
289 __str__ = lambda s: str(escape(s.obj))
290 __repr__ = lambda s: str(repr(escape(s.obj)))
291 __int__ = lambda s: int(s.obj)
292 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200293
294
295class LRUCache(object):
296 """A simple LRU Cache implementation."""
297 # this is fast for small capacities (something around 200) but doesn't
298 # scale. But as long as it's only used for the database connections in
299 # a non request fallback it's fine.
300
301 def __init__(self, capacity):
302 self.capacity = capacity
303 self._mapping = {}
304 self._queue = deque()
305
306 # alias all queue methods for faster lookup
307 self._popleft = self._queue.popleft
308 self._pop = self._queue.pop
309 if hasattr(self._queue, 'remove'):
310 self._remove = self._queue.remove
311 self._append = self._queue.append
312
313 def _remove(self, obj):
314 """Python 2.4 compatibility."""
315 for idx, item in enumerate(self._queue):
316 if item == obj:
317 del self._queue[idx]
318 break
319
320 def copy(self):
321 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200322 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200323 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200324 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200325 return rv
326
327 def get(self, key, default=None):
328 """Return an item from the cache dict or `default`"""
329 if key in self:
330 return self[key]
331 return default
332
333 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200334 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200335 leave unchanged. Return the value of this key.
336 """
337 if key in self:
338 return self[key]
339 self[key] = default
340 return default
341
342 def clear(self):
343 """Clear the cache."""
344 self._mapping.clear()
345 self._queue.clear()
346
347 def __contains__(self, key):
348 """Check if a key exists in this cache."""
349 return key in self._mapping
350
351 def __len__(self):
352 """Return the current size of the cache."""
353 return len(self._mapping)
354
355 def __repr__(self):
356 return '<%s %r>' % (
357 self.__class__.__name__,
358 self._mapping
359 )
360
361 def __getitem__(self, key):
362 """Get an item from the cache. Moves the item up so that it has the
363 highest priority then.
364
365 Raise an `KeyError` if it does not exist.
366 """
367 rv = self._mapping[key]
368 if self._queue[-1] != key:
369 self._remove(key)
370 self._append(key)
371 return rv
372
373 def __setitem__(self, key, value):
374 """Sets the value for an item. Moves the item up so that it
375 has the highest priority then.
376 """
377 if key in self._mapping:
378 self._remove(key)
379 elif len(self._mapping) == self.capacity:
380 del self._mapping[self._popleft()]
381 self._append(key)
382 self._mapping[key] = value
383
384 def __delitem__(self, key):
385 """Remove an item from the cache dict.
386 Raise an `KeyError` if it does not exist.
387 """
388 del self._mapping[key]
389 self._remove(key)
390
391 def __iter__(self):
392 """Iterate over all values in the cache dict, ordered by
393 the most recent usage.
394 """
395 return reversed(self._queue)
396
397 def __reversed__(self):
398 """Iterate over the values in the cache dict, oldest items
399 coming first.
400 """
401 return iter(self._queue)
402
403 __copy__ = copy
404
Armin Ronacherbd33f112008-04-18 09:17:32 +0200405
406# we have to import it down here as the speedups module imports the
407# markup type which is define above.
408try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200409 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200410except ImportError:
411 def escape(obj):
412 """Convert the characters &, <, >, and " in string s to HTML-safe
413 sequences. Use this if you need to display text that might contain
414 such characters in HTML.
415 """
416 if hasattr(obj, '__html__'):
417 return obj.__html__()
418 return Markup(unicode(obj)
419 .replace('&', '&amp;')
420 .replace('>', '&gt;')
421 .replace('<', '&lt;')
422 .replace('"', '&quot;')
423 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200424
425 def soft_unicode(s):
426 """Make a string unicode if it isn't already. That way a markup
427 string is not converted back to unicode.
428 """
429 if not isinstance(s, unicode):
430 s = unicode(s)
431 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200432
433
434# partials
435try:
436 from functools import partial
437except ImportError:
438 class partial(object):
439 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200440 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200441 self._args = args
442 self._kwargs = kwargs
443 def __call__(self, *args, **kwargs):
444 kwargs.update(self._kwargs)
445 return self._func(*(self._args + args), **kwargs)