blob: 6c3805a001bb3e7d5a820c41fd4cbb72e1db7aa2 [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 Ronacher9a0078d2008-08-13 18:24:17 +020066# ironpython without stdlib doesn't have keyword
67try:
68 from keyword import iskeyword as is_python_keyword
69except ImportError:
70 _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
71 def is_python_keyword(name):
72 if _py_identifier_re.search(name) is None:
73 return False
74 try:
75 exec name + " = 42"
76 except SyntaxError:
77 return False
78 return True
79
80
81# common types. These do exist in the special types module too which however
82# does not exist in IronPython out of the box.
83class _C(object):
84 def method(self): pass
85def _func():
86 yield None
87FunctionType = type(_func)
88GeneratorType = type(_func())
89MethodType = type(_C.method)
90CodeType = type(_C.method.func_code)
91try:
92 raise TypeError()
93except TypeError:
94 _tb = sys.exc_info()[2]
95 TracebackType = type(_tb)
96 FrameType = type(_tb.tb_frame)
97del _C, _tb, _func
98
99
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200100def contextfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200101 """This decorator can be used to mark a function or method context callable.
102 A context callable is passed the active :class:`Context` as first argument when
103 called from the template. This is useful if a function wants to get access
104 to the context or functions provided on the context object. For example
105 a function that returns a sorted list of template variables the current
106 template exports could look like this::
107
Armin Ronacher58f351d2008-05-28 21:30:14 +0200108 @contextfunction
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200109 def get_exported_names(context):
110 return sorted(context.exported_vars)
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200111 """
112 f.contextfunction = True
113 return f
114
115
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200116def environmentfunction(f):
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200117 """This decorator can be used to mark a function or method as environment
118 callable. This decorator works exactly like the :func:`contextfunction`
119 decorator just that the first argument is the active :class:`Environment`
120 and not context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200121 """
122 f.environmentfunction = True
123 return f
124
125
Armin Ronacherd416a972009-02-24 22:58:00 +0100126def internalcode(f):
127 """Marks the function as internally used"""
128 internal_code.add(f.func_code)
129 return f
130
131
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200132def is_undefined(obj):
133 """Check if the object passed is undefined. This does nothing more than
134 performing an instance check against :class:`Undefined` but looks nicer.
135 This can be used for custom filters or tests that want to react to
136 undefined variables. For example a custom default filter can look like
137 this::
138
139 def default(var, default=''):
140 if is_undefined(var):
141 return default
142 return var
143 """
144 from jinja2.runtime import Undefined
145 return isinstance(obj, Undefined)
146
147
Armin Ronacherba6e25a2008-11-02 15:58:14 +0100148def consume(iterable):
149 """Consumes an iterable without doing anything with it."""
150 for event in iterable:
151 pass
152
153
Armin Ronacher187bde12008-05-01 18:19:16 +0200154def clear_caches():
155 """Jinja2 keeps internal caches for environments and lexers. These are
156 used so that Jinja2 doesn't have to recreate environments and lexers all
157 the time. Normally you don't have to care about that but if you are
158 messuring memory consumption you may want to clean the caches.
159 """
160 from jinja2.environment import _spontaneous_environments
161 from jinja2.lexer import _lexer_cache
162 _spontaneous_environments.clear()
163 _lexer_cache.clear()
164
165
Armin Ronacherf59bac22008-04-20 13:11:43 +0200166def import_string(import_name, silent=False):
167 """Imports an object based on a string. This use useful if you want to
168 use import paths as endpoints or something similar. An import path can
169 be specified either in dotted notation (``xml.sax.saxutils.escape``)
170 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
171
172 If the `silent` is True the return value will be `None` if the import
173 fails.
174
175 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200176 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200177 try:
178 if ':' in import_name:
179 module, obj = import_name.split(':', 1)
180 elif '.' in import_name:
181 items = import_name.split('.')
182 module = '.'.join(items[:-1])
183 obj = items[-1]
184 else:
185 return __import__(import_name)
186 return getattr(__import__(module, None, None, [obj]), obj)
187 except (ImportError, AttributeError):
188 if not silent:
189 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200190
191
Armin Ronacherccae0552008-10-05 23:08:58 +0200192def open_if_exists(filename, mode='r'):
193 """Returns a file descriptor for the filename if that file exists,
194 otherwise `None`.
195 """
196 try:
197 return file(filename, mode)
198 except IOError, e:
199 if e.errno not in (errno.ENOENT, errno.EISDIR):
200 raise
201
202
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200203def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200204 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200205 builtin `pprint`.
206 """
207 try:
208 from pretty import pretty
209 return pretty(obj, verbose=verbose)
210 except ImportError:
211 from pprint import pformat
212 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200213
214
Christoph Hack80909862008-04-14 01:35:10 +0200215def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200216 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200217 https:// and www. links. Links can have trailing punctuation (periods,
218 commas, close-parens) and leading punctuation (opening parens) and
219 it'll still do the right thing.
220
221 If trim_url_limit is not None, the URLs in link text will be limited
222 to trim_url_limit characters.
223
224 If nofollow is True, the URLs in link text will get a rel="nofollow"
225 attribute.
226 """
227 trim_url = lambda x, limit=trim_url_limit: limit is not None \
228 and (x[:limit] + (len(x) >=limit and '...'
229 or '')) or x
Armin Ronacherd9342dc2008-11-17 00:35:30 +0100230 words = _word_split_re.split(unicode(escape(text)))
Christoph Hack80909862008-04-14 01:35:10 +0200231 nofollow_attr = nofollow and ' rel="nofollow"' or ''
232 for i, word in enumerate(words):
233 match = _punctuation_re.match(word)
234 if match:
235 lead, middle, trail = match.groups()
236 if middle.startswith('www.') or (
237 '@' not in middle and
238 not middle.startswith('http://') and
239 len(middle) > 0 and
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200240 middle[0] in _letters + _digits and (
Christoph Hack80909862008-04-14 01:35:10 +0200241 middle.endswith('.org') or
242 middle.endswith('.net') or
243 middle.endswith('.com')
244 )):
245 middle = '<a href="http://%s"%s>%s</a>' % (middle,
246 nofollow_attr, trim_url(middle))
247 if middle.startswith('http://') or \
248 middle.startswith('https://'):
249 middle = '<a href="%s"%s>%s</a>' % (middle,
250 nofollow_attr, trim_url(middle))
251 if '@' in middle and not middle.startswith('www.') and \
252 not ':' in middle and _simple_email_re.match(middle):
253 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
254 if lead + middle + trail != word:
255 words[i] = lead + middle + trail
256 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200257
258
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200259def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
260 """Generate some lorem impsum for the template."""
261 from jinja2.constants import LOREM_IPSUM_WORDS
262 from random import choice, random, randrange
263 words = LOREM_IPSUM_WORDS.split()
264 result = []
265
266 for _ in xrange(n):
267 next_capitalized = True
268 last_comma = last_fullstop = 0
269 word = None
270 last = None
271 p = []
272
273 # each paragraph contains out of 20 to 100 words.
274 for idx, _ in enumerate(xrange(randrange(min, max))):
275 while True:
276 word = choice(words)
277 if word != last:
278 last = word
279 break
280 if next_capitalized:
281 word = word.capitalize()
282 next_capitalized = False
283 # add commas
284 if idx - randrange(3, 8) > last_comma:
285 last_comma = idx
286 last_fullstop += 2
287 word += ','
288 # add end of sentences
289 if idx - randrange(10, 20) > last_fullstop:
290 last_comma = last_fullstop = idx
291 word += '.'
292 next_capitalized = True
293 p.append(word)
294
295 # ensure that the paragraph ends with a dot.
296 p = u' '.join(p)
297 if p.endswith(','):
298 p = p[:-1] + '.'
299 elif not p.endswith('.'):
300 p += '.'
301 result.append(p)
302
303 if not html:
304 return u'\n\n'.join(result)
305 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
306
307
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200308class Markup(unicode):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200309 r"""Marks a string as being safe for inclusion in HTML/XML output without
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200310 needing to be escaped. This implements the `__html__` interface a couple
Armin Ronacher58f351d2008-05-28 21:30:14 +0200311 of frameworks and web applications use. :class:`Markup` is a direct
312 subclass of `unicode` and provides all the methods of `unicode` just that
313 it escapes arguments passed and always returns `Markup`.
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200314
315 The `escape` function returns markup objects so that double escaping can't
Armin Ronacher88e1cb72008-09-08 23:55:32 +0200316 happen. If you want to use autoescaping in Jinja just enable the
317 autoescaping feature in the environment.
Armin Ronacher58f351d2008-05-28 21:30:14 +0200318
319 The constructor of the :class:`Markup` class can be used for three
320 different things: When passed an unicode object it's assumed to be safe,
321 when passed an object with an HTML representation (has an `__html__`
322 method) that representation is used, otherwise the object passed is
323 converted into a unicode string and then assumed to be safe:
324
325 >>> Markup("Hello <em>World</em>!")
326 Markup(u'Hello <em>World</em>!')
327 >>> class Foo(object):
328 ... def __html__(self):
329 ... return '<a href="#">foo</a>'
330 ...
331 >>> Markup(Foo())
332 Markup(u'<a href="#">foo</a>')
333
334 If you want object passed being always treated as unsafe you can use the
335 :meth:`escape` classmethod to create a :class:`Markup` object:
336
337 >>> Markup.escape("Hello <em>World</em>!")
338 Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
339
340 Operations on a markup string are markup aware which means that all
341 arguments are passed through the :func:`escape` function:
342
343 >>> em = Markup("<em>%s</em>")
344 >>> em % "foo & bar"
345 Markup(u'<em>foo &amp; bar</em>')
346 >>> strong = Markup("<strong>%(text)s</strong>")
347 >>> strong % {'text': '<blink>hacker here</blink>'}
348 Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
349 >>> Markup("<em>Hello</em> ") + "<foo>"
350 Markup(u'<em>Hello</em> &lt;foo&gt;')
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200351 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200352 __slots__ = ()
353
Armin Ronacher3ef20432008-06-09 18:27:19 +0200354 def __new__(cls, base=u'', encoding=None, errors='strict'):
Armin Ronacher4e6f9a22008-05-23 23:57:38 +0200355 if hasattr(base, '__html__'):
356 base = base.__html__()
Armin Ronacher3ef20432008-06-09 18:27:19 +0200357 if encoding is None:
358 return unicode.__new__(cls, base)
359 return unicode.__new__(cls, base, encoding, errors)
Armin Ronacher4e6f9a22008-05-23 23:57:38 +0200360
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200361 def __html__(self):
362 return self
363
364 def __add__(self, other):
365 if hasattr(other, '__html__') or isinstance(other, basestring):
366 return self.__class__(unicode(self) + unicode(escape(other)))
367 return NotImplemented
368
369 def __radd__(self, other):
370 if hasattr(other, '__html__') or isinstance(other, basestring):
371 return self.__class__(unicode(escape(other)) + unicode(self))
372 return NotImplemented
373
374 def __mul__(self, num):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200375 if isinstance(num, (int, long)):
376 return self.__class__(unicode.__mul__(self, num))
377 return NotImplemented
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200378 __rmul__ = __mul__
379
380 def __mod__(self, arg):
381 if isinstance(arg, tuple):
382 arg = tuple(imap(_MarkupEscapeHelper, arg))
383 else:
384 arg = _MarkupEscapeHelper(arg)
385 return self.__class__(unicode.__mod__(self, arg))
386
387 def __repr__(self):
388 return '%s(%s)' % (
389 self.__class__.__name__,
390 unicode.__repr__(self)
391 )
392
393 def join(self, seq):
394 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200395 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200396
397 def split(self, *args, **kwargs):
398 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200399 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200400
401 def rsplit(self, *args, **kwargs):
402 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200403 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200404
405 def splitlines(self, *args, **kwargs):
406 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200407 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200408
Armin Ronacher76c280b2008-05-04 12:31:48 +0200409 def unescape(self):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200410 r"""Unescape markup again into an unicode string. This also resolves
411 known HTML4 and XHTML entities:
412
413 >>> Markup("Main &raquo; <em>About</em>").unescape()
414 u'Main \xbb <em>About</em>'
415 """
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200416 from jinja2.constants import HTML_ENTITIES
Armin Ronacher76c280b2008-05-04 12:31:48 +0200417 def handle_match(m):
418 name = m.group(1)
Armin Ronacher9a0078d2008-08-13 18:24:17 +0200419 if name in HTML_ENTITIES:
420 return unichr(HTML_ENTITIES[name])
Armin Ronacher76c280b2008-05-04 12:31:48 +0200421 try:
422 if name[:2] in ('#x', '#X'):
423 return unichr(int(name[2:], 16))
424 elif name.startswith('#'):
425 return unichr(int(name[1:]))
426 except ValueError:
427 pass
428 return u''
429 return _entity_re.sub(handle_match, unicode(self))
430
431 def striptags(self):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200432 r"""Unescape markup into an unicode string and strip all tags. This
433 also resolves known HTML4 and XHTML entities. Whitespace is
434 normalized to one:
435
436 >>> Markup("Main &raquo; <em>About</em>").striptags()
437 u'Main \xbb About'
438 """
Armin Ronacher76c280b2008-05-04 12:31:48 +0200439 stripped = u' '.join(_striptags_re.sub('', self).split())
440 return Markup(stripped).unescape()
441
Armin Ronacherf35e2812008-05-06 16:04:10 +0200442 @classmethod
443 def escape(cls, s):
Armin Ronacher58f351d2008-05-28 21:30:14 +0200444 """Escape the string. Works like :func:`escape` with the difference
445 that for subclasses of :class:`Markup` this function would return the
446 correct subclass.
447 """
Armin Ronacherf35e2812008-05-06 16:04:10 +0200448 rv = escape(s)
449 if rv.__class__ is not cls:
450 return cls(rv)
451 return rv
452
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200453 def make_wrapper(name):
454 orig = getattr(unicode, name)
455 def func(self, *args, **kwargs):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200456 args = _escape_argspec(list(args), enumerate(args))
457 _escape_argspec(kwargs, kwargs.iteritems())
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200458 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200459 func.__name__ = orig.__name__
460 func.__doc__ = orig.__doc__
461 return func
Armin Ronacherd71fff02008-05-26 23:57:07 +0200462
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200463 for method in '__getitem__', '__getslice__', 'capitalize', \
464 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200465 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200466 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200467 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200468
469 # new in python 2.5
470 if hasattr(unicode, 'partition'):
Armin Ronacherd71fff02008-05-26 23:57:07 +0200471 partition = make_wrapper('partition'),
472 rpartition = make_wrapper('rpartition')
473
474 # new in python 2.6
475 if hasattr(unicode, 'format'):
476 format = make_wrapper('format')
477
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200478 del method, make_wrapper
479
480
Armin Ronacherd71fff02008-05-26 23:57:07 +0200481def _escape_argspec(obj, iterable):
482 """Helper for various string-wrapped functions."""
483 for key, value in iterable:
484 if hasattr(value, '__html__') or isinstance(value, basestring):
485 obj[key] = escape(value)
486 return obj
487
488
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200489class _MarkupEscapeHelper(object):
490 """Helper for Markup.__mod__"""
491
492 def __init__(self, obj):
493 self.obj = obj
494
495 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
496 __unicode__ = lambda s: unicode(escape(s.obj))
497 __str__ = lambda s: str(escape(s.obj))
Armin Ronacher3ef20432008-06-09 18:27:19 +0200498 __repr__ = lambda s: str(escape(repr(s.obj)))
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200499 __int__ = lambda s: int(s.obj)
500 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200501
502
503class LRUCache(object):
504 """A simple LRU Cache implementation."""
Armin Ronacher58f351d2008-05-28 21:30:14 +0200505
506 # this is fast for small capacities (something below 1000) but doesn't
507 # scale. But as long as it's only used as storage for templates this
508 # won't do any harm.
Armin Ronacher814f6c22008-04-17 15:52:23 +0200509
510 def __init__(self, capacity):
511 self.capacity = capacity
512 self._mapping = {}
513 self._queue = deque()
Armin Ronacher7962ce72008-05-20 17:52:52 +0200514 self._postinit()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200515
Armin Ronacher7962ce72008-05-20 17:52:52 +0200516 def _postinit(self):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200517 # alias all queue methods for faster lookup
518 self._popleft = self._queue.popleft
519 self._pop = self._queue.pop
520 if hasattr(self._queue, 'remove'):
521 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200522 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200523 self._append = self._queue.append
524
525 def _remove(self, obj):
526 """Python 2.4 compatibility."""
527 for idx, item in enumerate(self._queue):
528 if item == obj:
529 del self._queue[idx]
530 break
531
Armin Ronacher7962ce72008-05-20 17:52:52 +0200532 def __getstate__(self):
533 return {
534 'capacity': self.capacity,
535 '_mapping': self._mapping,
536 '_queue': self._queue
537 }
538
539 def __setstate__(self, d):
540 self.__dict__.update(d)
541 self._postinit()
542
543 def __getnewargs__(self):
544 return (self.capacity,)
545
Armin Ronacher814f6c22008-04-17 15:52:23 +0200546 def copy(self):
547 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200548 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200549 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200550 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200551 return rv
552
553 def get(self, key, default=None):
554 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200555 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200556 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200557 except KeyError:
558 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200559
560 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200561 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200562 leave unchanged. Return the value of this key.
563 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200564 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200565 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200566 except KeyError:
567 self[key] = default
568 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200569
570 def clear(self):
571 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200572 self._wlock.acquire()
573 try:
574 self._mapping.clear()
575 self._queue.clear()
576 finally:
577 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200578
579 def __contains__(self, key):
580 """Check if a key exists in this cache."""
581 return key in self._mapping
582
583 def __len__(self):
584 """Return the current size of the cache."""
585 return len(self._mapping)
586
587 def __repr__(self):
588 return '<%s %r>' % (
589 self.__class__.__name__,
590 self._mapping
591 )
592
593 def __getitem__(self, key):
594 """Get an item from the cache. Moves the item up so that it has the
595 highest priority then.
596
597 Raise an `KeyError` if it does not exist.
598 """
599 rv = self._mapping[key]
600 if self._queue[-1] != key:
Armin Ronacher8de6f182009-01-12 11:08:26 +0100601 try:
602 self._remove(key)
603 except:
604 # if something removed the key from the container
605 # when we read, ignore the ValueError that we would
606 # get otherwise.
607 pass
Armin Ronacher814f6c22008-04-17 15:52:23 +0200608 self._append(key)
609 return rv
610
611 def __setitem__(self, key, value):
612 """Sets the value for an item. Moves the item up so that it
613 has the highest priority then.
614 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200615 self._wlock.acquire()
616 try:
617 if key in self._mapping:
618 self._remove(key)
619 elif len(self._mapping) == self.capacity:
620 del self._mapping[self._popleft()]
621 self._append(key)
622 self._mapping[key] = value
623 finally:
624 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200625
626 def __delitem__(self, key):
627 """Remove an item from the cache dict.
628 Raise an `KeyError` if it does not exist.
629 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200630 self._wlock.acquire()
631 try:
632 del self._mapping[key]
633 self._remove(key)
634 finally:
635 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200636
Armin Ronachere25f24d2008-05-19 11:20:41 +0200637 def items(self):
638 """Return a list of items."""
639 result = [(key, self._mapping[key]) for key in list(self._queue)]
640 result.reverse()
641 return result
642
643 def iteritems(self):
644 """Iterate over all items."""
645 return iter(self.items())
646
647 def values(self):
648 """Return a list of all values."""
649 return [x[1] for x in self.items()]
650
651 def itervalue(self):
652 """Iterate over all values."""
653 return iter(self.values())
654
655 def keys(self):
656 """Return a list of all keys ordered by most recent usage."""
657 return list(self)
658
659 def iterkeys(self):
660 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200661 the most recent usage.
662 """
Armin Ronachere2244882008-05-19 09:25:57 +0200663 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200664
Armin Ronachere25f24d2008-05-19 11:20:41 +0200665 __iter__ = iterkeys
666
Armin Ronacher814f6c22008-04-17 15:52:23 +0200667 def __reversed__(self):
668 """Iterate over the values in the cache dict, oldest items
669 coming first.
670 """
Armin Ronachere2244882008-05-19 09:25:57 +0200671 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200672
673 __copy__ = copy
674
Armin Ronacherbd33f112008-04-18 09:17:32 +0200675
Armin Ronacher9bb7e472008-05-28 11:26:59 +0200676# register the LRU cache as mutable mapping if possible
677try:
678 from collections import MutableMapping
679 MutableMapping.register(LRUCache)
680except ImportError:
681 pass
682
683
Armin Ronacherccae0552008-10-05 23:08:58 +0200684class Cycler(object):
685 """A cycle helper for templates."""
686
687 def __init__(self, *items):
688 if not items:
689 raise RuntimeError('at least one item has to be provided')
690 self.items = items
691 self.reset()
692
693 def reset(self):
694 """Resets the cycle."""
695 self.pos = 0
696
697 @property
698 def current(self):
699 """Returns the current item."""
700 return self.items[self.pos]
701
702 def next(self):
703 """Goes one item ahead and returns it."""
704 rv = self.current
705 self.pos = (self.pos + 1) % len(self.items)
706 return rv
707
708
Armin Ronacherd34eb122008-10-13 23:47:51 +0200709class Joiner(object):
710 """A joining helper for templates."""
711
712 def __init__(self, sep=u', '):
713 self.sep = sep
714 self.used = False
715
716 def __call__(self):
717 if not self.used:
718 self.used = True
719 return u''
720 return self.sep
721
722
Armin Ronacherbd33f112008-04-18 09:17:32 +0200723# we have to import it down here as the speedups module imports the
724# markup type which is define above.
725try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200726 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200727except ImportError:
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200728 def escape(s):
Armin Ronacherf35e2812008-05-06 16:04:10 +0200729 """Convert the characters &, <, >, ' and " in string s to HTML-safe
730 sequences. Use this if you need to display text that might contain
Armin Ronacher9a1e33c2008-05-05 22:00:46 +0200731 such characters in HTML. Marks return value as markup string.
Armin Ronacherbd33f112008-04-18 09:17:32 +0200732 """
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200733 if hasattr(s, '__html__'):
734 return s.__html__()
735 return Markup(unicode(s)
Armin Ronacherbd33f112008-04-18 09:17:32 +0200736 .replace('&', '&amp;')
737 .replace('>', '&gt;')
738 .replace('<', '&lt;')
Armin Ronacherf35e2812008-05-06 16:04:10 +0200739 .replace("'", '&#39;')
Armin Ronacher9d42abf2008-05-14 18:10:41 +0200740 .replace('"', '&#34;')
Armin Ronacherbd33f112008-04-18 09:17:32 +0200741 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200742
743 def soft_unicode(s):
744 """Make a string unicode if it isn't already. That way a markup
745 string is not converted back to unicode.
746 """
747 if not isinstance(s, unicode):
748 s = unicode(s)
749 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200750
751
752# partials
753try:
754 from functools import partial
755except ImportError:
756 class partial(object):
757 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200758 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200759 self._args = args
760 self._kwargs = kwargs
761 def __call__(self, *args, **kwargs):
762 kwargs.update(self._kwargs)
763 return self._func(*(self._args + args), **kwargs)