OLD | NEW |
| (Empty) |
1 # -*- coding: utf-8 -*- | |
2 """ | |
3 jinja2.utils | |
4 ~~~~~~~~~~~~ | |
5 | |
6 Utility functions. | |
7 | |
8 :copyright: (c) 2010 by the Jinja Team. | |
9 :license: BSD, see LICENSE for more details. | |
10 """ | |
11 import re | |
12 import errno | |
13 from collections import deque | |
14 from jinja2._compat import text_type, string_types, implements_iterator, \ | |
15 allocate_lock, url_quote | |
16 | |
17 | |
18 _word_split_re = re.compile(r'(\s+)') | |
19 _punctuation_re = re.compile( | |
20 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( | |
21 '|'.join(map(re.escape, ('(', '<', '<'))), | |
22 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) | |
23 ) | |
24 ) | |
25 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') | |
26 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') | |
27 _entity_re = re.compile(r'&([^;]+);') | |
28 _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
29 _digits = '0123456789' | |
30 | |
31 # special singleton representing missing values for the runtime | |
32 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() | |
33 | |
34 # internal code | |
35 internal_code = set() | |
36 | |
37 concat = u''.join | |
38 | |
39 | |
40 def contextfunction(f): | |
41 """This decorator can be used to mark a function or method context callable. | |
42 A context callable is passed the active :class:`Context` as first argument w
hen | |
43 called from the template. This is useful if a function wants to get access | |
44 to the context or functions provided on the context object. For example | |
45 a function that returns a sorted list of template variables the current | |
46 template exports could look like this:: | |
47 | |
48 @contextfunction | |
49 def get_exported_names(context): | |
50 return sorted(context.exported_vars) | |
51 """ | |
52 f.contextfunction = True | |
53 return f | |
54 | |
55 | |
56 def evalcontextfunction(f): | |
57 """This decorator can be used to mark a function or method as an eval | |
58 context callable. This is similar to the :func:`contextfunction` | |
59 but instead of passing the context, an evaluation context object is | |
60 passed. For more information about the eval context, see | |
61 :ref:`eval-context`. | |
62 | |
63 .. versionadded:: 2.4 | |
64 """ | |
65 f.evalcontextfunction = True | |
66 return f | |
67 | |
68 | |
69 def environmentfunction(f): | |
70 """This decorator can be used to mark a function or method as environment | |
71 callable. This decorator works exactly like the :func:`contextfunction` | |
72 decorator just that the first argument is the active :class:`Environment` | |
73 and not context. | |
74 """ | |
75 f.environmentfunction = True | |
76 return f | |
77 | |
78 | |
79 def internalcode(f): | |
80 """Marks the function as internally used""" | |
81 internal_code.add(f.__code__) | |
82 return f | |
83 | |
84 | |
85 def is_undefined(obj): | |
86 """Check if the object passed is undefined. This does nothing more than | |
87 performing an instance check against :class:`Undefined` but looks nicer. | |
88 This can be used for custom filters or tests that want to react to | |
89 undefined variables. For example a custom default filter can look like | |
90 this:: | |
91 | |
92 def default(var, default=''): | |
93 if is_undefined(var): | |
94 return default | |
95 return var | |
96 """ | |
97 from jinja2.runtime import Undefined | |
98 return isinstance(obj, Undefined) | |
99 | |
100 | |
101 def consume(iterable): | |
102 """Consumes an iterable without doing anything with it.""" | |
103 for event in iterable: | |
104 pass | |
105 | |
106 | |
107 def clear_caches(): | |
108 """Jinja2 keeps internal caches for environments and lexers. These are | |
109 used so that Jinja2 doesn't have to recreate environments and lexers all | |
110 the time. Normally you don't have to care about that but if you are | |
111 messuring memory consumption you may want to clean the caches. | |
112 """ | |
113 from jinja2.environment import _spontaneous_environments | |
114 from jinja2.lexer import _lexer_cache | |
115 _spontaneous_environments.clear() | |
116 _lexer_cache.clear() | |
117 | |
118 | |
119 def import_string(import_name, silent=False): | |
120 """Imports an object based on a string. This is useful if you want to | |
121 use import paths as endpoints or something similar. An import path can | |
122 be specified either in dotted notation (``xml.sax.saxutils.escape``) | |
123 or with a colon as object delimiter (``xml.sax.saxutils:escape``). | |
124 | |
125 If the `silent` is True the return value will be `None` if the import | |
126 fails. | |
127 | |
128 :return: imported object | |
129 """ | |
130 try: | |
131 if ':' in import_name: | |
132 module, obj = import_name.split(':', 1) | |
133 elif '.' in import_name: | |
134 items = import_name.split('.') | |
135 module = '.'.join(items[:-1]) | |
136 obj = items[-1] | |
137 else: | |
138 return __import__(import_name) | |
139 return getattr(__import__(module, None, None, [obj]), obj) | |
140 except (ImportError, AttributeError): | |
141 if not silent: | |
142 raise | |
143 | |
144 | |
145 def open_if_exists(filename, mode='rb'): | |
146 """Returns a file descriptor for the filename if that file exists, | |
147 otherwise `None`. | |
148 """ | |
149 try: | |
150 return open(filename, mode) | |
151 except IOError as e: | |
152 if e.errno not in (errno.ENOENT, errno.EISDIR): | |
153 raise | |
154 | |
155 | |
156 def object_type_repr(obj): | |
157 """Returns the name of the object's type. For some recognized | |
158 singletons the name of the object is returned instead. (For | |
159 example for `None` and `Ellipsis`). | |
160 """ | |
161 if obj is None: | |
162 return 'None' | |
163 elif obj is Ellipsis: | |
164 return 'Ellipsis' | |
165 # __builtin__ in 2.x, builtins in 3.x | |
166 if obj.__class__.__module__ in ('__builtin__', 'builtins'): | |
167 name = obj.__class__.__name__ | |
168 else: | |
169 name = obj.__class__.__module__ + '.' + obj.__class__.__name__ | |
170 return '%s object' % name | |
171 | |
172 | |
173 def pformat(obj, verbose=False): | |
174 """Prettyprint an object. Either use the `pretty` library or the | |
175 builtin `pprint`. | |
176 """ | |
177 try: | |
178 from pretty import pretty | |
179 return pretty(obj, verbose=verbose) | |
180 except ImportError: | |
181 from pprint import pformat | |
182 return pformat(obj) | |
183 | |
184 | |
185 def urlize(text, trim_url_limit=None, nofollow=False): | |
186 """Converts any URLs in text into clickable links. Works on http://, | |
187 https:// and www. links. Links can have trailing punctuation (periods, | |
188 commas, close-parens) and leading punctuation (opening parens) and | |
189 it'll still do the right thing. | |
190 | |
191 If trim_url_limit is not None, the URLs in link text will be limited | |
192 to trim_url_limit characters. | |
193 | |
194 If nofollow is True, the URLs in link text will get a rel="nofollow" | |
195 attribute. | |
196 """ | |
197 trim_url = lambda x, limit=trim_url_limit: limit is not None \ | |
198 and (x[:limit] + (len(x) >=limit and '...' | |
199 or '')) or x | |
200 words = _word_split_re.split(text_type(escape(text))) | |
201 nofollow_attr = nofollow and ' rel="nofollow"' or '' | |
202 for i, word in enumerate(words): | |
203 match = _punctuation_re.match(word) | |
204 if match: | |
205 lead, middle, trail = match.groups() | |
206 if middle.startswith('www.') or ( | |
207 '@' not in middle and | |
208 not middle.startswith('http://') and | |
209 not middle.startswith('https://') and | |
210 len(middle) > 0 and | |
211 middle[0] in _letters + _digits and ( | |
212 middle.endswith('.org') or | |
213 middle.endswith('.net') or | |
214 middle.endswith('.com') | |
215 )): | |
216 middle = '<a href="http://%s"%s>%s</a>' % (middle, | |
217 nofollow_attr, trim_url(middle)) | |
218 if middle.startswith('http://') or \ | |
219 middle.startswith('https://'): | |
220 middle = '<a href="%s"%s>%s</a>' % (middle, | |
221 nofollow_attr, trim_url(middle)) | |
222 if '@' in middle and not middle.startswith('www.') and \ | |
223 not ':' in middle and _simple_email_re.match(middle): | |
224 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) | |
225 if lead + middle + trail != word: | |
226 words[i] = lead + middle + trail | |
227 return u''.join(words) | |
228 | |
229 | |
230 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): | |
231 """Generate some lorem impsum for the template.""" | |
232 from jinja2.constants import LOREM_IPSUM_WORDS | |
233 from random import choice, randrange | |
234 words = LOREM_IPSUM_WORDS.split() | |
235 result = [] | |
236 | |
237 for _ in range(n): | |
238 next_capitalized = True | |
239 last_comma = last_fullstop = 0 | |
240 word = None | |
241 last = None | |
242 p = [] | |
243 | |
244 # each paragraph contains out of 20 to 100 words. | |
245 for idx, _ in enumerate(range(randrange(min, max))): | |
246 while True: | |
247 word = choice(words) | |
248 if word != last: | |
249 last = word | |
250 break | |
251 if next_capitalized: | |
252 word = word.capitalize() | |
253 next_capitalized = False | |
254 # add commas | |
255 if idx - randrange(3, 8) > last_comma: | |
256 last_comma = idx | |
257 last_fullstop += 2 | |
258 word += ',' | |
259 # add end of sentences | |
260 if idx - randrange(10, 20) > last_fullstop: | |
261 last_comma = last_fullstop = idx | |
262 word += '.' | |
263 next_capitalized = True | |
264 p.append(word) | |
265 | |
266 # ensure that the paragraph ends with a dot. | |
267 p = u' '.join(p) | |
268 if p.endswith(','): | |
269 p = p[:-1] + '.' | |
270 elif not p.endswith('.'): | |
271 p += '.' | |
272 result.append(p) | |
273 | |
274 if not html: | |
275 return u'\n\n'.join(result) | |
276 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) | |
277 | |
278 | |
279 def unicode_urlencode(obj, charset='utf-8'): | |
280 """URL escapes a single bytestring or unicode string with the | |
281 given charset if applicable to URL safe quoting under all rules | |
282 that need to be considered under all supported Python versions. | |
283 | |
284 If non strings are provided they are converted to their unicode | |
285 representation first. | |
286 """ | |
287 if not isinstance(obj, string_types): | |
288 obj = text_type(obj) | |
289 if isinstance(obj, text_type): | |
290 obj = obj.encode(charset) | |
291 return text_type(url_quote(obj)) | |
292 | |
293 | |
294 class LRUCache(object): | |
295 """A simple LRU Cache implementation.""" | |
296 | |
297 # this is fast for small capacities (something below 1000) but doesn't | |
298 # scale. But as long as it's only used as storage for templates this | |
299 # won't do any harm. | |
300 | |
301 def __init__(self, capacity): | |
302 self.capacity = capacity | |
303 self._mapping = {} | |
304 self._queue = deque() | |
305 self._postinit() | |
306 | |
307 def _postinit(self): | |
308 # alias all queue methods for faster lookup | |
309 self._popleft = self._queue.popleft | |
310 self._pop = self._queue.pop | |
311 self._remove = self._queue.remove | |
312 self._wlock = allocate_lock() | |
313 self._append = self._queue.append | |
314 | |
315 def __getstate__(self): | |
316 return { | |
317 'capacity': self.capacity, | |
318 '_mapping': self._mapping, | |
319 '_queue': self._queue | |
320 } | |
321 | |
322 def __setstate__(self, d): | |
323 self.__dict__.update(d) | |
324 self._postinit() | |
325 | |
326 def __getnewargs__(self): | |
327 return (self.capacity,) | |
328 | |
329 def copy(self): | |
330 """Return a shallow copy of the instance.""" | |
331 rv = self.__class__(self.capacity) | |
332 rv._mapping.update(self._mapping) | |
333 rv._queue = deque(self._queue) | |
334 return rv | |
335 | |
336 def get(self, key, default=None): | |
337 """Return an item from the cache dict or `default`""" | |
338 try: | |
339 return self[key] | |
340 except KeyError: | |
341 return default | |
342 | |
343 def setdefault(self, key, default=None): | |
344 """Set `default` if the key is not in the cache otherwise | |
345 leave unchanged. Return the value of this key. | |
346 """ | |
347 self._wlock.acquire() | |
348 try: | |
349 try: | |
350 return self[key] | |
351 except KeyError: | |
352 self[key] = default | |
353 return default | |
354 finally: | |
355 self._wlock.release() | |
356 | |
357 def clear(self): | |
358 """Clear the cache.""" | |
359 self._wlock.acquire() | |
360 try: | |
361 self._mapping.clear() | |
362 self._queue.clear() | |
363 finally: | |
364 self._wlock.release() | |
365 | |
366 def __contains__(self, key): | |
367 """Check if a key exists in this cache.""" | |
368 return key in self._mapping | |
369 | |
370 def __len__(self): | |
371 """Return the current size of the cache.""" | |
372 return len(self._mapping) | |
373 | |
374 def __repr__(self): | |
375 return '<%s %r>' % ( | |
376 self.__class__.__name__, | |
377 self._mapping | |
378 ) | |
379 | |
380 def __getitem__(self, key): | |
381 """Get an item from the cache. Moves the item up so that it has the | |
382 highest priority then. | |
383 | |
384 Raise a `KeyError` if it does not exist. | |
385 """ | |
386 self._wlock.acquire() | |
387 try: | |
388 rv = self._mapping[key] | |
389 if self._queue[-1] != key: | |
390 try: | |
391 self._remove(key) | |
392 except ValueError: | |
393 # if something removed the key from the container | |
394 # when we read, ignore the ValueError that we would | |
395 # get otherwise. | |
396 pass | |
397 self._append(key) | |
398 return rv | |
399 finally: | |
400 self._wlock.release() | |
401 | |
402 def __setitem__(self, key, value): | |
403 """Sets the value for an item. Moves the item up so that it | |
404 has the highest priority then. | |
405 """ | |
406 self._wlock.acquire() | |
407 try: | |
408 if key in self._mapping: | |
409 self._remove(key) | |
410 elif len(self._mapping) == self.capacity: | |
411 del self._mapping[self._popleft()] | |
412 self._append(key) | |
413 self._mapping[key] = value | |
414 finally: | |
415 self._wlock.release() | |
416 | |
417 def __delitem__(self, key): | |
418 """Remove an item from the cache dict. | |
419 Raise a `KeyError` if it does not exist. | |
420 """ | |
421 self._wlock.acquire() | |
422 try: | |
423 del self._mapping[key] | |
424 try: | |
425 self._remove(key) | |
426 except ValueError: | |
427 # __getitem__ is not locked, it might happen | |
428 pass | |
429 finally: | |
430 self._wlock.release() | |
431 | |
432 def items(self): | |
433 """Return a list of items.""" | |
434 result = [(key, self._mapping[key]) for key in list(self._queue)] | |
435 result.reverse() | |
436 return result | |
437 | |
438 def iteritems(self): | |
439 """Iterate over all items.""" | |
440 return iter(self.items()) | |
441 | |
442 def values(self): | |
443 """Return a list of all values.""" | |
444 return [x[1] for x in self.items()] | |
445 | |
446 def itervalue(self): | |
447 """Iterate over all values.""" | |
448 return iter(self.values()) | |
449 | |
450 def keys(self): | |
451 """Return a list of all keys ordered by most recent usage.""" | |
452 return list(self) | |
453 | |
454 def iterkeys(self): | |
455 """Iterate over all keys in the cache dict, ordered by | |
456 the most recent usage. | |
457 """ | |
458 return reversed(tuple(self._queue)) | |
459 | |
460 __iter__ = iterkeys | |
461 | |
462 def __reversed__(self): | |
463 """Iterate over the values in the cache dict, oldest items | |
464 coming first. | |
465 """ | |
466 return iter(tuple(self._queue)) | |
467 | |
468 __copy__ = copy | |
469 | |
470 | |
471 # register the LRU cache as mutable mapping if possible | |
472 try: | |
473 from collections import MutableMapping | |
474 MutableMapping.register(LRUCache) | |
475 except ImportError: | |
476 pass | |
477 | |
478 | |
479 @implements_iterator | |
480 class Cycler(object): | |
481 """A cycle helper for templates.""" | |
482 | |
483 def __init__(self, *items): | |
484 if not items: | |
485 raise RuntimeError('at least one item has to be provided') | |
486 self.items = items | |
487 self.reset() | |
488 | |
489 def reset(self): | |
490 """Resets the cycle.""" | |
491 self.pos = 0 | |
492 | |
493 @property | |
494 def current(self): | |
495 """Returns the current item.""" | |
496 return self.items[self.pos] | |
497 | |
498 def __next__(self): | |
499 """Goes one item ahead and returns it.""" | |
500 rv = self.current | |
501 self.pos = (self.pos + 1) % len(self.items) | |
502 return rv | |
503 | |
504 | |
505 class Joiner(object): | |
506 """A joining helper for templates.""" | |
507 | |
508 def __init__(self, sep=u', '): | |
509 self.sep = sep | |
510 self.used = False | |
511 | |
512 def __call__(self): | |
513 if not self.used: | |
514 self.used = True | |
515 return u'' | |
516 return self.sep | |
517 | |
518 | |
519 # Imported here because that's where it was in the past | |
520 from markupsafe import Markup, escape, soft_unicode | |
OLD | NEW |