Finished support for newstyle gettext translations
[jinja2.git] / jinja2 / utils.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.utils
4     ~~~~~~~~~~~~
5
6     Utility functions.
7
8     :copyright: (c) 2010 by the Jinja Team.
9     :license: BSD, see LICENSE for more details.
10 """
11 import re
12 import sys
13 import errno
14 try:
15     from thread import allocate_lock
16 except ImportError:
17     from dummy_thread import allocate_lock
18 from collections import deque
19 from itertools import imap
20
21
22 _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._-]+$')
30 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
31 _entity_re = re.compile(r'&([^;]+);')
32 _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
33 _digits = '0123456789'
34
35 # special singleton representing missing values for the runtime
36 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
37
38 # internal code
39 internal_code = set()
40
41
42 # 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.
45 _concat = u''.join
46 try:
47     def _test_gen_bug():
48         raise TypeError(_test_gen_bug)
49         yield None
50     _concat(_test_gen_bug())
51 except TypeError, _error:
52     if not _error.args or _error.args[0] is not _test_gen_bug:
53         def concat(gen):
54             try:
55                 return _concat(list(gen))
56             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
61     else:
62         concat = _concat
63     del _test_gen_bug, _error
64
65
66 # for python 2.x we create outselves a next() function that does the
67 # basics without exception catching.
68 try:
69     next = next
70 except NameError:
71     def next(x):
72         return x.next()
73
74
75 # if this python version is unable to deal with unicode filenames
76 # when passed to encode we let this function encode it properly.
77 # This is used in a couple of places.  As far as Jinja is concerned
78 # filenames are unicode *or* bytestrings in 2.x and unicode only in
79 # 3.x because compile cannot handle bytes
80 if sys.version_info < (3, 0):
81     def _encode_filename(filename):
82         if isinstance(filename, unicode):
83             return filename.encode('utf-8')
84         return filename
85 else:
86     def _encode_filename(filename):
87         assert filename is None or isinstance(filename, str), \
88             'filenames must be strings'
89         return filename
90
91 from keyword import iskeyword as is_python_keyword
92
93
94 # common types.  These do exist in the special types module too which however
95 # does not exist in IronPython out of the box.  Also that way we don't have
96 # to deal with implementation specific stuff here
97 class _C(object):
98     def method(self): pass
99 def _func():
100     yield None
101 FunctionType = type(_func)
102 GeneratorType = type(_func())
103 MethodType = type(_C.method)
104 CodeType = type(_C.method.func_code)
105 try:
106     raise TypeError()
107 except TypeError:
108     _tb = sys.exc_info()[2]
109     TracebackType = type(_tb)
110     FrameType = type(_tb.tb_frame)
111 del _C, _tb, _func
112
113
114 def contextfunction(f):
115     """This decorator can be used to mark a function or method context callable.
116     A context callable is passed the active :class:`Context` as first argument when
117     called from the template.  This is useful if a function wants to get access
118     to the context or functions provided on the context object.  For example
119     a function that returns a sorted list of template variables the current
120     template exports could look like this::
121
122         @contextfunction
123         def get_exported_names(context):
124             return sorted(context.exported_vars)
125     """
126     f.contextfunction = True
127     return f
128
129
130 def evalcontextfunction(f):
131     """This decoraotr can be used to mark a function or method as an eval
132     context callable.  This is similar to the :func:`contextfunction`
133     but instead of passing the context, an evaluation context object is
134     passed.  For more information about the eval context, see
135     :ref:`eval-context`.
136
137     .. versionadded:: 2.4
138     """
139     f.evalcontextfunction = True
140     return f
141
142
143 def environmentfunction(f):
144     """This decorator can be used to mark a function or method as environment
145     callable.  This decorator works exactly like the :func:`contextfunction`
146     decorator just that the first argument is the active :class:`Environment`
147     and not context.
148     """
149     f.environmentfunction = True
150     return f
151
152
153 def internalcode(f):
154     """Marks the function as internally used"""
155     internal_code.add(f.func_code)
156     return f
157
158
159 def is_undefined(obj):
160     """Check if the object passed is undefined.  This does nothing more than
161     performing an instance check against :class:`Undefined` but looks nicer.
162     This can be used for custom filters or tests that want to react to
163     undefined variables.  For example a custom default filter can look like
164     this::
165
166         def default(var, default=''):
167             if is_undefined(var):
168                 return default
169             return var
170     """
171     from jinja2.runtime import Undefined
172     return isinstance(obj, Undefined)
173
174
175 def consume(iterable):
176     """Consumes an iterable without doing anything with it."""
177     for event in iterable:
178         pass
179
180
181 def clear_caches():
182     """Jinja2 keeps internal caches for environments and lexers.  These are
183     used so that Jinja2 doesn't have to recreate environments and lexers all
184     the time.  Normally you don't have to care about that but if you are
185     messuring memory consumption you may want to clean the caches.
186     """
187     from jinja2.environment import _spontaneous_environments
188     from jinja2.lexer import _lexer_cache
189     _spontaneous_environments.clear()
190     _lexer_cache.clear()
191
192
193 def import_string(import_name, silent=False):
194     """Imports an object based on a string.  This use useful if you want to
195     use import paths as endpoints or something similar.  An import path can
196     be specified either in dotted notation (``xml.sax.saxutils.escape``)
197     or with a colon as object delimiter (``xml.sax.saxutils:escape``).
198
199     If the `silent` is True the return value will be `None` if the import
200     fails.
201
202     :return: imported object
203     """
204     try:
205         if ':' in import_name:
206             module, obj = import_name.split(':', 1)
207         elif '.' in import_name:
208             items = import_name.split('.')
209             module = '.'.join(items[:-1])
210             obj = items[-1]
211         else:
212             return __import__(import_name)
213         return getattr(__import__(module, None, None, [obj]), obj)
214     except (ImportError, AttributeError):
215         if not silent:
216             raise
217
218
219 def open_if_exists(filename, mode='rb'):
220     """Returns a file descriptor for the filename if that file exists,
221     otherwise `None`.
222     """
223     try:
224         return open(filename, mode)
225     except IOError, e:
226         if e.errno not in (errno.ENOENT, errno.EISDIR):
227             raise
228
229
230 def object_type_repr(obj):
231     """Returns the name of the object's type.  For some recognized
232     singletons the name of the object is returned instead. (For
233     example for `None` and `Ellipsis`).
234     """
235     if obj is None:
236         return 'None'
237     elif obj is Ellipsis:
238         return 'Ellipsis'
239     # __builtin__ in 2.x, builtins in 3.x
240     if obj.__class__.__module__ in ('__builtin__', 'builtins'):
241         name = obj.__class__.__name__
242     else:
243         name = obj.__class__.__module__ + '.' + obj.__class__.__name__
244     return '%s object' % name
245
246
247 def pformat(obj, verbose=False):
248     """Prettyprint an object.  Either use the `pretty` library or the
249     builtin `pprint`.
250     """
251     try:
252         from pretty import pretty
253         return pretty(obj, verbose=verbose)
254     except ImportError:
255         from pprint import pformat
256         return pformat(obj)
257
258
259 def urlize(text, trim_url_limit=None, nofollow=False):
260     """Converts any URLs in text into clickable links. Works on http://,
261     https:// and www. links. Links can have trailing punctuation (periods,
262     commas, close-parens) and leading punctuation (opening parens) and
263     it'll still do the right thing.
264
265     If trim_url_limit is not None, the URLs in link text will be limited
266     to trim_url_limit characters.
267
268     If nofollow is True, the URLs in link text will get a rel="nofollow"
269     attribute.
270     """
271     trim_url = lambda x, limit=trim_url_limit: limit is not None \
272                          and (x[:limit] + (len(x) >=limit and '...'
273                          or '')) or x
274     words = _word_split_re.split(unicode(escape(text)))
275     nofollow_attr = nofollow and ' rel="nofollow"' or ''
276     for i, word in enumerate(words):
277         match = _punctuation_re.match(word)
278         if match:
279             lead, middle, trail = match.groups()
280             if middle.startswith('www.') or (
281                 '@' not in middle and
282                 not middle.startswith('http://') and
283                 len(middle) > 0 and
284                 middle[0] in _letters + _digits and (
285                     middle.endswith('.org') or
286                     middle.endswith('.net') or
287                     middle.endswith('.com')
288                 )):
289                 middle = '<a href="http://%s"%s>%s</a>' % (middle,
290                     nofollow_attr, trim_url(middle))
291             if middle.startswith('http://') or \
292                middle.startswith('https://'):
293                 middle = '<a href="%s"%s>%s</a>' % (middle,
294                     nofollow_attr, trim_url(middle))
295             if '@' in middle and not middle.startswith('www.') and \
296                not ':' in middle and _simple_email_re.match(middle):
297                 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
298             if lead + middle + trail != word:
299                 words[i] = lead + middle + trail
300     return u''.join(words)
301
302
303 def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
304     """Generate some lorem impsum for the template."""
305     from jinja2.constants import LOREM_IPSUM_WORDS
306     from random import choice, randrange
307     words = LOREM_IPSUM_WORDS.split()
308     result = []
309
310     for _ in xrange(n):
311         next_capitalized = True
312         last_comma = last_fullstop = 0
313         word = None
314         last = None
315         p = []
316
317         # each paragraph contains out of 20 to 100 words.
318         for idx, _ in enumerate(xrange(randrange(min, max))):
319             while True:
320                 word = choice(words)
321                 if word != last:
322                     last = word
323                     break
324             if next_capitalized:
325                 word = word.capitalize()
326                 next_capitalized = False
327             # add commas
328             if idx - randrange(3, 8) > last_comma:
329                 last_comma = idx
330                 last_fullstop += 2
331                 word += ','
332             # add end of sentences
333             if idx - randrange(10, 20) > last_fullstop:
334                 last_comma = last_fullstop = idx
335                 word += '.'
336                 next_capitalized = True
337             p.append(word)
338
339         # ensure that the paragraph ends with a dot.
340         p = u' '.join(p)
341         if p.endswith(','):
342             p = p[:-1] + '.'
343         elif not p.endswith('.'):
344             p += '.'
345         result.append(p)
346
347     if not html:
348         return u'\n\n'.join(result)
349     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
350
351
352 class Markup(unicode):
353     r"""Marks a string as being safe for inclusion in HTML/XML output without
354     needing to be escaped.  This implements the `__html__` interface a couple
355     of frameworks and web applications use.  :class:`Markup` is a direct
356     subclass of `unicode` and provides all the methods of `unicode` just that
357     it escapes arguments passed and always returns `Markup`.
358
359     The `escape` function returns markup objects so that double escaping can't
360     happen.  If you want to use autoescaping in Jinja just enable the
361     autoescaping feature in the environment.
362
363     The constructor of the :class:`Markup` class can be used for three
364     different things:  When passed an unicode object it's assumed to be safe,
365     when passed an object with an HTML representation (has an `__html__`
366     method) that representation is used, otherwise the object passed is
367     converted into a unicode string and then assumed to be safe:
368
369     >>> Markup("Hello <em>World</em>!")
370     Markup(u'Hello <em>World</em>!')
371     >>> class Foo(object):
372     ...  def __html__(self):
373     ...   return '<a href="#">foo</a>'
374     ... 
375     >>> Markup(Foo())
376     Markup(u'<a href="#">foo</a>')
377
378     If you want object passed being always treated as unsafe you can use the
379     :meth:`escape` classmethod to create a :class:`Markup` object:
380
381     >>> Markup.escape("Hello <em>World</em>!")
382     Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
383
384     Operations on a markup string are markup aware which means that all
385     arguments are passed through the :func:`escape` function:
386
387     >>> em = Markup("<em>%s</em>")
388     >>> em % "foo & bar"
389     Markup(u'<em>foo &amp; bar</em>')
390     >>> strong = Markup("<strong>%(text)s</strong>")
391     >>> strong % {'text': '<blink>hacker here</blink>'}
392     Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
393     >>> Markup("<em>Hello</em> ") + "<foo>"
394     Markup(u'<em>Hello</em> &lt;foo&gt;')
395     """
396     __slots__ = ()
397
398     def __new__(cls, base=u'', encoding=None, errors='strict'):
399         if hasattr(base, '__html__'):
400             base = base.__html__()
401         if encoding is None:
402             return unicode.__new__(cls, base)
403         return unicode.__new__(cls, base, encoding, errors)
404
405     def __html__(self):
406         return self
407
408     def __add__(self, other):
409         if hasattr(other, '__html__') or isinstance(other, basestring):
410             return self.__class__(unicode(self) + unicode(escape(other)))
411         return NotImplemented
412
413     def __radd__(self, other):
414         if hasattr(other, '__html__') or isinstance(other, basestring):
415             return self.__class__(unicode(escape(other)) + unicode(self))
416         return NotImplemented
417
418     def __mul__(self, num):
419         if isinstance(num, (int, long)):
420             return self.__class__(unicode.__mul__(self, num))
421         return NotImplemented
422     __rmul__ = __mul__
423
424     def __mod__(self, arg):
425         if isinstance(arg, tuple):
426             arg = tuple(imap(_MarkupEscapeHelper, arg))
427         else:
428             arg = _MarkupEscapeHelper(arg)
429         return self.__class__(unicode.__mod__(self, arg))
430
431     def __repr__(self):
432         return '%s(%s)' % (
433             self.__class__.__name__,
434             unicode.__repr__(self)
435         )
436
437     def join(self, seq):
438         return self.__class__(unicode.join(self, imap(escape, seq)))
439     join.__doc__ = unicode.join.__doc__
440
441     def split(self, *args, **kwargs):
442         return map(self.__class__, unicode.split(self, *args, **kwargs))
443     split.__doc__ = unicode.split.__doc__
444
445     def rsplit(self, *args, **kwargs):
446         return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
447     rsplit.__doc__ = unicode.rsplit.__doc__
448
449     def splitlines(self, *args, **kwargs):
450         return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
451     splitlines.__doc__ = unicode.splitlines.__doc__
452
453     def unescape(self):
454         r"""Unescape markup again into an unicode string.  This also resolves
455         known HTML4 and XHTML entities:
456
457         >>> Markup("Main &raquo; <em>About</em>").unescape()
458         u'Main \xbb <em>About</em>'
459         """
460         from jinja2.constants import HTML_ENTITIES
461         def handle_match(m):
462             name = m.group(1)
463             if name in HTML_ENTITIES:
464                 return unichr(HTML_ENTITIES[name])
465             try:
466                 if name[:2] in ('#x', '#X'):
467                     return unichr(int(name[2:], 16))
468                 elif name.startswith('#'):
469                     return unichr(int(name[1:]))
470             except ValueError:
471                 pass
472             return u''
473         return _entity_re.sub(handle_match, unicode(self))
474
475     def striptags(self):
476         r"""Unescape markup into an unicode string and strip all tags.  This
477         also resolves known HTML4 and XHTML entities.  Whitespace is
478         normalized to one:
479
480         >>> Markup("Main &raquo;  <em>About</em>").striptags()
481         u'Main \xbb About'
482         """
483         stripped = u' '.join(_striptags_re.sub('', self).split())
484         return Markup(stripped).unescape()
485
486     @classmethod
487     def escape(cls, s):
488         """Escape the string.  Works like :func:`escape` with the difference
489         that for subclasses of :class:`Markup` this function would return the
490         correct subclass.
491         """
492         rv = escape(s)
493         if rv.__class__ is not cls:
494             return cls(rv)
495         return rv
496
497     def make_wrapper(name):
498         orig = getattr(unicode, name)
499         def func(self, *args, **kwargs):
500             args = _escape_argspec(list(args), enumerate(args))
501             _escape_argspec(kwargs, kwargs.iteritems())
502             return self.__class__(orig(self, *args, **kwargs))
503         func.__name__ = orig.__name__
504         func.__doc__ = orig.__doc__
505         return func
506
507     for method in '__getitem__', 'capitalize', \
508                   'title', 'lower', 'upper', 'replace', 'ljust', \
509                   'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
510                   'translate', 'expandtabs', 'swapcase', 'zfill':
511         locals()[method] = make_wrapper(method)
512
513     # new in python 2.5
514     if hasattr(unicode, 'partition'):
515         partition = make_wrapper('partition'),
516         rpartition = make_wrapper('rpartition')
517
518     # new in python 2.6
519     if hasattr(unicode, 'format'):
520         format = make_wrapper('format')
521
522     # not in python 3
523     if hasattr(unicode, '__getslice__'):
524         __getslice__ = make_wrapper('__getslice__')
525
526     del method, make_wrapper
527
528
529 def _escape_argspec(obj, iterable):
530     """Helper for various string-wrapped functions."""
531     for key, value in iterable:
532         if hasattr(value, '__html__') or isinstance(value, basestring):
533             obj[key] = escape(value)
534     return obj
535
536
537 class _MarkupEscapeHelper(object):
538     """Helper for Markup.__mod__"""
539
540     def __init__(self, obj):
541         self.obj = obj
542
543     __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
544     __str__ = lambda s: str(escape(s.obj))
545     __unicode__ = lambda s: unicode(escape(s.obj))
546     __repr__ = lambda s: str(escape(repr(s.obj)))
547     __int__ = lambda s: int(s.obj)
548     __float__ = lambda s: float(s.obj)
549
550
551 class LRUCache(object):
552     """A simple LRU Cache implementation."""
553
554     # this is fast for small capacities (something below 1000) but doesn't
555     # scale.  But as long as it's only used as storage for templates this
556     # won't do any harm.
557
558     def __init__(self, capacity):
559         self.capacity = capacity
560         self._mapping = {}
561         self._queue = deque()
562         self._postinit()
563
564     def _postinit(self):
565         # alias all queue methods for faster lookup
566         self._popleft = self._queue.popleft
567         self._pop = self._queue.pop
568         if hasattr(self._queue, 'remove'):
569             self._remove = self._queue.remove
570         self._wlock = allocate_lock()
571         self._append = self._queue.append
572
573     def _remove(self, obj):
574         """Python 2.4 compatibility."""
575         for idx, item in enumerate(self._queue):
576             if item == obj:
577                 del self._queue[idx]
578                 break
579
580     def __getstate__(self):
581         return {
582             'capacity':     self.capacity,
583             '_mapping':     self._mapping,
584             '_queue':       self._queue
585         }
586
587     def __setstate__(self, d):
588         self.__dict__.update(d)
589         self._postinit()
590
591     def __getnewargs__(self):
592         return (self.capacity,)
593
594     def copy(self):
595         """Return an shallow copy of the instance."""
596         rv = self.__class__(self.capacity)
597         rv._mapping.update(self._mapping)
598         rv._queue = deque(self._queue)
599         return rv
600
601     def get(self, key, default=None):
602         """Return an item from the cache dict or `default`"""
603         try:
604             return self[key]
605         except KeyError:
606             return default
607
608     def setdefault(self, key, default=None):
609         """Set `default` if the key is not in the cache otherwise
610         leave unchanged. Return the value of this key.
611         """
612         try:
613             return self[key]
614         except KeyError:
615             self[key] = default
616             return default
617
618     def clear(self):
619         """Clear the cache."""
620         self._wlock.acquire()
621         try:
622             self._mapping.clear()
623             self._queue.clear()
624         finally:
625             self._wlock.release()
626
627     def __contains__(self, key):
628         """Check if a key exists in this cache."""
629         return key in self._mapping
630
631     def __len__(self):
632         """Return the current size of the cache."""
633         return len(self._mapping)
634
635     def __repr__(self):
636         return '<%s %r>' % (
637             self.__class__.__name__,
638             self._mapping
639         )
640
641     def __getitem__(self, key):
642         """Get an item from the cache. Moves the item up so that it has the
643         highest priority then.
644
645         Raise an `KeyError` if it does not exist.
646         """
647         rv = self._mapping[key]
648         if self._queue[-1] != key:
649             try:
650                 self._remove(key)
651             except ValueError:
652                 # if something removed the key from the container
653                 # when we read, ignore the ValueError that we would
654                 # get otherwise.
655                 pass
656             self._append(key)
657         return rv
658
659     def __setitem__(self, key, value):
660         """Sets the value for an item. Moves the item up so that it
661         has the highest priority then.
662         """
663         self._wlock.acquire()
664         try:
665             if key in self._mapping:
666                 try:
667                     self._remove(key)
668                 except ValueError:
669                     # __getitem__ is not locked, it might happen
670                     pass
671             elif len(self._mapping) == self.capacity:
672                 del self._mapping[self._popleft()]
673             self._append(key)
674             self._mapping[key] = value
675         finally:
676             self._wlock.release()
677
678     def __delitem__(self, key):
679         """Remove an item from the cache dict.
680         Raise an `KeyError` if it does not exist.
681         """
682         self._wlock.acquire()
683         try:
684             del self._mapping[key]
685             try:
686                 self._remove(key)
687             except ValueError:
688                 # __getitem__ is not locked, it might happen
689                 pass
690         finally:
691             self._wlock.release()
692
693     def items(self):
694         """Return a list of items."""
695         result = [(key, self._mapping[key]) for key in list(self._queue)]
696         result.reverse()
697         return result
698
699     def iteritems(self):
700         """Iterate over all items."""
701         return iter(self.items())
702
703     def values(self):
704         """Return a list of all values."""
705         return [x[1] for x in self.items()]
706
707     def itervalue(self):
708         """Iterate over all values."""
709         return iter(self.values())
710
711     def keys(self):
712         """Return a list of all keys ordered by most recent usage."""
713         return list(self)
714
715     def iterkeys(self):
716         """Iterate over all keys in the cache dict, ordered by
717         the most recent usage.
718         """
719         return reversed(tuple(self._queue))
720
721     __iter__ = iterkeys
722
723     def __reversed__(self):
724         """Iterate over the values in the cache dict, oldest items
725         coming first.
726         """
727         return iter(tuple(self._queue))
728
729     __copy__ = copy
730
731
732 # register the LRU cache as mutable mapping if possible
733 try:
734     from collections import MutableMapping
735     MutableMapping.register(LRUCache)
736 except ImportError:
737     pass
738
739
740 class Cycler(object):
741     """A cycle helper for templates."""
742
743     def __init__(self, *items):
744         if not items:
745             raise RuntimeError('at least one item has to be provided')
746         self.items = items
747         self.reset()
748
749     def reset(self):
750         """Resets the cycle."""
751         self.pos = 0
752
753     @property
754     def current(self):
755         """Returns the current item."""
756         return self.items[self.pos]
757
758     def next(self):
759         """Goes one item ahead and returns it."""
760         rv = self.current
761         self.pos = (self.pos + 1) % len(self.items)
762         return rv
763
764
765 class Joiner(object):
766     """A joining helper for templates."""
767
768     def __init__(self, sep=u', '):
769         self.sep = sep
770         self.used = False
771
772     def __call__(self):
773         if not self.used:
774             self.used = True
775             return u''
776         return self.sep
777
778
779 # we have to import it down here as the speedups module imports the
780 # markup type which is define above.
781 try:
782     from jinja2._speedups import escape, soft_unicode
783 except ImportError:
784     def escape(s):
785         """Convert the characters &, <, >, ' and " in string s to HTML-safe
786         sequences.  Use this if you need to display text that might contain
787         such characters in HTML.  Marks return value as markup string.
788         """
789         if hasattr(s, '__html__'):
790             return s.__html__()
791         return Markup(unicode(s)
792             .replace('&', '&amp;')
793             .replace('>', '&gt;')
794             .replace('<', '&lt;')
795             .replace("'", '&#39;')
796             .replace('"', '&#34;')
797         )
798
799     def soft_unicode(s):
800         """Make a string unicode if it isn't already.  That way a markup
801         string is not converted back to unicode.
802         """
803         if not isinstance(s, unicode):
804             s = unicode(s)
805         return s
806
807
808 # partials
809 try:
810     from functools import partial
811 except ImportError:
812     class partial(object):
813         def __init__(self, _func, *args, **kwargs):
814             self._func = _func
815             self._args = args
816             self._kwargs = kwargs
817         def __call__(self, *args, **kwargs):
818             kwargs.update(self._kwargs)
819             return self._func(*(self._args + args), **kwargs)