blob: 313a53e20c5f0e566ef5c0582c4b3bc1c41386b3 [file] [log] [blame]
# -*- 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, ('(', '<', '&lt;'))),
'|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
)
)
_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 callable as context callable. A
context callable is passed the active context as first argument if it
was directly stored in the context.
"""
f.contextfunction = True
return f
def environmentfunction(f):
"""This decorator can be used to mark a callable as environment callable.
A environment callable is passed the current environment as first argument
if it was directly stored in the context.
"""
f.environmentfunction = True
return f
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):
"""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.
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`.
"""
__slots__ = ()
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 not isinstance(num, (int, long)):
return NotImplemented
return self.__class__(unicode.__mul__(self, num))
__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):
"""Unescape markup."""
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):
"""Strip tags and resolve enities."""
stripped = u' '.join(_striptags_re.sub('', self).split())
return Markup(stripped).unescape()
@classmethod
def escape(cls, s):
"""Escape the string. Works like :func:`escape`."""
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 = list(args)
for idx, arg in enumerate(args):
if hasattr(arg, '__html__') or isinstance(arg, basestring):
args[idx] = escape(arg)
for name, arg in kwargs.iteritems():
if hasattr(arg, '__html__') or isinstance(arg, basestring):
kwargs[name] = escape(arg)
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'):
locals().update(
partition=make_wrapper('partition'),
rpartition=make_wrapper('rpartition')
)
del method, make_wrapper
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(repr(escape(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 around 200) but doesn't
# scale. But as long as it's only used for the database connections in
# a non request fallback it's fine.
def __init__(self, capacity):
self.capacity = capacity
self._mapping = {}
self._queue = deque()
# 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 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 __iter__(self):
"""Iterate over all values in the cache dict, ordered by
the most recent usage.
"""
return reversed(self._queue)
def __reversed__(self):
"""Iterate over the values in the cache dict, oldest items
coming first.
"""
return iter(self._queue)
__copy__ = copy
# 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('&', '&amp;')
.replace('>', '&gt;')
.replace('<', '&lt;')
.replace("'", '&#39;')
.replace('"', '&#34;')
)
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)