blob: 6a5c9a07c5bc126f198ea8b2e4cc8f7c4869f41d [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 Ronacherf59bac22008-04-20 13:11:43 +020050def import_string(import_name, silent=False):
51 """Imports an object based on a string. This use useful if you want to
52 use import paths as endpoints or something similar. An import path can
53 be specified either in dotted notation (``xml.sax.saxutils.escape``)
54 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
55
56 If the `silent` is True the return value will be `None` if the import
57 fails.
58
59 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +020060 """
Armin Ronacherf59bac22008-04-20 13:11:43 +020061 try:
62 if ':' in import_name:
63 module, obj = import_name.split(':', 1)
64 elif '.' in import_name:
65 items = import_name.split('.')
66 module = '.'.join(items[:-1])
67 obj = items[-1]
68 else:
69 return __import__(import_name)
70 return getattr(__import__(module, None, None, [obj]), obj)
71 except (ImportError, AttributeError):
72 if not silent:
73 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +020074
75
Christoph Hacke9e43bb2008-04-13 23:35:48 +020076def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +020077 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +020078 builtin `pprint`.
79 """
80 try:
81 from pretty import pretty
82 return pretty(obj, verbose=verbose)
83 except ImportError:
84 from pprint import pformat
85 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +020086
87
Christoph Hack80909862008-04-14 01:35:10 +020088def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +020089 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +020090 https:// and www. links. Links can have trailing punctuation (periods,
91 commas, close-parens) and leading punctuation (opening parens) and
92 it'll still do the right thing.
93
94 If trim_url_limit is not None, the URLs in link text will be limited
95 to trim_url_limit characters.
96
97 If nofollow is True, the URLs in link text will get a rel="nofollow"
98 attribute.
99 """
100 trim_url = lambda x, limit=trim_url_limit: limit is not None \
101 and (x[:limit] + (len(x) >=limit and '...'
102 or '')) or x
103 words = _word_split_re.split(text)
104 nofollow_attr = nofollow and ' rel="nofollow"' or ''
105 for i, word in enumerate(words):
106 match = _punctuation_re.match(word)
107 if match:
108 lead, middle, trail = match.groups()
109 if middle.startswith('www.') or (
110 '@' not in middle and
111 not middle.startswith('http://') and
112 len(middle) > 0 and
113 middle[0] in string.letters + string.digits and (
114 middle.endswith('.org') or
115 middle.endswith('.net') or
116 middle.endswith('.com')
117 )):
118 middle = '<a href="http://%s"%s>%s</a>' % (middle,
119 nofollow_attr, trim_url(middle))
120 if middle.startswith('http://') or \
121 middle.startswith('https://'):
122 middle = '<a href="%s"%s>%s</a>' % (middle,
123 nofollow_attr, trim_url(middle))
124 if '@' in middle and not middle.startswith('www.') and \
125 not ':' in middle and _simple_email_re.match(middle):
126 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
127 if lead + middle + trail != word:
128 words[i] = lead + middle + trail
129 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200130
131
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200132def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
133 """Generate some lorem impsum for the template."""
134 from jinja2.constants import LOREM_IPSUM_WORDS
135 from random import choice, random, randrange
136 words = LOREM_IPSUM_WORDS.split()
137 result = []
138
139 for _ in xrange(n):
140 next_capitalized = True
141 last_comma = last_fullstop = 0
142 word = None
143 last = None
144 p = []
145
146 # each paragraph contains out of 20 to 100 words.
147 for idx, _ in enumerate(xrange(randrange(min, max))):
148 while True:
149 word = choice(words)
150 if word != last:
151 last = word
152 break
153 if next_capitalized:
154 word = word.capitalize()
155 next_capitalized = False
156 # add commas
157 if idx - randrange(3, 8) > last_comma:
158 last_comma = idx
159 last_fullstop += 2
160 word += ','
161 # add end of sentences
162 if idx - randrange(10, 20) > last_fullstop:
163 last_comma = last_fullstop = idx
164 word += '.'
165 next_capitalized = True
166 p.append(word)
167
168 # ensure that the paragraph ends with a dot.
169 p = u' '.join(p)
170 if p.endswith(','):
171 p = p[:-1] + '.'
172 elif not p.endswith('.'):
173 p += '.'
174 result.append(p)
175
176 if not html:
177 return u'\n\n'.join(result)
178 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
179
180
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200181class Markup(unicode):
182 """Marks a string as being safe for inclusion in HTML/XML output without
183 needing to be escaped. This implements the `__html__` interface a couple
184 of frameworks and web applications use.
185
186 The `escape` function returns markup objects so that double escaping can't
187 happen. If you want to use autoescaping in Jinja just set the finalizer
188 of the environment to `escape`.
189 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200190 __slots__ = ()
191
192 def __html__(self):
193 return self
194
195 def __add__(self, other):
196 if hasattr(other, '__html__') or isinstance(other, basestring):
197 return self.__class__(unicode(self) + unicode(escape(other)))
198 return NotImplemented
199
200 def __radd__(self, other):
201 if hasattr(other, '__html__') or isinstance(other, basestring):
202 return self.__class__(unicode(escape(other)) + unicode(self))
203 return NotImplemented
204
205 def __mul__(self, num):
206 if not isinstance(num, (int, long)):
207 return NotImplemented
208 return self.__class__(unicode.__mul__(self, num))
209 __rmul__ = __mul__
210
211 def __mod__(self, arg):
212 if isinstance(arg, tuple):
213 arg = tuple(imap(_MarkupEscapeHelper, arg))
214 else:
215 arg = _MarkupEscapeHelper(arg)
216 return self.__class__(unicode.__mod__(self, arg))
217
218 def __repr__(self):
219 return '%s(%s)' % (
220 self.__class__.__name__,
221 unicode.__repr__(self)
222 )
223
224 def join(self, seq):
225 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200226 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200227
228 def split(self, *args, **kwargs):
229 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200230 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200231
232 def rsplit(self, *args, **kwargs):
233 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200234 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200235
236 def splitlines(self, *args, **kwargs):
237 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200238 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200239
240 def make_wrapper(name):
241 orig = getattr(unicode, name)
242 def func(self, *args, **kwargs):
243 args = list(args)
244 for idx, arg in enumerate(args):
245 if hasattr(arg, '__html__') or isinstance(arg, basestring):
246 args[idx] = escape(arg)
247 for name, arg in kwargs.iteritems():
248 if hasattr(arg, '__html__') or isinstance(arg, basestring):
249 kwargs[name] = escape(arg)
250 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200251 func.__name__ = orig.__name__
252 func.__doc__ = orig.__doc__
253 return func
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200254 for method in '__getitem__', '__getslice__', 'capitalize', \
255 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200256 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200257 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200258 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200259
260 # new in python 2.5
261 if hasattr(unicode, 'partition'):
Armin Ronacher316157d2008-04-28 18:30:27 +0200262 locals().update(
263 partition=make_wrapper('partition'),
264 rpartition=make_wrapper('rpartition')
265 )
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200266 del method, make_wrapper
267
268
269class _MarkupEscapeHelper(object):
270 """Helper for Markup.__mod__"""
271
272 def __init__(self, obj):
273 self.obj = obj
274
275 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
276 __unicode__ = lambda s: unicode(escape(s.obj))
277 __str__ = lambda s: str(escape(s.obj))
278 __repr__ = lambda s: str(repr(escape(s.obj)))
279 __int__ = lambda s: int(s.obj)
280 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200281
282
283class LRUCache(object):
284 """A simple LRU Cache implementation."""
285 # this is fast for small capacities (something around 200) but doesn't
286 # scale. But as long as it's only used for the database connections in
287 # a non request fallback it's fine.
288
289 def __init__(self, capacity):
290 self.capacity = capacity
291 self._mapping = {}
292 self._queue = deque()
293
294 # alias all queue methods for faster lookup
295 self._popleft = self._queue.popleft
296 self._pop = self._queue.pop
297 if hasattr(self._queue, 'remove'):
298 self._remove = self._queue.remove
299 self._append = self._queue.append
300
301 def _remove(self, obj):
302 """Python 2.4 compatibility."""
303 for idx, item in enumerate(self._queue):
304 if item == obj:
305 del self._queue[idx]
306 break
307
308 def copy(self):
309 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200310 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200311 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200312 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200313 return rv
314
315 def get(self, key, default=None):
316 """Return an item from the cache dict or `default`"""
317 if key in self:
318 return self[key]
319 return default
320
321 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200322 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200323 leave unchanged. Return the value of this key.
324 """
325 if key in self:
326 return self[key]
327 self[key] = default
328 return default
329
330 def clear(self):
331 """Clear the cache."""
332 self._mapping.clear()
333 self._queue.clear()
334
335 def __contains__(self, key):
336 """Check if a key exists in this cache."""
337 return key in self._mapping
338
339 def __len__(self):
340 """Return the current size of the cache."""
341 return len(self._mapping)
342
343 def __repr__(self):
344 return '<%s %r>' % (
345 self.__class__.__name__,
346 self._mapping
347 )
348
349 def __getitem__(self, key):
350 """Get an item from the cache. Moves the item up so that it has the
351 highest priority then.
352
353 Raise an `KeyError` if it does not exist.
354 """
355 rv = self._mapping[key]
356 if self._queue[-1] != key:
357 self._remove(key)
358 self._append(key)
359 return rv
360
361 def __setitem__(self, key, value):
362 """Sets the value for an item. Moves the item up so that it
363 has the highest priority then.
364 """
365 if key in self._mapping:
366 self._remove(key)
367 elif len(self._mapping) == self.capacity:
368 del self._mapping[self._popleft()]
369 self._append(key)
370 self._mapping[key] = value
371
372 def __delitem__(self, key):
373 """Remove an item from the cache dict.
374 Raise an `KeyError` if it does not exist.
375 """
376 del self._mapping[key]
377 self._remove(key)
378
379 def __iter__(self):
380 """Iterate over all values in the cache dict, ordered by
381 the most recent usage.
382 """
383 return reversed(self._queue)
384
385 def __reversed__(self):
386 """Iterate over the values in the cache dict, oldest items
387 coming first.
388 """
389 return iter(self._queue)
390
391 __copy__ = copy
392
Armin Ronacherbd33f112008-04-18 09:17:32 +0200393
394# we have to import it down here as the speedups module imports the
395# markup type which is define above.
396try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200397 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200398except ImportError:
399 def escape(obj):
400 """Convert the characters &, <, >, and " in string s to HTML-safe
401 sequences. Use this if you need to display text that might contain
402 such characters in HTML.
403 """
404 if hasattr(obj, '__html__'):
405 return obj.__html__()
406 return Markup(unicode(obj)
407 .replace('&', '&amp;')
408 .replace('>', '&gt;')
409 .replace('<', '&lt;')
410 .replace('"', '&quot;')
411 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200412
413 def soft_unicode(s):
414 """Make a string unicode if it isn't already. That way a markup
415 string is not converted back to unicode.
416 """
417 if not isinstance(s, unicode):
418 s = unicode(s)
419 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200420
421
422# partials
423try:
424 from functools import partial
425except ImportError:
426 class partial(object):
427 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200428 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200429 self._args = args
430 self._kwargs = kwargs
431 def __call__(self, *args, **kwargs):
432 kwargs.update(self._kwargs)
433 return self._func(*(self._args + args), **kwargs)