:license: BSD, see LICENSE for more details.
"""
import re
-import string
+import sys
+import errno
+try:
+ from thread import allocate_lock
+except ImportError:
+ from dummy_thread import allocate_lock
from collections import deque
-from copy import deepcopy
-from functools import update_wrapper
from itertools import imap
)
)
_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'})()
+
+
+# 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
+
+
+# ironpython without stdlib doesn't have keyword
+try:
+ from keyword import iskeyword as is_python_keyword
+except ImportError:
+ _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
+ def is_python_keyword(name):
+ if _py_identifier_re.search(name) is None:
+ return False
+ try:
+ exec name + " = 42"
+ except SyntaxError:
+ return False
+ return True
+
+
+# common types. These do exist in the special types module too which however
+# does not exist in IronPython out of the box.
+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 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):
raise
+def open_if_exists(filename, mode='r'):
+ """Returns a file descriptor for the filename if that file exists,
+ otherwise `None`.
+ """
+ try:
+ return file(filename, mode)
+ except IOError, e:
+ if e.errno not in (errno.ENOENT, errno.EISDIR):
+ raise
+
+
def pformat(obj, verbose=False):
"""Prettyprint an object. Either use the `pretty` library or the
builtin `pprint`.
'@' not in middle and
not middle.startswith('http://') and
len(middle) > 0 and
- middle[0] in string.letters + string.digits and (
+ middle[0] in _letters + _digits and (
middle.endswith('.org') or
middle.endswith('.net') or
middle.endswith('.com')
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
+ 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.
+ 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`.
+ happen. If you want to use autoescaping in Jinja just enable the
+ autoescaping feature in the environment.
+
+ 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
return NotImplemented
def __mul__(self, num):
- if not isinstance(num, (int, long)):
- return NotImplemented
- return self.__class__(unicode.__mul__(self, num))
+ if isinstance(num, (int, long)):
+ return self.__class__(unicode.__mul__(self, num))
+ return NotImplemented
__rmul__ = __mul__
def __mod__(self, arg):
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>'
+ """
+ from jinja2.constants import HTML_ENTITIES
+ def handle_match(m):
+ name = m.group(1)
+ if name in HTML_ENTITIES:
+ return unichr(HTML_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 = 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)
+ args = _escape_argspec(list(args), enumerate(args))
+ _escape_argspec(kwargs, kwargs.iteritems())
return self.__class__(orig(self, *args, **kwargs))
- return update_wrapper(func, orig, ('__name__', '__doc__'))
+ func.__name__ = orig.__name__
+ func.__doc__ = orig.__doc__
+ return func
+
for method in '__getitem__', '__getslice__', 'capitalize', \
'title', 'lower', 'upper', 'replace', 'ljust', \
- 'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
- 'strip', 'translate', 'expandtabs', 'rpartition', \
- 'swapcase', 'zfill':
+ '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__"""
__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)))
+ __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 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.
+
+ # 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):
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)
def get(self, key, default=None):
"""Return an item from the cache dict or `default`"""
- if key in self:
+ try:
return self[key]
- return default
+ 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.
"""
- if key in self:
+ try:
return self[key]
- self[key] = default
- return default
+ except KeyError:
+ self[key] = default
+ return default
def clear(self):
"""Clear the cache."""
- self._mapping.clear()
- self._queue.clear()
+ 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."""
"""Sets the value for an item. Moves the item up so that it
has the highest priority then.
"""
- 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
+ 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.
"""
- del self._mapping[key]
- self._remove(key)
+ 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 __iter__(self):
- """Iterate over all values in the cache dict, ordered by
+ 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(self._queue)
+ return reversed(tuple(self._queue))
+
+ __iter__ = iterkeys
def __reversed__(self):
"""Iterate over the values in the cache dict, oldest items
coming first.
"""
- return iter(self._queue)
+ 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
+
+
# 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(obj):
- """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.
+ 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(obj, '__html__'):
- return obj.__html__()
- return Markup(unicode(obj)
+ if hasattr(s, '__html__'):
+ return s.__html__()
+ return Markup(unicode(s)
.replace('&', '&')
.replace('>', '>')
.replace('<', '<')
- .replace('"', '"')
+ .replace("'", ''')
+ .replace('"', '"')
)
def soft_unicode(s):
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)