• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
11import re
12import errno
13from collections import deque
14from 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
32missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
33
34# internal code
35internal_code = set()
36
37concat = u''.join
38
39
40def 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
56def 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
69def 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
79def internalcode(f):
80    """Marks the function as internally used"""
81    internal_code.add(f.__code__)
82    return f
83
84
85def 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
101def consume(iterable):
102    """Consumes an iterable without doing anything with it."""
103    for event in iterable:
104        pass
105
106
107def 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
119def 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
145def 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
156def 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
173def 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
185def 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
230def 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
279def 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
294class 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
472try:
473    from collections import MutableMapping
474    MutableMapping.register(LRUCache)
475except ImportError:
476    pass
477
478
479@implements_iterator
480class 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
505class 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
520from markupsafe import Markup, escape, soft_unicode
521