mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-23 23:29:58 +00:00
3ef20437d9
--HG-- branch : trunk
660 lines
21 KiB
Python
660 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
jinja2.utils
|
|
~~~~~~~~~~~~
|
|
|
|
Utility functions.
|
|
|
|
:copyright: 2008 by Armin Ronacher.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
import re
|
|
import sys
|
|
import string
|
|
try:
|
|
from thread import allocate_lock
|
|
except ImportError:
|
|
from dummy_thread import allocate_lock
|
|
from htmlentitydefs import name2codepoint
|
|
from collections import deque
|
|
from copy import deepcopy
|
|
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'&([^;]+);')
|
|
_entities = name2codepoint.copy()
|
|
_entities['apos'] = 39
|
|
|
|
# special singleton representing missing values for the runtime
|
|
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
|
|
|
|
|
|
# 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:
|
|
# 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
|
|
|
|
|
|
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 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 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 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 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(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 string.letters + string.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, random, 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 Markup(unicode):
|
|
r"""Marks a string as being safe for inclusion in HTML/XML output without
|
|
needing to be escaped. This implements the `__html__` interface a couple
|
|
of frameworks and web applications use. :class:`Markup` is a direct
|
|
subclass of `unicode` and provides all the methods of `unicode` just that
|
|
it escapes arguments passed and always returns `Markup`.
|
|
|
|
The `escape` function returns markup objects so that double escaping can't
|
|
happen. If you want to use autoescaping in Jinja just set the finalizer
|
|
of the environment to `escape`.
|
|
|
|
The constructor of the :class:`Markup` class can be used for three
|
|
different things: When passed an unicode object it's assumed to be safe,
|
|
when passed an object with an HTML representation (has an `__html__`
|
|
method) that representation is used, otherwise the object passed is
|
|
converted into a unicode string and then assumed to be safe:
|
|
|
|
>>> Markup("Hello <em>World</em>!")
|
|
Markup(u'Hello <em>World</em>!')
|
|
>>> class Foo(object):
|
|
... def __html__(self):
|
|
... return '<a href="#">foo</a>'
|
|
...
|
|
>>> Markup(Foo())
|
|
Markup(u'<a href="#">foo</a>')
|
|
|
|
If you want object passed being always treated as unsafe you can use the
|
|
:meth:`escape` classmethod to create a :class:`Markup` object:
|
|
|
|
>>> Markup.escape("Hello <em>World</em>!")
|
|
Markup(u'Hello <em>World</em>!')
|
|
|
|
Operations on a markup string are markup aware which means that all
|
|
arguments are passed through the :func:`escape` function:
|
|
|
|
>>> em = Markup("<em>%s</em>")
|
|
>>> em % "foo & bar"
|
|
Markup(u'<em>foo & bar</em>')
|
|
>>> strong = Markup("<strong>%(text)s</strong>")
|
|
>>> strong % {'text': '<blink>hacker here</blink>'}
|
|
Markup(u'<strong><blink>hacker here</blink></strong>')
|
|
>>> Markup("<em>Hello</em> ") + "<foo>"
|
|
Markup(u'<em>Hello</em> <foo>')
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def __new__(cls, base=u'', encoding=None, errors='strict'):
|
|
if hasattr(base, '__html__'):
|
|
base = base.__html__()
|
|
if encoding is None:
|
|
return unicode.__new__(cls, base)
|
|
return unicode.__new__(cls, base, encoding, errors)
|
|
|
|
def __html__(self):
|
|
return self
|
|
|
|
def __add__(self, other):
|
|
if hasattr(other, '__html__') or isinstance(other, basestring):
|
|
return self.__class__(unicode(self) + unicode(escape(other)))
|
|
return NotImplemented
|
|
|
|
def __radd__(self, other):
|
|
if hasattr(other, '__html__') or isinstance(other, basestring):
|
|
return self.__class__(unicode(escape(other)) + unicode(self))
|
|
return NotImplemented
|
|
|
|
def __mul__(self, num):
|
|
if isinstance(num, (int, long)):
|
|
return self.__class__(unicode.__mul__(self, num))
|
|
return NotImplemented
|
|
__rmul__ = __mul__
|
|
|
|
def __mod__(self, arg):
|
|
if isinstance(arg, tuple):
|
|
arg = tuple(imap(_MarkupEscapeHelper, arg))
|
|
else:
|
|
arg = _MarkupEscapeHelper(arg)
|
|
return self.__class__(unicode.__mod__(self, arg))
|
|
|
|
def __repr__(self):
|
|
return '%s(%s)' % (
|
|
self.__class__.__name__,
|
|
unicode.__repr__(self)
|
|
)
|
|
|
|
def join(self, seq):
|
|
return self.__class__(unicode.join(self, imap(escape, seq)))
|
|
join.__doc__ = unicode.join.__doc__
|
|
|
|
def split(self, *args, **kwargs):
|
|
return map(self.__class__, unicode.split(self, *args, **kwargs))
|
|
split.__doc__ = unicode.split.__doc__
|
|
|
|
def rsplit(self, *args, **kwargs):
|
|
return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
|
|
rsplit.__doc__ = unicode.rsplit.__doc__
|
|
|
|
def splitlines(self, *args, **kwargs):
|
|
return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
|
|
splitlines.__doc__ = unicode.splitlines.__doc__
|
|
|
|
def unescape(self):
|
|
r"""Unescape markup again into an unicode string. This also resolves
|
|
known HTML4 and XHTML entities:
|
|
|
|
>>> Markup("Main » <em>About</em>").unescape()
|
|
u'Main \xbb <em>About</em>'
|
|
"""
|
|
def handle_match(m):
|
|
name = m.group(1)
|
|
if name in _entities:
|
|
return unichr(_entities[name])
|
|
try:
|
|
if name[:2] in ('#x', '#X'):
|
|
return unichr(int(name[2:], 16))
|
|
elif name.startswith('#'):
|
|
return unichr(int(name[1:]))
|
|
except ValueError:
|
|
pass
|
|
return u''
|
|
return _entity_re.sub(handle_match, unicode(self))
|
|
|
|
def striptags(self):
|
|
r"""Unescape markup into an unicode string and strip all tags. This
|
|
also resolves known HTML4 and XHTML entities. Whitespace is
|
|
normalized to one:
|
|
|
|
>>> Markup("Main » <em>About</em>").striptags()
|
|
u'Main \xbb About'
|
|
"""
|
|
stripped = u' '.join(_striptags_re.sub('', self).split())
|
|
return Markup(stripped).unescape()
|
|
|
|
@classmethod
|
|
def escape(cls, s):
|
|
"""Escape the string. Works like :func:`escape` with the difference
|
|
that for subclasses of :class:`Markup` this function would return the
|
|
correct subclass.
|
|
"""
|
|
rv = escape(s)
|
|
if rv.__class__ is not cls:
|
|
return cls(rv)
|
|
return rv
|
|
|
|
def make_wrapper(name):
|
|
orig = getattr(unicode, name)
|
|
def func(self, *args, **kwargs):
|
|
args = _escape_argspec(list(args), enumerate(args))
|
|
_escape_argspec(kwargs, kwargs.iteritems())
|
|
return self.__class__(orig(self, *args, **kwargs))
|
|
func.__name__ = orig.__name__
|
|
func.__doc__ = orig.__doc__
|
|
return func
|
|
|
|
for method in '__getitem__', '__getslice__', 'capitalize', \
|
|
'title', 'lower', 'upper', 'replace', 'ljust', \
|
|
'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
|
|
'translate', 'expandtabs', 'swapcase', 'zfill':
|
|
locals()[method] = make_wrapper(method)
|
|
|
|
# new in python 2.5
|
|
if hasattr(unicode, 'partition'):
|
|
partition = make_wrapper('partition'),
|
|
rpartition = make_wrapper('rpartition')
|
|
|
|
# new in python 2.6
|
|
if hasattr(unicode, 'format'):
|
|
format = make_wrapper('format')
|
|
|
|
del method, make_wrapper
|
|
|
|
|
|
def _escape_argspec(obj, iterable):
|
|
"""Helper for various string-wrapped functions."""
|
|
for key, value in iterable:
|
|
if hasattr(value, '__html__') or isinstance(value, basestring):
|
|
obj[key] = escape(value)
|
|
return obj
|
|
|
|
|
|
class _MarkupEscapeHelper(object):
|
|
"""Helper for Markup.__mod__"""
|
|
|
|
def __init__(self, obj):
|
|
self.obj = obj
|
|
|
|
__getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
|
|
__unicode__ = lambda s: unicode(escape(s.obj))
|
|
__str__ = lambda s: str(escape(s.obj))
|
|
__repr__ = lambda s: str(escape(repr(s.obj)))
|
|
__int__ = lambda s: int(s.obj)
|
|
__float__ = lambda s: float(s.obj)
|
|
|
|
|
|
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:
|
|
self._remove(key)
|
|
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:
|
|
self._remove(key)
|
|
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]
|
|
self._remove(key)
|
|
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
|
|
|
|
|
|
# we have to import it down here as the speedups module imports the
|
|
# markup type which is define above.
|
|
try:
|
|
from jinja2._speedups import escape, soft_unicode
|
|
except ImportError:
|
|
def escape(s):
|
|
"""Convert the characters &, <, >, ' and " in string s to HTML-safe
|
|
sequences. Use this if you need to display text that might contain
|
|
such characters in HTML. Marks return value as markup string.
|
|
"""
|
|
if hasattr(s, '__html__'):
|
|
return s.__html__()
|
|
return Markup(unicode(s)
|
|
.replace('&', '&')
|
|
.replace('>', '>')
|
|
.replace('<', '<')
|
|
.replace("'", ''')
|
|
.replace('"', '"')
|
|
)
|
|
|
|
def soft_unicode(s):
|
|
"""Make a string unicode if it isn't already. That way a markup
|
|
string is not converted back to unicode.
|
|
"""
|
|
if not isinstance(s, unicode):
|
|
s = unicode(s)
|
|
return s
|
|
|
|
|
|
# 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)
|