blob: be8cd6d1817281dabf079b3efaaa0239626cea2c [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.utils
4 ~~~~~~~~~~~~
5
6 Utility functions.
7
Armin Ronacher62ccd1b2009-01-04 14:26:19 +01008 :copyright: (c) 2009 by the Jinja Team.
Armin Ronacher07bc6842008-03-31 14:18:49 +02009 :license: BSD, see LICENSE for more details.
10"""
Christoph Hack80909862008-04-14 01:35:10 +020011import re
Benjamin Wiegand96828552008-05-03 22:27:29 +020012import sys
Armin Ronacherccae0552008-10-05 23:08:58 +020013import errno
Armin Ronacher000b4912008-05-01 18:40:15 +020014try:
15 from thread import allocate_lock
16except ImportError:
17 from dummy_thread import allocate_lock
Armin Ronacher814f6c22008-04-17 15:52:23 +020018from collections import deque
Armin Ronacher18c6ca02008-04-17 10:03:29 +020019from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020020
21
Armin Ronacherbe4ae242008-04-18 09:49:08 +020022_word_split_re = re.compile(r'(\s+)')
23_punctuation_re = re.compile(
24 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
25 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
26 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
27 )
28)
29_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
Armin Ronacher76c280b2008-05-04 12:31:48 +020030_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
31_entity_re = re.compile(r'&([^;]+);')
Armin Ronacher9a0078d2008-08-13 18:24:17 +020032_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
33_digits = '0123456789'
Armin Ronacherbe4ae242008-04-18 09:49:08 +020034
Armin Ronacher7259c762008-04-30 13:03:59 +020035# special singleton representing missing values for the runtime
36missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
37
Armin Ronacherd416a972009-02-24 22:58:00 +010038# internal code
39internal_code = set()
40
Armin Ronacher7259c762008-04-30 13:03:59 +020041
Armin Ronacher7ceced52008-05-03 10:15:31 +020042# concatenate a list of strings and convert them to unicode.
43# unfortunately there is a bug in python 2.4 and lower that causes
44# unicode.join trash the traceback.
Armin Ronachercda43df2008-05-03 17:10:05 +020045_concat = u''.join
Armin Ronacher7ceced52008-05-03 10:15:31 +020046try:
47 def _test_gen_bug():
48 raise TypeError(_test_gen_bug)
49 yield None
Armin Ronachercda43df2008-05-03 17:10:05 +020050 _concat(_test_gen_bug())
Armin Ronacher7ceced52008-05-03 10:15:31 +020051except TypeError, _error:
Armin Ronachercda43df2008-05-03 17:10:05 +020052 if not _error.args or _error.args[0] is not _test_gen_bug:
Armin Ronacher7ceced52008-05-03 10:15:31 +020053 def concat(gen):
54 try:
Armin Ronachercda43df2008-05-03 17:10:05 +020055 return _concat(list(gen))
Armin Ronacher7ceced52008-05-03 10:15:31 +020056 except:
57 # this hack is needed so that the current frame
58 # does not show up in the traceback.
59 exc_type, exc_value, tb = sys.exc_info()
60 raise exc_type, exc_value, tb.tb_next
Armin Ronachercda43df2008-05-03 17:10:05 +020061 else:
62 concat = _concat
Armin Ronacher7ceced52008-05-03 10:15:31 +020063 del _test_gen_bug, _error
64
65
Armin Ronacherbd357722009-08-05 20:25:06 +020066# for python 2.x we create outselves a next() function that does the
67# basics without exception catching.
68try:
69 next = next
70except NameError:
71 def next(x):
72 return x.next()
73
74
Armin Ronacher9a0078d2008-08-13 18:24:17 +020075# ironpython without stdlib doesn't have keyword
76try:
77 from keyword import iskeyword as is_python_keyword
78except ImportError:
79 _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
80 def is_python_keyword(name):
81 if _py_identifier_re.search(name) is None:
82 return False
83 try:
84 exec name + " = 42"
85 except SyntaxError:
86 return False
87 return True
88
89
90# common types. These do exist in the special types module too which however
91# does not exist in IronPython out of the box.
92class _C(object):
93 def method(self): pass
94def _func():
95 yield None
96FunctionType = type(_func)
97GeneratorType = type(_func())
98MethodType = type(_C.method)
99CodeType = type(_C.method.func_code)
100try:
101 raise TypeError()
102except TypeError:
103 _tb = sys.exc_info()[2]
104 TracebackType = type(_tb)
105 FrameType = type(_tb.tb_frame)
106del _C, _tb, _func
107
108
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200109def contextfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200110 """This decorator can be used to mark a function or method context callable.
111 A context callable is passed the active :class:`Context` as first argument when
112 called from the template. This is useful if a function wants to get access
113 to the context or functions provided on the context object. For example
114 a function that returns a sorted list of template variables the current
115 template exports could look like this::
116
Armin Ronacher58f351d2008-05-28 21:30:14 +0200117 @contextfunction
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200118 def get_exported_names(context):
119 return sorted(context.exported_vars)
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200120 """
121 f.contextfunction = True
122 return f
123
124
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200125def environmentfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200126 """This decorator can be used to mark a function or method as environment
127 callable. This decorator works exactly like the :func:`contextfunction`
128 decorator just that the first argument is the active :class:`Environment`
129 and not context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200130 """
131 f.environmentfunction = True
132 return f
133
134
Armin Ronacherd416a972009-02-24 22:58:00 +0100135def internalcode(f):
136 """Marks the function as internally used"""
137 internal_code.add(f.func_code)
138 return f
139
140
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200141def is_undefined(obj):
142 """Check if the object passed is undefined. This does nothing more than
143 performing an instance check against :class:`Undefined` but looks nicer.
144 This can be used for custom filters or tests that want to react to
145 undefined variables. For example a custom default filter can look like
146 this::
147
148 def default(var, default=''):
149 if is_undefined(var):
150 return default
151 return var
152 """
153 from jinja2.runtime import Undefined
154 return isinstance(obj, Undefined)
155
156
Armin Ronacherba6e25a2008-11-02 15:58:14 +0100157def consume(iterable):
158 """Consumes an iterable without doing anything with it."""
159 for event in iterable:
160 pass
161
162
Armin Ronacher187bde12008-05-01 18:19:16 +0200163def clear_caches():
164 """Jinja2 keeps internal caches for environments and lexers. These are
165 used so that Jinja2 doesn't have to recreate environments and lexers all
166 the time. Normally you don't have to care about that but if you are
167 messuring memory consumption you may want to clean the caches.
168 """
169 from jinja2.environment import _spontaneous_environments
170 from jinja2.lexer import _lexer_cache
171 _spontaneous_environments.clear()
172 _lexer_cache.clear()
173
174
Armin Ronacherf59bac22008-04-20 13:11:43 +0200175def import_string(import_name, silent=False):
176 """Imports an object based on a string. This use useful if you want to
177 use import paths as endpoints or something similar. An import path can
178 be specified either in dotted notation (``xml.sax.saxutils.escape``)
179 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
180
181 If the `silent` is True the return value will be `None` if the import
182 fails.
183
184 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200185 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200186 try:
187 if ':' in import_name:
188 module, obj = import_name.split(':', 1)
189 elif '.' in import_name:
190 items = import_name.split('.')
191 module = '.'.join(items[:-1])
192 obj = items[-1]
193 else:
194 return __import__(import_name)
195 return getattr(__import__(module, None, None, [obj]), obj)
196 except (ImportError, AttributeError):
197 if not silent:
198 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200199
200
Armin Ronacherccae0552008-10-05 23:08:58 +0200201def open_if_exists(filename, mode='r'):
202 """Returns a file descriptor for the filename if that file exists,
203 otherwise `None`.
204 """
205 try:
206 return file(filename, mode)
207 except IOError, e:
208 if e.errno not in (errno.ENOENT, errno.EISDIR):
209 raise
210
211
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200212def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200213 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200214 builtin `pprint`.
215 """
216 try:
217 from pretty import pretty
218 return pretty(obj, verbose=verbose)
219 except ImportError:
220 from pprint import pformat
221 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200222
223
Christoph Hack80909862008-04-14 01:35:10 +0200224def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200225 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200226 https:// and www. links. Links can have trailing punctuation (periods,
227 commas, close-parens) and leading punctuation (opening parens) and
228 it'll still do the right thing.
229
230 If trim_url_limit is not None, the URLs in link text will be limited
231 to trim_url_limit characters.
232
233 If nofollow is True, the URLs in link text will get a rel="nofollow"
234 attribute.
235 """
236 trim_url = lambda x, limit=trim_url_limit: limit is not None \
237 and (x[:limit] + (len(x) >=limit and '...'
238 or '')) or x
Armin Ronacherd9342dc2008-11-17 00:35:30 +0100239 words = _word_split_re.split(unicode(escape(text)))
Christoph Hack80909862008-04-14 01:35:10 +0200240 nofollow_attr = nofollow and ' rel="nofollow"' or ''
241 for i, word in enumerate(words):
242 match = _punctuation_re.match(word)
243 if match:
244 lead, middle, trail = match.groups()
245 if middle.startswith('www.') or (
246 '@' not in middle and
247 not middle.startswith('http://') and
248 len(middle) > 0 and
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200249 middle[0] in _letters + _digits and (
Christoph Hack80909862008-04-14 01:35:10 +0200250 middle.endswith('.org') or
251 middle.endswith('.net') or
252 middle.endswith('.com')
253 )):
254 middle = '<a href="http://%s"%s>%s</a>' % (middle,
255 nofollow_attr, trim_url(middle))
256 if middle.startswith('http://') or \
257 middle.startswith('https://'):
258 middle = '<a href="%s"%s>%s</a>' % (middle,
259 nofollow_attr, trim_url(middle))
260 if '@' in middle and not middle.startswith('www.') and \
261 not ':' in middle and _simple_email_re.match(middle):
262 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
263 if lead + middle + trail != word:
264 words[i] = lead + middle + trail
265 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200266
267
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200268def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
269 """Generate some lorem impsum for the template."""
270 from jinja2.constants import LOREM_IPSUM_WORDS
Georg Brandl95632c42009-11-22 18:35:18 +0100271 from random import choice, randrange
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200272 words = LOREM_IPSUM_WORDS.split()
273 result = []
274
275 for _ in xrange(n):
276 next_capitalized = True
277 last_comma = last_fullstop = 0
278 word = None
279 last = None
280 p = []
281
282 # each paragraph contains out of 20 to 100 words.
283 for idx, _ in enumerate(xrange(randrange(min, max))):
284 while True:
285 word = choice(words)
286 if word != last:
287 last = word
288 break
289 if next_capitalized:
290 word = word.capitalize()
291 next_capitalized = False
292 # add commas
293 if idx - randrange(3, 8) > last_comma:
294 last_comma = idx
295 last_fullstop += 2
296 word += ','
297 # add end of sentences
298 if idx - randrange(10, 20) > last_fullstop:
299 last_comma = last_fullstop = idx
300 word += '.'
301 next_capitalized = True
302 p.append(word)
303
304 # ensure that the paragraph ends with a dot.
305 p = u' '.join(p)
306 if p.endswith(','):
307 p = p[:-1] + '.'
308 elif not p.endswith('.'):
309 p += '.'
310 result.append(p)
311
312 if not html:
313 return u'\n\n'.join(result)
314 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
315
316
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200317class Markup(unicode):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200318 r"""Marks a string as being safe for inclusion in HTML/XML output without
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200319 needing to be escaped. This implements the `__html__` interface a couple
Armin Ronacher58f351d2008-05-28 21:30:14 +0200320 of frameworks and web applications use. :class:`Markup` is a direct
321 subclass of `unicode` and provides all the methods of `unicode` just that
322 it escapes arguments passed and always returns `Markup`.
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200323
324 The `escape` function returns markup objects so that double escaping can't
Armin Ronacher88e1cb72008-09-08 23:55:32 +0200325 happen. If you want to use autoescaping in Jinja just enable the
326 autoescaping feature in the environment.
Armin Ronacher58f351d2008-05-28 21:30:14 +0200327
328 The constructor of the :class:`Markup` class can be used for three
329 different things: When passed an unicode object it's assumed to be safe,
330 when passed an object with an HTML representation (has an `__html__`
331 method) that representation is used, otherwise the object passed is
332 converted into a unicode string and then assumed to be safe:
333
334 >>> Markup("Hello <em>World</em>!")
335 Markup(u'Hello <em>World</em>!')
336 >>> class Foo(object):
337 ... def __html__(self):
338 ... return '<a href="#">foo</a>'
339 ...
340 >>> Markup(Foo())
341 Markup(u'<a href="#">foo</a>')
342
343 If you want object passed being always treated as unsafe you can use the
344 :meth:`escape` classmethod to create a :class:`Markup` object:
345
346 >>> Markup.escape("Hello <em>World</em>!")
347 Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
348
349 Operations on a markup string are markup aware which means that all
350 arguments are passed through the :func:`escape` function:
351
352 >>> em = Markup("<em>%s</em>")
353 >>> em % "foo & bar"
354 Markup(u'<em>foo &amp; bar</em>')
355 >>> strong = Markup("<strong>%(text)s</strong>")
356 >>> strong % {'text': '<blink>hacker here</blink>'}
357 Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
358 >>> Markup("<em>Hello</em> ") + "<foo>"
359 Markup(u'<em>Hello</em> &lt;foo&gt;')
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200360 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200361 __slots__ = ()
362
Armin Ronacher3ef20432008-06-09 18:27:19 +0200363 def __new__(cls, base=u'', encoding=None, errors='strict'):
Armin Ronacher4e6f9a22008-05-23 23:57:38 +0200364 if hasattr(base, '__html__'):
365 base = base.__html__()
Armin Ronacher3ef20432008-06-09 18:27:19 +0200366 if encoding is None:
367 return unicode.__new__(cls, base)
368 return unicode.__new__(cls, base, encoding, errors)
Armin Ronacher4e6f9a22008-05-23 23:57:38 +0200369
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200370 def __html__(self):
371 return self
372
373 def __add__(self, other):
374 if hasattr(other, '__html__') or isinstance(other, basestring):
375 return self.__class__(unicode(self) + unicode(escape(other)))
376 return NotImplemented
377
378 def __radd__(self, other):
379 if hasattr(other, '__html__') or isinstance(other, basestring):
380 return self.__class__(unicode(escape(other)) + unicode(self))
381 return NotImplemented
382
383 def __mul__(self, num):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200384 if isinstance(num, (int, long)):
385 return self.__class__(unicode.__mul__(self, num))
386 return NotImplemented
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200387 __rmul__ = __mul__
388
389 def __mod__(self, arg):
390 if isinstance(arg, tuple):
391 arg = tuple(imap(_MarkupEscapeHelper, arg))
392 else:
393 arg = _MarkupEscapeHelper(arg)
394 return self.__class__(unicode.__mod__(self, arg))
395
396 def __repr__(self):
397 return '%s(%s)' % (
398 self.__class__.__name__,
399 unicode.__repr__(self)
400 )
401
402 def join(self, seq):
403 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200404 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200405
406 def split(self, *args, **kwargs):
407 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200408 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200409
410 def rsplit(self, *args, **kwargs):
411 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200412 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200413
414 def splitlines(self, *args, **kwargs):
415 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200416 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200417
Armin Ronacher76c280b2008-05-04 12:31:48 +0200418 def unescape(self):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200419 r"""Unescape markup again into an unicode string. This also resolves
420 known HTML4 and XHTML entities:
421
422 >>> Markup("Main &raquo; <em>About</em>").unescape()
423 u'Main \xbb <em>About</em>'
424 """
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200425 from jinja2.constants import HTML_ENTITIES
Armin Ronacher76c280b2008-05-04 12:31:48 +0200426 def handle_match(m):
427 name = m.group(1)
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200428 if name in HTML_ENTITIES:
429 return unichr(HTML_ENTITIES[name])
Armin Ronacher76c280b2008-05-04 12:31:48 +0200430 try:
431 if name[:2] in ('#x', '#X'):
432 return unichr(int(name[2:], 16))
433 elif name.startswith('#'):
434 return unichr(int(name[1:]))
435 except ValueError:
436 pass
437 return u''
438 return _entity_re.sub(handle_match, unicode(self))
439
440 def striptags(self):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200441 r"""Unescape markup into an unicode string and strip all tags. This
442 also resolves known HTML4 and XHTML entities. Whitespace is
443 normalized to one:
444
445 >>> Markup("Main &raquo; <em>About</em>").striptags()
446 u'Main \xbb About'
447 """
Armin Ronacher76c280b2008-05-04 12:31:48 +0200448 stripped = u' '.join(_striptags_re.sub('', self).split())
449 return Markup(stripped).unescape()
450
Armin Ronacherf35e2812008-05-06 16:04:10 +0200451 @classmethod
452 def escape(cls, s):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200453 """Escape the string. Works like :func:`escape` with the difference
454 that for subclasses of :class:`Markup` this function would return the
455 correct subclass.
456 """
Armin Ronacherf35e2812008-05-06 16:04:10 +0200457 rv = escape(s)
458 if rv.__class__ is not cls:
459 return cls(rv)
460 return rv
461
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200462 def make_wrapper(name):
463 orig = getattr(unicode, name)
464 def func(self, *args, **kwargs):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200465 args = _escape_argspec(list(args), enumerate(args))
466 _escape_argspec(kwargs, kwargs.iteritems())
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200467 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200468 func.__name__ = orig.__name__
469 func.__doc__ = orig.__doc__
470 return func
Armin Ronacherd71fff02008-05-26 23:57:07 +0200471
Armin Ronacher42a19882009-08-05 18:45:39 +0200472 for method in '__getitem__', 'capitalize', \
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200473 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200474 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200475 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200476 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200477
478 # new in python 2.5
479 if hasattr(unicode, 'partition'):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200480 partition = make_wrapper('partition'),
481 rpartition = make_wrapper('rpartition')
482
483 # new in python 2.6
484 if hasattr(unicode, 'format'):
485 format = make_wrapper('format')
486
Armin Ronacher42a19882009-08-05 18:45:39 +0200487 # not in python 3
488 if hasattr(unicode, '__getslice__'):
489 __getslice__ = make_wrapper('__getslice__')
490
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200491 del method, make_wrapper
492
493
Armin Ronacherd71fff02008-05-26 23:57:07 +0200494def _escape_argspec(obj, iterable):
495 """Helper for various string-wrapped functions."""
496 for key, value in iterable:
497 if hasattr(value, '__html__') or isinstance(value, basestring):
498 obj[key] = escape(value)
499 return obj
500
501
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200502class _MarkupEscapeHelper(object):
503 """Helper for Markup.__mod__"""
504
505 def __init__(self, obj):
506 self.obj = obj
507
508 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
509 __unicode__ = lambda s: unicode(escape(s.obj))
510 __str__ = lambda s: str(escape(s.obj))
Armin Ronacher3ef20432008-06-09 18:27:19 +0200511 __repr__ = lambda s: str(escape(repr(s.obj)))
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200512 __int__ = lambda s: int(s.obj)
513 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200514
515
516class LRUCache(object):
517 """A simple LRU Cache implementation."""
Armin Ronacher58f351d2008-05-28 21:30:14 +0200518
519 # this is fast for small capacities (something below 1000) but doesn't
520 # scale. But as long as it's only used as storage for templates this
521 # won't do any harm.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200522
523 def __init__(self, capacity):
524 self.capacity = capacity
525 self._mapping = {}
526 self._queue = deque()
Armin Ronacher7962ce72008-05-20 17:52:52 +0200527 self._postinit()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200528
Armin Ronacher7962ce72008-05-20 17:52:52 +0200529 def _postinit(self):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200530 # alias all queue methods for faster lookup
531 self._popleft = self._queue.popleft
532 self._pop = self._queue.pop
533 if hasattr(self._queue, 'remove'):
534 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200535 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200536 self._append = self._queue.append
537
538 def _remove(self, obj):
539 """Python 2.4 compatibility."""
540 for idx, item in enumerate(self._queue):
541 if item == obj:
542 del self._queue[idx]
543 break
544
Armin Ronacher7962ce72008-05-20 17:52:52 +0200545 def __getstate__(self):
546 return {
547 'capacity': self.capacity,
548 '_mapping': self._mapping,
549 '_queue': self._queue
550 }
551
552 def __setstate__(self, d):
553 self.__dict__.update(d)
554 self._postinit()
555
556 def __getnewargs__(self):
557 return (self.capacity,)
558
Armin Ronacher814f6c22008-04-17 15:52:23 +0200559 def copy(self):
560 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200561 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200562 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200563 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200564 return rv
565
566 def get(self, key, default=None):
567 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200568 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200569 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200570 except KeyError:
571 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200572
573 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200574 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200575 leave unchanged. Return the value of this key.
576 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200577 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200578 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200579 except KeyError:
580 self[key] = default
581 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200582
583 def clear(self):
584 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200585 self._wlock.acquire()
586 try:
587 self._mapping.clear()
588 self._queue.clear()
589 finally:
590 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200591
592 def __contains__(self, key):
593 """Check if a key exists in this cache."""
594 return key in self._mapping
595
596 def __len__(self):
597 """Return the current size of the cache."""
598 return len(self._mapping)
599
600 def __repr__(self):
601 return '<%s %r>' % (
602 self.__class__.__name__,
603 self._mapping
604 )
605
606 def __getitem__(self, key):
607 """Get an item from the cache. Moves the item up so that it has the
608 highest priority then.
609
610 Raise an `KeyError` if it does not exist.
611 """
612 rv = self._mapping[key]
613 if self._queue[-1] != key:
Armin Ronacher8de6f182009-01-12 11:08:26 +0100614 try:
615 self._remove(key)
Armin Ronachere7c72bc2009-09-14 12:20:33 -0700616 except ValueError:
Armin Ronacher8de6f182009-01-12 11:08:26 +0100617 # if something removed the key from the container
618 # when we read, ignore the ValueError that we would
619 # get otherwise.
620 pass
Armin Ronacher814f6c22008-04-17 15:52:23 +0200621 self._append(key)
622 return rv
623
624 def __setitem__(self, key, value):
625 """Sets the value for an item. Moves the item up so that it
626 has the highest priority then.
627 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200628 self._wlock.acquire()
629 try:
630 if key in self._mapping:
Armin Ronacher74230e62009-10-25 12:46:31 +0100631 try:
632 self._remove(key)
633 except ValueError:
634 # __getitem__ is not locked, it might happen
635 pass
Armin Ronacher000b4912008-05-01 18:40:15 +0200636 elif len(self._mapping) == self.capacity:
637 del self._mapping[self._popleft()]
638 self._append(key)
639 self._mapping[key] = value
640 finally:
641 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200642
643 def __delitem__(self, key):
644 """Remove an item from the cache dict.
645 Raise an `KeyError` if it does not exist.
646 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200647 self._wlock.acquire()
648 try:
649 del self._mapping[key]
Armin Ronachere7c72bc2009-09-14 12:20:33 -0700650 try:
651 self._remove(key)
652 except ValueError:
653 # __getitem__ is not locked, it might happen
654 pass
Armin Ronacher000b4912008-05-01 18:40:15 +0200655 finally:
656 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200657
Armin Ronachere25f24d2008-05-19 11:20:41 +0200658 def items(self):
659 """Return a list of items."""
660 result = [(key, self._mapping[key]) for key in list(self._queue)]
661 result.reverse()
662 return result
663
664 def iteritems(self):
665 """Iterate over all items."""
666 return iter(self.items())
667
668 def values(self):
669 """Return a list of all values."""
670 return [x[1] for x in self.items()]
671
672 def itervalue(self):
673 """Iterate over all values."""
674 return iter(self.values())
675
676 def keys(self):
677 """Return a list of all keys ordered by most recent usage."""
678 return list(self)
679
680 def iterkeys(self):
681 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200682 the most recent usage.
683 """
Armin Ronachere2244882008-05-19 09:25:57 +0200684 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200685
Armin Ronachere25f24d2008-05-19 11:20:41 +0200686 __iter__ = iterkeys
687
Armin Ronacher814f6c22008-04-17 15:52:23 +0200688 def __reversed__(self):
689 """Iterate over the values in the cache dict, oldest items
690 coming first.
691 """
Armin Ronachere2244882008-05-19 09:25:57 +0200692 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200693
694 __copy__ = copy
695
Armin Ronacherbd33f112008-04-18 09:17:32 +0200696
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200697# register the LRU cache as mutable mapping if possible
698try:
699 from collections import MutableMapping
700 MutableMapping.register(LRUCache)
701except ImportError:
702 pass
703
704
Armin Ronacherccae0552008-10-05 23:08:58 +0200705class Cycler(object):
706 """A cycle helper for templates."""
707
708 def __init__(self, *items):
709 if not items:
710 raise RuntimeError('at least one item has to be provided')
711 self.items = items
712 self.reset()
713
714 def reset(self):
715 """Resets the cycle."""
716 self.pos = 0
717
718 @property
719 def current(self):
720 """Returns the current item."""
721 return self.items[self.pos]
722
723 def next(self):
724 """Goes one item ahead and returns it."""
725 rv = self.current
726 self.pos = (self.pos + 1) % len(self.items)
727 return rv
728
729
Armin Ronacherd34eb122008-10-13 23:47:51 +0200730class Joiner(object):
731 """A joining helper for templates."""
732
733 def __init__(self, sep=u', '):
734 self.sep = sep
735 self.used = False
736
737 def __call__(self):
738 if not self.used:
739 self.used = True
740 return u''
741 return self.sep
742
743
Armin Ronacherbd33f112008-04-18 09:17:32 +0200744# we have to import it down here as the speedups module imports the
745# markup type which is define above.
746try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200747 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200748except ImportError:
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200749 def escape(s):
Armin Ronacherf35e2812008-05-06 16:04:10 +0200750 """Convert the characters &, <, >, ' and " in string s to HTML-safe
751 sequences. Use this if you need to display text that might contain
Armin Ronacher9a1e33c2008-05-05 22:00:46 +0200752 such characters in HTML. Marks return value as markup string.
Armin Ronacherbd33f112008-04-18 09:17:32 +0200753 """
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200754 if hasattr(s, '__html__'):
755 return s.__html__()
756 return Markup(unicode(s)
Armin Ronacherbd33f112008-04-18 09:17:32 +0200757 .replace('&', '&amp;')
758 .replace('>', '&gt;')
759 .replace('<', '&lt;')
Armin Ronacherf35e2812008-05-06 16:04:10 +0200760 .replace("'", '&#39;')
Armin Ronacher9d42abf2008-05-14 18:10:41 +0200761 .replace('"', '&#34;')
Armin Ronacherbd33f112008-04-18 09:17:32 +0200762 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200763
764 def soft_unicode(s):
765 """Make a string unicode if it isn't already. That way a markup
766 string is not converted back to unicode.
767 """
768 if not isinstance(s, unicode):
769 s = unicode(s)
770 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200771
772
773# partials
774try:
775 from functools import partial
776except ImportError:
777 class partial(object):
778 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200779 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200780 self._args = args
781 self._kwargs = kwargs
782 def __call__(self, *args, **kwargs):
783 kwargs.update(self._kwargs)
784 return self._func(*(self._args + args), **kwargs)