| Index: third_party/jinja2/utils.py
|
| diff --git a/third_party/jinja2/utils.py b/third_party/jinja2/utils.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..49e9e9ae0737dd5876b1ca67d8f00185dcad0370
|
| --- /dev/null
|
| +++ b/third_party/jinja2/utils.py
|
| @@ -0,0 +1,601 @@
|
| +# -*- coding: utf-8 -*-
|
| +"""
|
| + jinja2.utils
|
| + ~~~~~~~~~~~~
|
| +
|
| + Utility functions.
|
| +
|
| + :copyright: (c) 2010 by the Jinja Team.
|
| + :license: BSD, see LICENSE for more details.
|
| +"""
|
| +import re
|
| +import sys
|
| +import errno
|
| +try:
|
| + from thread import allocate_lock
|
| +except ImportError:
|
| + from dummy_thread import allocate_lock
|
| +from collections import deque
|
| +from itertools import imap
|
| +
|
| +
|
| +_word_split_re = re.compile(r'(\s+)')
|
| +_punctuation_re = re.compile(
|
| + '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
|
| + '|'.join(imap(re.escape, ('(', '<', '<'))),
|
| + '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>')))
|
| + )
|
| +)
|
| +_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
| +_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
| +_entity_re = re.compile(r'&([^;]+);')
|
| +_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
| +_digits = '0123456789'
|
| +
|
| +# special singleton representing missing values for the runtime
|
| +missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
|
| +
|
| +# internal code
|
| +internal_code = set()
|
| +
|
| +
|
| +# concatenate a list of strings and convert them to unicode.
|
| +# unfortunately there is a bug in python 2.4 and lower that causes
|
| +# unicode.join trash the traceback.
|
| +_concat = u''.join
|
| +try:
|
| + def _test_gen_bug():
|
| + raise TypeError(_test_gen_bug)
|
| + yield None
|
| + _concat(_test_gen_bug())
|
| +except TypeError, _error:
|
| + if not _error.args or _error.args[0] is not _test_gen_bug:
|
| + def concat(gen):
|
| + try:
|
| + return _concat(list(gen))
|
| + except Exception:
|
| + # this hack is needed so that the current frame
|
| + # does not show up in the traceback.
|
| + exc_type, exc_value, tb = sys.exc_info()
|
| + raise exc_type, exc_value, tb.tb_next
|
| + else:
|
| + concat = _concat
|
| + del _test_gen_bug, _error
|
| +
|
| +
|
| +# for python 2.x we create outselves a next() function that does the
|
| +# basics without exception catching.
|
| +try:
|
| + next = next
|
| +except NameError:
|
| + def next(x):
|
| + return x.next()
|
| +
|
| +
|
| +# if this python version is unable to deal with unicode filenames
|
| +# when passed to encode we let this function encode it properly.
|
| +# This is used in a couple of places. As far as Jinja is concerned
|
| +# filenames are unicode *or* bytestrings in 2.x and unicode only in
|
| +# 3.x because compile cannot handle bytes
|
| +if sys.version_info < (3, 0):
|
| + def _encode_filename(filename):
|
| + if isinstance(filename, unicode):
|
| + return filename.encode('utf-8')
|
| + return filename
|
| +else:
|
| + def _encode_filename(filename):
|
| + assert filename is None or isinstance(filename, str), \
|
| + 'filenames must be strings'
|
| + return filename
|
| +
|
| +from keyword import iskeyword as is_python_keyword
|
| +
|
| +
|
| +# common types. These do exist in the special types module too which however
|
| +# does not exist in IronPython out of the box. Also that way we don't have
|
| +# to deal with implementation specific stuff here
|
| +class _C(object):
|
| + def method(self): pass
|
| +def _func():
|
| + yield None
|
| +FunctionType = type(_func)
|
| +GeneratorType = type(_func())
|
| +MethodType = type(_C.method)
|
| +CodeType = type(_C.method.func_code)
|
| +try:
|
| + raise TypeError()
|
| +except TypeError:
|
| + _tb = sys.exc_info()[2]
|
| + TracebackType = type(_tb)
|
| + FrameType = type(_tb.tb_frame)
|
| +del _C, _tb, _func
|
| +
|
| +
|
| +def contextfunction(f):
|
| + """This decorator can be used to mark a function or method context callable.
|
| + A context callable is passed the active :class:`Context` as first argument when
|
| + called from the template. This is useful if a function wants to get access
|
| + to the context or functions provided on the context object. For example
|
| + a function that returns a sorted list of template variables the current
|
| + template exports could look like this::
|
| +
|
| + @contextfunction
|
| + def get_exported_names(context):
|
| + return sorted(context.exported_vars)
|
| + """
|
| + f.contextfunction = True
|
| + return f
|
| +
|
| +
|
| +def evalcontextfunction(f):
|
| + """This decoraotr can be used to mark a function or method as an eval
|
| + context callable. This is similar to the :func:`contextfunction`
|
| + but instead of passing the context, an evaluation context object is
|
| + passed. For more information about the eval context, see
|
| + :ref:`eval-context`.
|
| +
|
| + .. versionadded:: 2.4
|
| + """
|
| + f.evalcontextfunction = True
|
| + return f
|
| +
|
| +
|
| +def environmentfunction(f):
|
| + """This decorator can be used to mark a function or method as environment
|
| + callable. This decorator works exactly like the :func:`contextfunction`
|
| + decorator just that the first argument is the active :class:`Environment`
|
| + and not context.
|
| + """
|
| + f.environmentfunction = True
|
| + return f
|
| +
|
| +
|
| +def internalcode(f):
|
| + """Marks the function as internally used"""
|
| + internal_code.add(f.func_code)
|
| + return f
|
| +
|
| +
|
| +def is_undefined(obj):
|
| + """Check if the object passed is undefined. This does nothing more than
|
| + performing an instance check against :class:`Undefined` but looks nicer.
|
| + This can be used for custom filters or tests that want to react to
|
| + undefined variables. For example a custom default filter can look like
|
| + this::
|
| +
|
| + def default(var, default=''):
|
| + if is_undefined(var):
|
| + return default
|
| + return var
|
| + """
|
| + from jinja2.runtime import Undefined
|
| + return isinstance(obj, Undefined)
|
| +
|
| +
|
| +def consume(iterable):
|
| + """Consumes an iterable without doing anything with it."""
|
| + for event in iterable:
|
| + pass
|
| +
|
| +
|
| +def clear_caches():
|
| + """Jinja2 keeps internal caches for environments and lexers. These are
|
| + used so that Jinja2 doesn't have to recreate environments and lexers all
|
| + the time. Normally you don't have to care about that but if you are
|
| + messuring memory consumption you may want to clean the caches.
|
| + """
|
| + from jinja2.environment import _spontaneous_environments
|
| + from jinja2.lexer import _lexer_cache
|
| + _spontaneous_environments.clear()
|
| + _lexer_cache.clear()
|
| +
|
| +
|
| +def import_string(import_name, silent=False):
|
| + """Imports an object based on a string. This use useful if you want to
|
| + use import paths as endpoints or something similar. An import path can
|
| + be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
| + or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
| +
|
| + If the `silent` is True the return value will be `None` if the import
|
| + fails.
|
| +
|
| + :return: imported object
|
| + """
|
| + try:
|
| + if ':' in import_name:
|
| + module, obj = import_name.split(':', 1)
|
| + elif '.' in import_name:
|
| + items = import_name.split('.')
|
| + module = '.'.join(items[:-1])
|
| + obj = items[-1]
|
| + else:
|
| + return __import__(import_name)
|
| + return getattr(__import__(module, None, None, [obj]), obj)
|
| + except (ImportError, AttributeError):
|
| + if not silent:
|
| + raise
|
| +
|
| +
|
| +def open_if_exists(filename, mode='rb'):
|
| + """Returns a file descriptor for the filename if that file exists,
|
| + otherwise `None`.
|
| + """
|
| + try:
|
| + return open(filename, mode)
|
| + except IOError, e:
|
| + if e.errno not in (errno.ENOENT, errno.EISDIR):
|
| + raise
|
| +
|
| +
|
| +def object_type_repr(obj):
|
| + """Returns the name of the object's type. For some recognized
|
| + singletons the name of the object is returned instead. (For
|
| + example for `None` and `Ellipsis`).
|
| + """
|
| + if obj is None:
|
| + return 'None'
|
| + elif obj is Ellipsis:
|
| + return 'Ellipsis'
|
| + # __builtin__ in 2.x, builtins in 3.x
|
| + if obj.__class__.__module__ in ('__builtin__', 'builtins'):
|
| + name = obj.__class__.__name__
|
| + else:
|
| + name = obj.__class__.__module__ + '.' + obj.__class__.__name__
|
| + return '%s object' % name
|
| +
|
| +
|
| +def pformat(obj, verbose=False):
|
| + """Prettyprint an object. Either use the `pretty` library or the
|
| + builtin `pprint`.
|
| + """
|
| + try:
|
| + from pretty import pretty
|
| + return pretty(obj, verbose=verbose)
|
| + except ImportError:
|
| + from pprint import pformat
|
| + return pformat(obj)
|
| +
|
| +
|
| +def urlize(text, trim_url_limit=None, nofollow=False):
|
| + """Converts any URLs in text into clickable links. Works on http://,
|
| + https:// and www. links. Links can have trailing punctuation (periods,
|
| + commas, close-parens) and leading punctuation (opening parens) and
|
| + it'll still do the right thing.
|
| +
|
| + If trim_url_limit is not None, the URLs in link text will be limited
|
| + to trim_url_limit characters.
|
| +
|
| + If nofollow is True, the URLs in link text will get a rel="nofollow"
|
| + attribute.
|
| + """
|
| + trim_url = lambda x, limit=trim_url_limit: limit is not None \
|
| + and (x[:limit] + (len(x) >=limit and '...'
|
| + or '')) or x
|
| + words = _word_split_re.split(unicode(escape(text)))
|
| + nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
| + for i, word in enumerate(words):
|
| + match = _punctuation_re.match(word)
|
| + if match:
|
| + lead, middle, trail = match.groups()
|
| + if middle.startswith('www.') or (
|
| + '@' not in middle and
|
| + not middle.startswith('http://') and
|
| + len(middle) > 0 and
|
| + middle[0] in _letters + _digits and (
|
| + middle.endswith('.org') or
|
| + middle.endswith('.net') or
|
| + middle.endswith('.com')
|
| + )):
|
| + middle = '<a href="http://%s"%s>%s</a>' % (middle,
|
| + nofollow_attr, trim_url(middle))
|
| + if middle.startswith('http://') or \
|
| + middle.startswith('https://'):
|
| + middle = '<a href="%s"%s>%s</a>' % (middle,
|
| + nofollow_attr, trim_url(middle))
|
| + if '@' in middle and not middle.startswith('www.') and \
|
| + not ':' in middle and _simple_email_re.match(middle):
|
| + middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
| + if lead + middle + trail != word:
|
| + words[i] = lead + middle + trail
|
| + return u''.join(words)
|
| +
|
| +
|
| +def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
| + """Generate some lorem impsum for the template."""
|
| + from jinja2.constants import LOREM_IPSUM_WORDS
|
| + from random import choice, randrange
|
| + words = LOREM_IPSUM_WORDS.split()
|
| + result = []
|
| +
|
| + for _ in xrange(n):
|
| + next_capitalized = True
|
| + last_comma = last_fullstop = 0
|
| + word = None
|
| + last = None
|
| + p = []
|
| +
|
| + # each paragraph contains out of 20 to 100 words.
|
| + for idx, _ in enumerate(xrange(randrange(min, max))):
|
| + while True:
|
| + word = choice(words)
|
| + if word != last:
|
| + last = word
|
| + break
|
| + if next_capitalized:
|
| + word = word.capitalize()
|
| + next_capitalized = False
|
| + # add commas
|
| + if idx - randrange(3, 8) > last_comma:
|
| + last_comma = idx
|
| + last_fullstop += 2
|
| + word += ','
|
| + # add end of sentences
|
| + if idx - randrange(10, 20) > last_fullstop:
|
| + last_comma = last_fullstop = idx
|
| + word += '.'
|
| + next_capitalized = True
|
| + p.append(word)
|
| +
|
| + # ensure that the paragraph ends with a dot.
|
| + p = u' '.join(p)
|
| + if p.endswith(','):
|
| + p = p[:-1] + '.'
|
| + elif not p.endswith('.'):
|
| + p += '.'
|
| + result.append(p)
|
| +
|
| + if not html:
|
| + return u'\n\n'.join(result)
|
| + return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
|
| +
|
| +
|
| +class LRUCache(object):
|
| + """A simple LRU Cache implementation."""
|
| +
|
| + # this is fast for small capacities (something below 1000) but doesn't
|
| + # scale. But as long as it's only used as storage for templates this
|
| + # won't do any harm.
|
| +
|
| + def __init__(self, capacity):
|
| + self.capacity = capacity
|
| + self._mapping = {}
|
| + self._queue = deque()
|
| + self._postinit()
|
| +
|
| + def _postinit(self):
|
| + # alias all queue methods for faster lookup
|
| + self._popleft = self._queue.popleft
|
| + self._pop = self._queue.pop
|
| + if hasattr(self._queue, 'remove'):
|
| + self._remove = self._queue.remove
|
| + self._wlock = allocate_lock()
|
| + self._append = self._queue.append
|
| +
|
| + def _remove(self, obj):
|
| + """Python 2.4 compatibility."""
|
| + for idx, item in enumerate(self._queue):
|
| + if item == obj:
|
| + del self._queue[idx]
|
| + break
|
| +
|
| + def __getstate__(self):
|
| + return {
|
| + 'capacity': self.capacity,
|
| + '_mapping': self._mapping,
|
| + '_queue': self._queue
|
| + }
|
| +
|
| + def __setstate__(self, d):
|
| + self.__dict__.update(d)
|
| + self._postinit()
|
| +
|
| + def __getnewargs__(self):
|
| + return (self.capacity,)
|
| +
|
| + def copy(self):
|
| + """Return an shallow copy of the instance."""
|
| + rv = self.__class__(self.capacity)
|
| + rv._mapping.update(self._mapping)
|
| + rv._queue = deque(self._queue)
|
| + return rv
|
| +
|
| + def get(self, key, default=None):
|
| + """Return an item from the cache dict or `default`"""
|
| + try:
|
| + return self[key]
|
| + except KeyError:
|
| + return default
|
| +
|
| + def setdefault(self, key, default=None):
|
| + """Set `default` if the key is not in the cache otherwise
|
| + leave unchanged. Return the value of this key.
|
| + """
|
| + try:
|
| + return self[key]
|
| + except KeyError:
|
| + self[key] = default
|
| + return default
|
| +
|
| + def clear(self):
|
| + """Clear the cache."""
|
| + self._wlock.acquire()
|
| + try:
|
| + self._mapping.clear()
|
| + self._queue.clear()
|
| + finally:
|
| + self._wlock.release()
|
| +
|
| + def __contains__(self, key):
|
| + """Check if a key exists in this cache."""
|
| + return key in self._mapping
|
| +
|
| + def __len__(self):
|
| + """Return the current size of the cache."""
|
| + return len(self._mapping)
|
| +
|
| + def __repr__(self):
|
| + return '<%s %r>' % (
|
| + self.__class__.__name__,
|
| + self._mapping
|
| + )
|
| +
|
| + def __getitem__(self, key):
|
| + """Get an item from the cache. Moves the item up so that it has the
|
| + highest priority then.
|
| +
|
| + Raise an `KeyError` if it does not exist.
|
| + """
|
| + rv = self._mapping[key]
|
| + if self._queue[-1] != key:
|
| + try:
|
| + self._remove(key)
|
| + except ValueError:
|
| + # if something removed the key from the container
|
| + # when we read, ignore the ValueError that we would
|
| + # get otherwise.
|
| + pass
|
| + self._append(key)
|
| + return rv
|
| +
|
| + def __setitem__(self, key, value):
|
| + """Sets the value for an item. Moves the item up so that it
|
| + has the highest priority then.
|
| + """
|
| + self._wlock.acquire()
|
| + try:
|
| + if key in self._mapping:
|
| + try:
|
| + self._remove(key)
|
| + except ValueError:
|
| + # __getitem__ is not locked, it might happen
|
| + pass
|
| + elif len(self._mapping) == self.capacity:
|
| + del self._mapping[self._popleft()]
|
| + self._append(key)
|
| + self._mapping[key] = value
|
| + finally:
|
| + self._wlock.release()
|
| +
|
| + def __delitem__(self, key):
|
| + """Remove an item from the cache dict.
|
| + Raise an `KeyError` if it does not exist.
|
| + """
|
| + self._wlock.acquire()
|
| + try:
|
| + del self._mapping[key]
|
| + try:
|
| + self._remove(key)
|
| + except ValueError:
|
| + # __getitem__ is not locked, it might happen
|
| + pass
|
| + finally:
|
| + self._wlock.release()
|
| +
|
| + def items(self):
|
| + """Return a list of items."""
|
| + result = [(key, self._mapping[key]) for key in list(self._queue)]
|
| + result.reverse()
|
| + return result
|
| +
|
| + def iteritems(self):
|
| + """Iterate over all items."""
|
| + return iter(self.items())
|
| +
|
| + def values(self):
|
| + """Return a list of all values."""
|
| + return [x[1] for x in self.items()]
|
| +
|
| + def itervalue(self):
|
| + """Iterate over all values."""
|
| + return iter(self.values())
|
| +
|
| + def keys(self):
|
| + """Return a list of all keys ordered by most recent usage."""
|
| + return list(self)
|
| +
|
| + def iterkeys(self):
|
| + """Iterate over all keys in the cache dict, ordered by
|
| + the most recent usage.
|
| + """
|
| + return reversed(tuple(self._queue))
|
| +
|
| + __iter__ = iterkeys
|
| +
|
| + def __reversed__(self):
|
| + """Iterate over the values in the cache dict, oldest items
|
| + coming first.
|
| + """
|
| + return iter(tuple(self._queue))
|
| +
|
| + __copy__ = copy
|
| +
|
| +
|
| +# register the LRU cache as mutable mapping if possible
|
| +try:
|
| + from collections import MutableMapping
|
| + MutableMapping.register(LRUCache)
|
| +except ImportError:
|
| + pass
|
| +
|
| +
|
| +class Cycler(object):
|
| + """A cycle helper for templates."""
|
| +
|
| + def __init__(self, *items):
|
| + if not items:
|
| + raise RuntimeError('at least one item has to be provided')
|
| + self.items = items
|
| + self.reset()
|
| +
|
| + def reset(self):
|
| + """Resets the cycle."""
|
| + self.pos = 0
|
| +
|
| + @property
|
| + def current(self):
|
| + """Returns the current item."""
|
| + return self.items[self.pos]
|
| +
|
| + def next(self):
|
| + """Goes one item ahead and returns it."""
|
| + rv = self.current
|
| + self.pos = (self.pos + 1) % len(self.items)
|
| + return rv
|
| +
|
| +
|
| +class Joiner(object):
|
| + """A joining helper for templates."""
|
| +
|
| + def __init__(self, sep=u', '):
|
| + self.sep = sep
|
| + self.used = False
|
| +
|
| + def __call__(self):
|
| + if not self.used:
|
| + self.used = True
|
| + return u''
|
| + return self.sep
|
| +
|
| +
|
| +# try markupsafe first, if that fails go with Jinja2's bundled version
|
| +# of markupsafe. Markupsafe was previously Jinja2's implementation of
|
| +# the Markup object but was moved into a separate package in a patchleve
|
| +# release
|
| +try:
|
| + from markupsafe import Markup, escape, soft_unicode
|
| +except ImportError:
|
| + from jinja2._markupsafe import Markup, escape, soft_unicode
|
| +
|
| +
|
| +# partials
|
| +try:
|
| + from functools import partial
|
| +except ImportError:
|
| + class partial(object):
|
| + def __init__(self, _func, *args, **kwargs):
|
| + self._func = _func
|
| + self._args = args
|
| + self._kwargs = kwargs
|
| + def __call__(self, *args, **kwargs):
|
| + kwargs.update(self._kwargs)
|
| + return self._func(*(self._args + args), **kwargs)
|
|
|