- add sources.
[platform/framework/web/crosswalk.git] / src / third_party / jinja2 / utils.py
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, ('(', '<', '&lt;'))),
22         '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
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 when
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