blob: c030d24c35795f8232a33e224115a5cc4fe95418 [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 functools import update_wrapper
16from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020017
18
19def escape(obj, attribute=False):
20 """HTML escape an object."""
Armin Ronacher68f77672008-04-17 11:50:39 +020021 if obj is None:
22 return u''
23 elif hasattr(obj, '__html__'):
Armin Ronacher8edbe492008-04-10 20:43:43 +020024 return obj.__html__()
Armin Ronacher18c6ca02008-04-17 10:03:29 +020025 return Markup(unicode(obj)
26 .replace('&', '&')
27 .replace('>', '>')
28 .replace('<', '&lt;')
Armin Ronacher449167d2008-04-11 17:55:05 +020029 .replace('"', '&quot;')
Armin Ronacher18c6ca02008-04-17 10:03:29 +020030 )
Christoph Hacke9e43bb2008-04-13 23:35:48 +020031
32
Armin Ronacher9a027f42008-04-17 11:13:40 +020033def soft_unicode(s):
34 """Make a string unicode if it isn't already. That way a markup
35 string is not converted back to unicode.
36 """
37 if not isinstance(s, unicode):
38 s = unicode(s)
39 return s
40
41
Christoph Hacke9e43bb2008-04-13 23:35:48 +020042def pformat(obj, verbose=False):
43 """
44 Prettyprint an object. Either use the `pretty` library or the
45 builtin `pprint`.
46 """
47 try:
48 from pretty import pretty
49 return pretty(obj, verbose=verbose)
50 except ImportError:
51 from pprint import pformat
52 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +020053
54
55_word_split_re = re.compile(r'(\s+)')
56
57_punctuation_re = re.compile(
Armin Ronacher18c6ca02008-04-17 10:03:29 +020058 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
59 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
60 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
Christoph Hack80909862008-04-14 01:35:10 +020061 )
62)
63
64_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
65
66
67def urlize(text, trim_url_limit=None, nofollow=False):
68 """
69 Converts any URLs in text into clickable links. Works on http://,
70 https:// and www. links. Links can have trailing punctuation (periods,
71 commas, close-parens) and leading punctuation (opening parens) and
72 it'll still do the right thing.
73
74 If trim_url_limit is not None, the URLs in link text will be limited
75 to trim_url_limit characters.
76
77 If nofollow is True, the URLs in link text will get a rel="nofollow"
78 attribute.
79 """
80 trim_url = lambda x, limit=trim_url_limit: limit is not None \
81 and (x[:limit] + (len(x) >=limit and '...'
82 or '')) or x
83 words = _word_split_re.split(text)
84 nofollow_attr = nofollow and ' rel="nofollow"' or ''
85 for i, word in enumerate(words):
86 match = _punctuation_re.match(word)
87 if match:
88 lead, middle, trail = match.groups()
89 if middle.startswith('www.') or (
90 '@' not in middle and
91 not middle.startswith('http://') and
92 len(middle) > 0 and
93 middle[0] in string.letters + string.digits and (
94 middle.endswith('.org') or
95 middle.endswith('.net') or
96 middle.endswith('.com')
97 )):
98 middle = '<a href="http://%s"%s>%s</a>' % (middle,
99 nofollow_attr, trim_url(middle))
100 if middle.startswith('http://') or \
101 middle.startswith('https://'):
102 middle = '<a href="%s"%s>%s</a>' % (middle,
103 nofollow_attr, trim_url(middle))
104 if '@' in middle and not middle.startswith('www.') and \
105 not ':' in middle and _simple_email_re.match(middle):
106 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
107 if lead + middle + trail != word:
108 words[i] = lead + middle + trail
109 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200110
111
112class Markup(unicode):
113 """Marks a string as being safe for inclusion in HTML/XML output without
114 needing to be escaped. This implements the `__html__` interface a couple
115 of frameworks and web applications use.
116
117 The `escape` function returns markup objects so that double escaping can't
118 happen. If you want to use autoescaping in Jinja just set the finalizer
119 of the environment to `escape`.
120 """
121
122 __slots__ = ()
123
124 def __html__(self):
125 return self
126
127 def __add__(self, other):
128 if hasattr(other, '__html__') or isinstance(other, basestring):
129 return self.__class__(unicode(self) + unicode(escape(other)))
130 return NotImplemented
131
132 def __radd__(self, other):
133 if hasattr(other, '__html__') or isinstance(other, basestring):
134 return self.__class__(unicode(escape(other)) + unicode(self))
135 return NotImplemented
136
137 def __mul__(self, num):
138 if not isinstance(num, (int, long)):
139 return NotImplemented
140 return self.__class__(unicode.__mul__(self, num))
141 __rmul__ = __mul__
142
143 def __mod__(self, arg):
144 if isinstance(arg, tuple):
145 arg = tuple(imap(_MarkupEscapeHelper, arg))
146 else:
147 arg = _MarkupEscapeHelper(arg)
148 return self.__class__(unicode.__mod__(self, arg))
149
150 def __repr__(self):
151 return '%s(%s)' % (
152 self.__class__.__name__,
153 unicode.__repr__(self)
154 )
155
156 def join(self, seq):
157 return self.__class__(unicode.join(self, imap(escape, seq)))
158
159 def split(self, *args, **kwargs):
160 return map(self.__class__, unicode.split(self, *args, **kwargs))
161
162 def rsplit(self, *args, **kwargs):
163 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
164
165 def splitlines(self, *args, **kwargs):
166 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
167
168 def make_wrapper(name):
169 orig = getattr(unicode, name)
170 def func(self, *args, **kwargs):
171 args = list(args)
172 for idx, arg in enumerate(args):
173 if hasattr(arg, '__html__') or isinstance(arg, basestring):
174 args[idx] = escape(arg)
175 for name, arg in kwargs.iteritems():
176 if hasattr(arg, '__html__') or isinstance(arg, basestring):
177 kwargs[name] = escape(arg)
178 return self.__class__(orig(self, *args, **kwargs))
179 return update_wrapper(func, orig, ('__name__', '__doc__'))
180 for method in '__getitem__', '__getslice__', 'capitalize', \
181 'title', 'lower', 'upper', 'replace', 'ljust', \
182 'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
183 'strip', 'translate', 'expandtabs', 'rpartition', \
184 'swapcase', 'zfill':
185 locals()[method] = make_wrapper(method)
186 del method, make_wrapper
187
188
189class _MarkupEscapeHelper(object):
190 """Helper for Markup.__mod__"""
191
192 def __init__(self, obj):
193 self.obj = obj
194
195 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
196 __unicode__ = lambda s: unicode(escape(s.obj))
197 __str__ = lambda s: str(escape(s.obj))
198 __repr__ = lambda s: str(repr(escape(s.obj)))
199 __int__ = lambda s: int(s.obj)
200 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200201
202
203class LRUCache(object):
204 """A simple LRU Cache implementation."""
205 # this is fast for small capacities (something around 200) but doesn't
206 # scale. But as long as it's only used for the database connections in
207 # a non request fallback it's fine.
208
209 def __init__(self, capacity):
210 self.capacity = capacity
211 self._mapping = {}
212 self._queue = deque()
213
214 # alias all queue methods for faster lookup
215 self._popleft = self._queue.popleft
216 self._pop = self._queue.pop
217 if hasattr(self._queue, 'remove'):
218 self._remove = self._queue.remove
219 self._append = self._queue.append
220
221 def _remove(self, obj):
222 """Python 2.4 compatibility."""
223 for idx, item in enumerate(self._queue):
224 if item == obj:
225 del self._queue[idx]
226 break
227
228 def copy(self):
229 """Return an shallow copy of the instance."""
230 rv = LRUCache(self.capacity)
231 rv._mapping.update(self._mapping)
232 rv._queue = self._queue[:]
233 return rv
234
235 def get(self, key, default=None):
236 """Return an item from the cache dict or `default`"""
237 if key in self:
238 return self[key]
239 return default
240
241 def setdefault(self, key, default=None):
242 """
243 Set `default` if the key is not in the cache otherwise
244 leave unchanged. Return the value of this key.
245 """
246 if key in self:
247 return self[key]
248 self[key] = default
249 return default
250
251 def clear(self):
252 """Clear the cache."""
253 self._mapping.clear()
254 self._queue.clear()
255
256 def __contains__(self, key):
257 """Check if a key exists in this cache."""
258 return key in self._mapping
259
260 def __len__(self):
261 """Return the current size of the cache."""
262 return len(self._mapping)
263
264 def __repr__(self):
265 return '<%s %r>' % (
266 self.__class__.__name__,
267 self._mapping
268 )
269
270 def __getitem__(self, key):
271 """Get an item from the cache. Moves the item up so that it has the
272 highest priority then.
273
274 Raise an `KeyError` if it does not exist.
275 """
276 rv = self._mapping[key]
277 if self._queue[-1] != key:
278 self._remove(key)
279 self._append(key)
280 return rv
281
282 def __setitem__(self, key, value):
283 """Sets the value for an item. Moves the item up so that it
284 has the highest priority then.
285 """
286 if key in self._mapping:
287 self._remove(key)
288 elif len(self._mapping) == self.capacity:
289 del self._mapping[self._popleft()]
290 self._append(key)
291 self._mapping[key] = value
292
293 def __delitem__(self, key):
294 """Remove an item from the cache dict.
295 Raise an `KeyError` if it does not exist.
296 """
297 del self._mapping[key]
298 self._remove(key)
299
300 def __iter__(self):
301 """Iterate over all values in the cache dict, ordered by
302 the most recent usage.
303 """
304 return reversed(self._queue)
305
306 def __reversed__(self):
307 """Iterate over the values in the cache dict, oldest items
308 coming first.
309 """
310 return iter(self._queue)
311
312 __copy__ = copy
313
314 def __deepcopy__(self):
315 """Return a deep copy of the LRU Cache"""
316 rv = LRUCache(self.capacity)
317 rv._mapping = deepcopy(self._mapping)
318 rv._queue = deepcopy(self._queue)
319 return rv