blob: 90eb737811ea93edcbc179ec879a00c0c5bd600d [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.utils
4 ~~~~~~~~~~~~
5
6 Utility functions.
7
8 :copyright: 2008 by Armin Ronacher.
9 :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
Christoph Hack80909862008-04-14 01:35:10 +020013import string
Armin Ronacher000b4912008-05-01 18:40:15 +020014try:
15 from thread import allocate_lock
16except ImportError:
17 from dummy_thread import allocate_lock
Armin Ronacher76c280b2008-05-04 12:31:48 +020018from htmlentitydefs import name2codepoint
Armin Ronacher814f6c22008-04-17 15:52:23 +020019from collections import deque
20from copy import deepcopy
Armin Ronacher18c6ca02008-04-17 10:03:29 +020021from itertools import imap
Armin Ronacher8edbe492008-04-10 20:43:43 +020022
23
Armin Ronacherbe4ae242008-04-18 09:49:08 +020024_word_split_re = re.compile(r'(\s+)')
25_punctuation_re = re.compile(
26 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
27 '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
28 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
29 )
30)
31_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
Armin Ronacher76c280b2008-05-04 12:31:48 +020032_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
33_entity_re = re.compile(r'&([^;]+);')
34_entities = name2codepoint.copy()
35_entities['apos'] = 39
Armin Ronacherbe4ae242008-04-18 09:49:08 +020036
Armin Ronacher7259c762008-04-30 13:03:59 +020037# special singleton representing missing values for the runtime
38missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
39
40
Armin Ronacher7ceced52008-05-03 10:15:31 +020041# concatenate a list of strings and convert them to unicode.
42# unfortunately there is a bug in python 2.4 and lower that causes
43# unicode.join trash the traceback.
Armin Ronachercda43df2008-05-03 17:10:05 +020044_concat = u''.join
Armin Ronacher7ceced52008-05-03 10:15:31 +020045try:
46 def _test_gen_bug():
47 raise TypeError(_test_gen_bug)
48 yield None
Armin Ronachercda43df2008-05-03 17:10:05 +020049 _concat(_test_gen_bug())
Armin Ronacher7ceced52008-05-03 10:15:31 +020050except TypeError, _error:
Armin Ronachercda43df2008-05-03 17:10:05 +020051 if not _error.args or _error.args[0] is not _test_gen_bug:
Armin Ronacher7ceced52008-05-03 10:15:31 +020052 def concat(gen):
53 try:
Armin Ronachercda43df2008-05-03 17:10:05 +020054 return _concat(list(gen))
Armin Ronacher7ceced52008-05-03 10:15:31 +020055 except:
56 # this hack is needed so that the current frame
57 # does not show up in the traceback.
58 exc_type, exc_value, tb = sys.exc_info()
59 raise exc_type, exc_value, tb.tb_next
Armin Ronachercda43df2008-05-03 17:10:05 +020060 else:
61 concat = _concat
Armin Ronacher7ceced52008-05-03 10:15:31 +020062 del _test_gen_bug, _error
63
64
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020065def contextfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020066 """This decorator can be used to mark a callable as context callable. A
67 context callable is passed the active context as first argument if it
68 was directly stored in the context.
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020069 """
70 f.contextfunction = True
71 return f
72
73
Armin Ronacher203bfcb2008-04-24 21:54:44 +020074def environmentfunction(f):
Armin Ronacherd84ec462008-04-29 13:43:16 +020075 """This decorator can be used to mark a callable as environment callable.
76 A environment callable is passed the current environment as first argument
77 if it was directly stored in the context.
Armin Ronacher203bfcb2008-04-24 21:54:44 +020078 """
79 f.environmentfunction = True
80 return f
81
82
Armin Ronacher187bde12008-05-01 18:19:16 +020083def clear_caches():
84 """Jinja2 keeps internal caches for environments and lexers. These are
85 used so that Jinja2 doesn't have to recreate environments and lexers all
86 the time. Normally you don't have to care about that but if you are
87 messuring memory consumption you may want to clean the caches.
88 """
89 from jinja2.environment import _spontaneous_environments
90 from jinja2.lexer import _lexer_cache
91 _spontaneous_environments.clear()
92 _lexer_cache.clear()
93
94
Armin Ronacherf59bac22008-04-20 13:11:43 +020095def import_string(import_name, silent=False):
96 """Imports an object based on a string. This use useful if you want to
97 use import paths as endpoints or something similar. An import path can
98 be specified either in dotted notation (``xml.sax.saxutils.escape``)
99 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
100
101 If the `silent` is True the return value will be `None` if the import
102 fails.
103
104 :return: imported object
Armin Ronacher9a027f42008-04-17 11:13:40 +0200105 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200106 try:
107 if ':' in import_name:
108 module, obj = import_name.split(':', 1)
109 elif '.' in import_name:
110 items = import_name.split('.')
111 module = '.'.join(items[:-1])
112 obj = items[-1]
113 else:
114 return __import__(import_name)
115 return getattr(__import__(module, None, None, [obj]), obj)
116 except (ImportError, AttributeError):
117 if not silent:
118 raise
Armin Ronacher9a027f42008-04-17 11:13:40 +0200119
120
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200121def pformat(obj, verbose=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200122 """Prettyprint an object. Either use the `pretty` library or the
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200123 builtin `pprint`.
124 """
125 try:
126 from pretty import pretty
127 return pretty(obj, verbose=verbose)
128 except ImportError:
129 from pprint import pformat
130 return pformat(obj)
Christoph Hack80909862008-04-14 01:35:10 +0200131
132
Christoph Hack80909862008-04-14 01:35:10 +0200133def urlize(text, trim_url_limit=None, nofollow=False):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200134 """Converts any URLs in text into clickable links. Works on http://,
Christoph Hack80909862008-04-14 01:35:10 +0200135 https:// and www. links. Links can have trailing punctuation (periods,
136 commas, close-parens) and leading punctuation (opening parens) and
137 it'll still do the right thing.
138
139 If trim_url_limit is not None, the URLs in link text will be limited
140 to trim_url_limit characters.
141
142 If nofollow is True, the URLs in link text will get a rel="nofollow"
143 attribute.
144 """
145 trim_url = lambda x, limit=trim_url_limit: limit is not None \
146 and (x[:limit] + (len(x) >=limit and '...'
147 or '')) or x
148 words = _word_split_re.split(text)
149 nofollow_attr = nofollow and ' rel="nofollow"' or ''
150 for i, word in enumerate(words):
151 match = _punctuation_re.match(word)
152 if match:
153 lead, middle, trail = match.groups()
154 if middle.startswith('www.') or (
155 '@' not in middle and
156 not middle.startswith('http://') and
157 len(middle) > 0 and
158 middle[0] in string.letters + string.digits and (
159 middle.endswith('.org') or
160 middle.endswith('.net') or
161 middle.endswith('.com')
162 )):
163 middle = '<a href="http://%s"%s>%s</a>' % (middle,
164 nofollow_attr, trim_url(middle))
165 if middle.startswith('http://') or \
166 middle.startswith('https://'):
167 middle = '<a href="%s"%s>%s</a>' % (middle,
168 nofollow_attr, trim_url(middle))
169 if '@' in middle and not middle.startswith('www.') and \
170 not ':' in middle and _simple_email_re.match(middle):
171 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
172 if lead + middle + trail != word:
173 words[i] = lead + middle + trail
174 return u''.join(words)
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200175
176
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200177def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
178 """Generate some lorem impsum for the template."""
179 from jinja2.constants import LOREM_IPSUM_WORDS
180 from random import choice, random, randrange
181 words = LOREM_IPSUM_WORDS.split()
182 result = []
183
184 for _ in xrange(n):
185 next_capitalized = True
186 last_comma = last_fullstop = 0
187 word = None
188 last = None
189 p = []
190
191 # each paragraph contains out of 20 to 100 words.
192 for idx, _ in enumerate(xrange(randrange(min, max))):
193 while True:
194 word = choice(words)
195 if word != last:
196 last = word
197 break
198 if next_capitalized:
199 word = word.capitalize()
200 next_capitalized = False
201 # add commas
202 if idx - randrange(3, 8) > last_comma:
203 last_comma = idx
204 last_fullstop += 2
205 word += ','
206 # add end of sentences
207 if idx - randrange(10, 20) > last_fullstop:
208 last_comma = last_fullstop = idx
209 word += '.'
210 next_capitalized = True
211 p.append(word)
212
213 # ensure that the paragraph ends with a dot.
214 p = u' '.join(p)
215 if p.endswith(','):
216 p = p[:-1] + '.'
217 elif not p.endswith('.'):
218 p += '.'
219 result.append(p)
220
221 if not html:
222 return u'\n\n'.join(result)
223 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
224
225
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200226class Markup(unicode):
227 """Marks a string as being safe for inclusion in HTML/XML output without
228 needing to be escaped. This implements the `__html__` interface a couple
229 of frameworks and web applications use.
230
231 The `escape` function returns markup objects so that double escaping can't
232 happen. If you want to use autoescaping in Jinja just set the finalizer
233 of the environment to `escape`.
234 """
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200235 __slots__ = ()
236
237 def __html__(self):
238 return self
239
240 def __add__(self, other):
241 if hasattr(other, '__html__') or isinstance(other, basestring):
242 return self.__class__(unicode(self) + unicode(escape(other)))
243 return NotImplemented
244
245 def __radd__(self, other):
246 if hasattr(other, '__html__') or isinstance(other, basestring):
247 return self.__class__(unicode(escape(other)) + unicode(self))
248 return NotImplemented
249
250 def __mul__(self, num):
251 if not isinstance(num, (int, long)):
252 return NotImplemented
253 return self.__class__(unicode.__mul__(self, num))
254 __rmul__ = __mul__
255
256 def __mod__(self, arg):
257 if isinstance(arg, tuple):
258 arg = tuple(imap(_MarkupEscapeHelper, arg))
259 else:
260 arg = _MarkupEscapeHelper(arg)
261 return self.__class__(unicode.__mod__(self, arg))
262
263 def __repr__(self):
264 return '%s(%s)' % (
265 self.__class__.__name__,
266 unicode.__repr__(self)
267 )
268
269 def join(self, seq):
270 return self.__class__(unicode.join(self, imap(escape, seq)))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200271 join.__doc__ = unicode.join.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200272
273 def split(self, *args, **kwargs):
274 return map(self.__class__, unicode.split(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200275 split.__doc__ = unicode.split.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200276
277 def rsplit(self, *args, **kwargs):
278 return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200279 rsplit.__doc__ = unicode.rsplit.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200280
281 def splitlines(self, *args, **kwargs):
282 return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
Armin Ronacherf59bac22008-04-20 13:11:43 +0200283 splitlines.__doc__ = unicode.splitlines.__doc__
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200284
Armin Ronacher76c280b2008-05-04 12:31:48 +0200285 def unescape(self):
286 """Unescape markup."""
287 def handle_match(m):
288 name = m.group(1)
289 if name in _entities:
290 return unichr(_entities[name])
291 try:
292 if name[:2] in ('#x', '#X'):
293 return unichr(int(name[2:], 16))
294 elif name.startswith('#'):
295 return unichr(int(name[1:]))
296 except ValueError:
297 pass
298 return u''
299 return _entity_re.sub(handle_match, unicode(self))
300
301 def striptags(self):
302 """Strip tags and resolve enities."""
303 stripped = u' '.join(_striptags_re.sub('', self).split())
304 return Markup(stripped).unescape()
305
Armin Ronacherf35e2812008-05-06 16:04:10 +0200306 @classmethod
307 def escape(cls, s):
308 """Escape the string. Works like :func:`escape`."""
309 rv = escape(s)
310 if rv.__class__ is not cls:
311 return cls(rv)
312 return rv
313
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200314 def make_wrapper(name):
315 orig = getattr(unicode, name)
316 def func(self, *args, **kwargs):
317 args = list(args)
318 for idx, arg in enumerate(args):
319 if hasattr(arg, '__html__') or isinstance(arg, basestring):
320 args[idx] = escape(arg)
321 for name, arg in kwargs.iteritems():
322 if hasattr(arg, '__html__') or isinstance(arg, basestring):
323 kwargs[name] = escape(arg)
324 return self.__class__(orig(self, *args, **kwargs))
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200325 func.__name__ = orig.__name__
326 func.__doc__ = orig.__doc__
327 return func
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200328 for method in '__getitem__', '__getslice__', 'capitalize', \
329 'title', 'lower', 'upper', 'replace', 'ljust', \
Armin Ronacher709f6e52008-04-28 18:18:16 +0200330 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
Armin Ronacher316157d2008-04-28 18:30:27 +0200331 'translate', 'expandtabs', 'swapcase', 'zfill':
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200332 locals()[method] = make_wrapper(method)
Armin Ronacher709f6e52008-04-28 18:18:16 +0200333
334 # new in python 2.5
335 if hasattr(unicode, 'partition'):
Armin Ronacher316157d2008-04-28 18:30:27 +0200336 locals().update(
337 partition=make_wrapper('partition'),
338 rpartition=make_wrapper('rpartition')
339 )
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200340 del method, make_wrapper
341
342
343class _MarkupEscapeHelper(object):
344 """Helper for Markup.__mod__"""
345
346 def __init__(self, obj):
347 self.obj = obj
348
349 __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
350 __unicode__ = lambda s: unicode(escape(s.obj))
351 __str__ = lambda s: str(escape(s.obj))
352 __repr__ = lambda s: str(repr(escape(s.obj)))
353 __int__ = lambda s: int(s.obj)
354 __float__ = lambda s: float(s.obj)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200355
356
357class LRUCache(object):
358 """A simple LRU Cache implementation."""
359 # this is fast for small capacities (something around 200) but doesn't
360 # scale. But as long as it's only used for the database connections in
361 # a non request fallback it's fine.
362
363 def __init__(self, capacity):
364 self.capacity = capacity
365 self._mapping = {}
366 self._queue = deque()
Armin Ronacher7962ce72008-05-20 17:52:52 +0200367 self._postinit()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200368
Armin Ronacher7962ce72008-05-20 17:52:52 +0200369 def _postinit(self):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200370 # alias all queue methods for faster lookup
371 self._popleft = self._queue.popleft
372 self._pop = self._queue.pop
373 if hasattr(self._queue, 'remove'):
374 self._remove = self._queue.remove
Armin Ronacher000b4912008-05-01 18:40:15 +0200375 self._wlock = allocate_lock()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200376 self._append = self._queue.append
377
378 def _remove(self, obj):
379 """Python 2.4 compatibility."""
380 for idx, item in enumerate(self._queue):
381 if item == obj:
382 del self._queue[idx]
383 break
384
Armin Ronacher7962ce72008-05-20 17:52:52 +0200385 def __getstate__(self):
386 return {
387 'capacity': self.capacity,
388 '_mapping': self._mapping,
389 '_queue': self._queue
390 }
391
392 def __setstate__(self, d):
393 self.__dict__.update(d)
394 self._postinit()
395
396 def __getnewargs__(self):
397 return (self.capacity,)
398
Armin Ronacher814f6c22008-04-17 15:52:23 +0200399 def copy(self):
400 """Return an shallow copy of the instance."""
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200401 rv = self.__class__(self.capacity)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200402 rv._mapping.update(self._mapping)
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200403 rv._queue = deque(self._queue)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200404 return rv
405
406 def get(self, key, default=None):
407 """Return an item from the cache dict or `default`"""
Armin Ronacher000b4912008-05-01 18:40:15 +0200408 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200409 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200410 except KeyError:
411 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200412
413 def setdefault(self, key, default=None):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200414 """Set `default` if the key is not in the cache otherwise
Armin Ronacher814f6c22008-04-17 15:52:23 +0200415 leave unchanged. Return the value of this key.
416 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200417 try:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200418 return self[key]
Armin Ronacher000b4912008-05-01 18:40:15 +0200419 except KeyError:
420 self[key] = default
421 return default
Armin Ronacher814f6c22008-04-17 15:52:23 +0200422
423 def clear(self):
424 """Clear the cache."""
Armin Ronacher000b4912008-05-01 18:40:15 +0200425 self._wlock.acquire()
426 try:
427 self._mapping.clear()
428 self._queue.clear()
429 finally:
430 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200431
432 def __contains__(self, key):
433 """Check if a key exists in this cache."""
434 return key in self._mapping
435
436 def __len__(self):
437 """Return the current size of the cache."""
438 return len(self._mapping)
439
440 def __repr__(self):
441 return '<%s %r>' % (
442 self.__class__.__name__,
443 self._mapping
444 )
445
446 def __getitem__(self, key):
447 """Get an item from the cache. Moves the item up so that it has the
448 highest priority then.
449
450 Raise an `KeyError` if it does not exist.
451 """
452 rv = self._mapping[key]
453 if self._queue[-1] != key:
454 self._remove(key)
455 self._append(key)
456 return rv
457
458 def __setitem__(self, key, value):
459 """Sets the value for an item. Moves the item up so that it
460 has the highest priority then.
461 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200462 self._wlock.acquire()
463 try:
464 if key in self._mapping:
465 self._remove(key)
466 elif len(self._mapping) == self.capacity:
467 del self._mapping[self._popleft()]
468 self._append(key)
469 self._mapping[key] = value
470 finally:
471 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200472
473 def __delitem__(self, key):
474 """Remove an item from the cache dict.
475 Raise an `KeyError` if it does not exist.
476 """
Armin Ronacher000b4912008-05-01 18:40:15 +0200477 self._wlock.acquire()
478 try:
479 del self._mapping[key]
480 self._remove(key)
481 finally:
482 self._wlock.release()
Armin Ronacher814f6c22008-04-17 15:52:23 +0200483
Armin Ronachere25f24d2008-05-19 11:20:41 +0200484 def items(self):
485 """Return a list of items."""
486 result = [(key, self._mapping[key]) for key in list(self._queue)]
487 result.reverse()
488 return result
489
490 def iteritems(self):
491 """Iterate over all items."""
492 return iter(self.items())
493
494 def values(self):
495 """Return a list of all values."""
496 return [x[1] for x in self.items()]
497
498 def itervalue(self):
499 """Iterate over all values."""
500 return iter(self.values())
501
502 def keys(self):
503 """Return a list of all keys ordered by most recent usage."""
504 return list(self)
505
506 def iterkeys(self):
507 """Iterate over all keys in the cache dict, ordered by
Armin Ronacher814f6c22008-04-17 15:52:23 +0200508 the most recent usage.
509 """
Armin Ronachere2244882008-05-19 09:25:57 +0200510 return reversed(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200511
Armin Ronachere25f24d2008-05-19 11:20:41 +0200512 __iter__ = iterkeys
513
Armin Ronacher814f6c22008-04-17 15:52:23 +0200514 def __reversed__(self):
515 """Iterate over the values in the cache dict, oldest items
516 coming first.
517 """
Armin Ronachere2244882008-05-19 09:25:57 +0200518 return iter(tuple(self._queue))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200519
520 __copy__ = copy
521
Armin Ronacherbd33f112008-04-18 09:17:32 +0200522
523# we have to import it down here as the speedups module imports the
524# markup type which is define above.
525try:
Armin Ronacherf59bac22008-04-20 13:11:43 +0200526 from jinja2._speedups import escape, soft_unicode
Armin Ronacherbd33f112008-04-18 09:17:32 +0200527except ImportError:
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200528 def escape(s):
Armin Ronacherf35e2812008-05-06 16:04:10 +0200529 """Convert the characters &, <, >, ' and " in string s to HTML-safe
530 sequences. Use this if you need to display text that might contain
Armin Ronacher9a1e33c2008-05-05 22:00:46 +0200531 such characters in HTML. Marks return value as markup string.
Armin Ronacherbd33f112008-04-18 09:17:32 +0200532 """
Lukas Meuserad48a2e2008-05-01 18:19:57 +0200533 if hasattr(s, '__html__'):
534 return s.__html__()
535 return Markup(unicode(s)
Armin Ronacherbd33f112008-04-18 09:17:32 +0200536 .replace('&', '&amp;')
537 .replace('>', '&gt;')
538 .replace('<', '&lt;')
Armin Ronacherf35e2812008-05-06 16:04:10 +0200539 .replace("'", '&#39;')
Armin Ronacher9d42abf2008-05-14 18:10:41 +0200540 .replace('"', '&#34;')
Armin Ronacherbd33f112008-04-18 09:17:32 +0200541 )
Armin Ronacherf59bac22008-04-20 13:11:43 +0200542
543 def soft_unicode(s):
544 """Make a string unicode if it isn't already. That way a markup
545 string is not converted back to unicode.
546 """
547 if not isinstance(s, unicode):
548 s = unicode(s)
549 return s
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200550
551
552# partials
553try:
554 from functools import partial
555except ImportError:
556 class partial(object):
557 def __init__(self, _func, *args, **kwargs):
Benjamin Wiegand228c1832008-04-28 18:09:27 +0200558 self._func = _func
Armin Ronacher4f7d2d52008-04-22 10:40:26 +0200559 self._args = args
560 self._kwargs = kwargs
561 def __call__(self, *args, **kwargs):
562 kwargs.update(self._kwargs)
563 return self._func(*(self._args + args), **kwargs)