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