Home | History | Annotate | Download | only in jinja2
      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
    521