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"""
11import re
12import sys
13import errno
14try:
15    from thread import allocate_lock
16except ImportError:
17    from dummy_thread import allocate_lock
18from collections import deque
19from 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
36missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
37
38# internal code
39internal_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
46try:
47    def _test_gen_bug():
48        raise TypeError(_test_gen_bug)
49        yield None
50    _concat(_test_gen_bug())
51except 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.
68try:
69    next = next
70except 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
80if sys.version_info < (3, 0):
81    def _encode_filename(filename):
82        if isinstance(filename, unicode):
83            return filename.encode('utf-8')
84        return filename
85else:
86    def _encode_filename(filename):
87        assert filename is None or isinstance(filename, str), \
88            'filenames must be strings'
89        return filename
90
91from 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
97class _C(object):
98    def method(self): pass
99def _func():
100    yield None
101FunctionType = type(_func)
102GeneratorType = type(_func())
103MethodType = type(_C.method)
104CodeType = type(_C.method.func_code)
105try:
106    raise TypeError()
107except TypeError:
108    _tb = sys.exc_info()[2]
109    TracebackType = type(_tb)
110    FrameType = type(_tb.tb_frame)
111del _C, _tb, _func
112
113
114def 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
130def 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
143def 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
153def internalcode(f):
154    """Marks the function as internally used"""
155    internal_code.add(f.func_code)
156    return f
157
158
159def 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
175def consume(iterable):
176    """Consumes an iterable without doing anything with it."""
177    for event in iterable:
178        pass
179
180
181def 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
193def 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
219def 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
230def 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    if obj.__class__.__module__ == '__builtin__':
240        name = obj.__class__.__name__
241    else:
242        name = obj.__class__.__module__ + '.' + obj.__class__.__name__
243    return '%s object' % name
244
245
246def pformat(obj, verbose=False):
247    """Prettyprint an object.  Either use the `pretty` library or the
248    builtin `pprint`.
249    """
250    try:
251        from pretty import pretty
252        return pretty(obj, verbose=verbose)
253    except ImportError:
254        from pprint import pformat
255        return pformat(obj)
256
257
258def urlize(text, trim_url_limit=None, nofollow=False):
259    """Converts any URLs in text into clickable links. Works on http://,
260    https:// and www. links. Links can have trailing punctuation (periods,
261    commas, close-parens) and leading punctuation (opening parens) and
262    it'll still do the right thing.
263
264    If trim_url_limit is not None, the URLs in link text will be limited
265    to trim_url_limit characters.
266
267    If nofollow is True, the URLs in link text will get a rel="nofollow"
268    attribute.
269    """
270    trim_url = lambda x, limit=trim_url_limit: limit is not None \
271                         and (x[:limit] + (len(x) >=limit and '...'
272                         or '')) or x
273    words = _word_split_re.split(unicode(escape(text)))
274    nofollow_attr = nofollow and ' rel="nofollow"' or ''
275    for i, word in enumerate(words):
276        match = _punctuation_re.match(word)
277        if match:
278            lead, middle, trail = match.groups()
279            if middle.startswith('www.') or (
280                '@' not in middle and
281                not middle.startswith('http://') and
282                len(middle) > 0 and
283                middle[0] in _letters + _digits and (
284                    middle.endswith('.org') or
285                    middle.endswith('.net') or
286                    middle.endswith('.com')
287                )):
288                middle = '<a href="http://%s"%s>%s</a>' % (middle,
289                    nofollow_attr, trim_url(middle))
290            if middle.startswith('http://') or \
291               middle.startswith('https://'):
292                middle = '<a href="%s"%s>%s</a>' % (middle,
293                    nofollow_attr, trim_url(middle))
294            if '@' in middle and not middle.startswith('www.') and \
295               not ':' in middle and _simple_email_re.match(middle):
296                middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
297            if lead + middle + trail != word:
298                words[i] = lead + middle + trail
299    return u''.join(words)
300
301
302def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
303    """Generate some lorem impsum for the template."""
304    from jinja2.constants import LOREM_IPSUM_WORDS
305    from random import choice, randrange
306    words = LOREM_IPSUM_WORDS.split()
307    result = []
308
309    for _ in xrange(n):
310        next_capitalized = True
311        last_comma = last_fullstop = 0
312        word = None
313        last = None
314        p = []
315
316        # each paragraph contains out of 20 to 100 words.
317        for idx, _ in enumerate(xrange(randrange(min, max))):
318            while True:
319                word = choice(words)
320                if word != last:
321                    last = word
322                    break
323            if next_capitalized:
324                word = word.capitalize()
325                next_capitalized = False
326            # add commas
327            if idx - randrange(3, 8) > last_comma:
328                last_comma = idx
329                last_fullstop += 2
330                word += ','
331            # add end of sentences
332            if idx - randrange(10, 20) > last_fullstop:
333                last_comma = last_fullstop = idx
334                word += '.'
335                next_capitalized = True
336            p.append(word)
337
338        # ensure that the paragraph ends with a dot.
339        p = u' '.join(p)
340        if p.endswith(','):
341            p = p[:-1] + '.'
342        elif not p.endswith('.'):
343            p += '.'
344        result.append(p)
345
346    if not html:
347        return u'\n\n'.join(result)
348    return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
349
350
351class Markup(unicode):
352    r"""Marks a string as being safe for inclusion in HTML/XML output without
353    needing to be escaped.  This implements the `__html__` interface a couple
354    of frameworks and web applications use.  :class:`Markup` is a direct
355    subclass of `unicode` and provides all the methods of `unicode` just that
356    it escapes arguments passed and always returns `Markup`.
357
358    The `escape` function returns markup objects so that double escaping can't
359    happen.  If you want to use autoescaping in Jinja just enable the
360    autoescaping feature in the environment.
361
362    The constructor of the :class:`Markup` class can be used for three
363    different things:  When passed an unicode object it's assumed to be safe,
364    when passed an object with an HTML representation (has an `__html__`
365    method) that representation is used, otherwise the object passed is
366    converted into a unicode string and then assumed to be safe:
367
368    >>> Markup("Hello <em>World</em>!")
369    Markup(u'Hello <em>World</em>!')
370    >>> class Foo(object):
371    ...  def __html__(self):
372    ...   return '<a href="#">foo</a>'
373    ...
374    >>> Markup(Foo())
375    Markup(u'<a href="#">foo</a>')
376
377    If you want object passed being always treated as unsafe you can use the
378    :meth:`escape` classmethod to create a :class:`Markup` object:
379
380    >>> Markup.escape("Hello <em>World</em>!")
381    Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
382
383    Operations on a markup string are markup aware which means that all
384    arguments are passed through the :func:`escape` function:
385
386    >>> em = Markup("<em>%s</em>")
387    >>> em % "foo & bar"
388    Markup(u'<em>foo &amp; bar</em>')
389    >>> strong = Markup("<strong>%(text)s</strong>")
390    >>> strong % {'text': '<blink>hacker here</blink>'}
391    Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
392    >>> Markup("<em>Hello</em> ") + "<foo>"
393    Markup(u'<em>Hello</em> &lt;foo&gt;')
394    """
395    __slots__ = ()
396
397    def __new__(cls, base=u'', encoding=None, errors='strict'):
398        if hasattr(base, '__html__'):
399            base = base.__html__()
400        if encoding is None:
401            return unicode.__new__(cls, base)
402        return unicode.__new__(cls, base, encoding, errors)
403
404    def __html__(self):
405        return self
406
407    def __add__(self, other):
408        if hasattr(other, '__html__') or isinstance(other, basestring):
409            return self.__class__(unicode(self) + unicode(escape(other)))
410        return NotImplemented
411
412    def __radd__(self, other):
413        if hasattr(other, '__html__') or isinstance(other, basestring):
414            return self.__class__(unicode(escape(other)) + unicode(self))
415        return NotImplemented
416
417    def __mul__(self, num):
418        if isinstance(num, (int, long)):
419            return self.__class__(unicode.__mul__(self, num))
420        return NotImplemented
421    __rmul__ = __mul__
422
423    def __mod__(self, arg):
424        if isinstance(arg, tuple):
425            arg = tuple(imap(_MarkupEscapeHelper, arg))
426        else:
427            arg = _MarkupEscapeHelper(arg)
428        return self.__class__(unicode.__mod__(self, arg))
429
430    def __repr__(self):
431        return '%s(%s)' % (
432            self.__class__.__name__,
433            unicode.__repr__(self)
434        )
435
436    def join(self, seq):
437        return self.__class__(unicode.join(self, imap(escape, seq)))
438    join.__doc__ = unicode.join.__doc__
439
440    def split(self, *args, **kwargs):
441        return map(self.__class__, unicode.split(self, *args, **kwargs))
442    split.__doc__ = unicode.split.__doc__
443
444    def rsplit(self, *args, **kwargs):
445        return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
446    rsplit.__doc__ = unicode.rsplit.__doc__
447
448    def splitlines(self, *args, **kwargs):
449        return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
450    splitlines.__doc__ = unicode.splitlines.__doc__
451
452    def unescape(self):
453        r"""Unescape markup again into an unicode string.  This also resolves
454        known HTML4 and XHTML entities:
455
456        >>> Markup("Main &raquo; <em>About</em>").unescape()
457        u'Main \xbb <em>About</em>'
458        """
459        from jinja2.constants import HTML_ENTITIES
460        def handle_match(m):
461            name = m.group(1)
462            if name in HTML_ENTITIES:
463                return unichr(HTML_ENTITIES[name])
464            try:
465                if name[:2] in ('#x', '#X'):
466                    return unichr(int(name[2:], 16))
467                elif name.startswith('#'):
468                    return unichr(int(name[1:]))
469            except ValueError:
470                pass
471            return u''
472        return _entity_re.sub(handle_match, unicode(self))
473
474    def striptags(self):
475        r"""Unescape markup into an unicode string and strip all tags.  This
476        also resolves known HTML4 and XHTML entities.  Whitespace is
477        normalized to one:
478
479        >>> Markup("Main &raquo;  <em>About</em>").striptags()
480        u'Main \xbb About'
481        """
482        stripped = u' '.join(_striptags_re.sub('', self).split())
483        return Markup(stripped).unescape()
484
485    @classmethod
486    def escape(cls, s):
487        """Escape the string.  Works like :func:`escape` with the difference
488        that for subclasses of :class:`Markup` this function would return the
489        correct subclass.
490        """
491        rv = escape(s)
492        if rv.__class__ is not cls:
493            return cls(rv)
494        return rv
495
496    def make_wrapper(name):
497        orig = getattr(unicode, name)
498        def func(self, *args, **kwargs):
499            args = _escape_argspec(list(args), enumerate(args))
500            _escape_argspec(kwargs, kwargs.iteritems())
501            return self.__class__(orig(self, *args, **kwargs))
502        func.__name__ = orig.__name__
503        func.__doc__ = orig.__doc__
504        return func
505
506    for method in '__getitem__', 'capitalize', \
507                  'title', 'lower', 'upper', 'replace', 'ljust', \
508                  'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
509                  'translate', 'expandtabs', 'swapcase', 'zfill':
510        locals()[method] = make_wrapper(method)
511
512    # new in python 2.5
513    if hasattr(unicode, 'partition'):
514        partition = make_wrapper('partition'),
515        rpartition = make_wrapper('rpartition')
516
517    # new in python 2.6
518    if hasattr(unicode, 'format'):
519        format = make_wrapper('format')
520
521    # not in python 3
522    if hasattr(unicode, '__getslice__'):
523        __getslice__ = make_wrapper('__getslice__')
524
525    del method, make_wrapper
526
527
528def _escape_argspec(obj, iterable):
529    """Helper for various string-wrapped functions."""
530    for key, value in iterable:
531        if hasattr(value, '__html__') or isinstance(value, basestring):
532            obj[key] = escape(value)
533    return obj
534
535
536class _MarkupEscapeHelper(object):
537    """Helper for Markup.__mod__"""
538
539    def __init__(self, obj):
540        self.obj = obj
541
542    __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
543    __str__ = lambda s: str(escape(s.obj))
544    __unicode__ = lambda s: unicode(escape(s.obj))
545    __repr__ = lambda s: str(escape(repr(s.obj)))
546    __int__ = lambda s: int(s.obj)
547    __float__ = lambda s: float(s.obj)
548
549
550class LRUCache(object):
551    """A simple LRU Cache implementation."""
552
553    # this is fast for small capacities (something below 1000) but doesn't
554    # scale.  But as long as it's only used as storage for templates this
555    # won't do any harm.
556
557    def __init__(self, capacity):
558        self.capacity = capacity
559        self._mapping = {}
560        self._queue = deque()
561        self._postinit()
562
563    def _postinit(self):
564        # alias all queue methods for faster lookup
565        self._popleft = self._queue.popleft
566        self._pop = self._queue.pop
567        if hasattr(self._queue, 'remove'):
568            self._remove = self._queue.remove
569        self._wlock = allocate_lock()
570        self._append = self._queue.append
571
572    def _remove(self, obj):
573        """Python 2.4 compatibility."""
574        for idx, item in enumerate(self._queue):
575            if item == obj:
576                del self._queue[idx]
577                break
578
579    def __getstate__(self):
580        return {
581            'capacity':     self.capacity,
582            '_mapping':     self._mapping,
583            '_queue':       self._queue
584        }
585
586    def __setstate__(self, d):
587        self.__dict__.update(d)
588        self._postinit()
589
590    def __getnewargs__(self):
591        return (self.capacity,)
592
593    def copy(self):
594        """Return an shallow copy of the instance."""
595        rv = self.__class__(self.capacity)
596        rv._mapping.update(self._mapping)
597        rv._queue = deque(self._queue)
598        return rv
599
600    def get(self, key, default=None):
601        """Return an item from the cache dict or `default`"""
602        try:
603            return self[key]
604        except KeyError:
605            return default
606
607    def setdefault(self, key, default=None):
608        """Set `default` if the key is not in the cache otherwise
609        leave unchanged. Return the value of this key.
610        """
611        try:
612            return self[key]
613        except KeyError:
614            self[key] = default
615            return default
616
617    def clear(self):
618        """Clear the cache."""
619        self._wlock.acquire()
620        try:
621            self._mapping.clear()
622            self._queue.clear()
623        finally:
624            self._wlock.release()
625
626    def __contains__(self, key):
627        """Check if a key exists in this cache."""
628        return key in self._mapping
629
630    def __len__(self):
631        """Return the current size of the cache."""
632        return len(self._mapping)
633
634    def __repr__(self):
635        return '<%s %r>' % (
636            self.__class__.__name__,
637            self._mapping
638        )
639
640    def __getitem__(self, key):
641        """Get an item from the cache. Moves the item up so that it has the
642        highest priority then.
643
644        Raise an `KeyError` if it does not exist.
645        """
646        rv = self._mapping[key]
647        if self._queue[-1] != key:
648            try:
649                self._remove(key)
650            except ValueError:
651                # if something removed the key from the container
652                # when we read, ignore the ValueError that we would
653                # get otherwise.
654                pass
655            self._append(key)
656        return rv
657
658    def __setitem__(self, key, value):
659        """Sets the value for an item. Moves the item up so that it
660        has the highest priority then.
661        """
662        self._wlock.acquire()
663        try:
664            if key in self._mapping:
665                try:
666                    self._remove(key)
667                except ValueError:
668                    # __getitem__ is not locked, it might happen
669                    pass
670            elif len(self._mapping) == self.capacity:
671                del self._mapping[self._popleft()]
672            self._append(key)
673            self._mapping[key] = value
674        finally:
675            self._wlock.release()
676
677    def __delitem__(self, key):
678        """Remove an item from the cache dict.
679        Raise an `KeyError` if it does not exist.
680        """
681        self._wlock.acquire()
682        try:
683            del self._mapping[key]
684            try:
685                self._remove(key)
686            except ValueError:
687                # __getitem__ is not locked, it might happen
688                pass
689        finally:
690            self._wlock.release()
691
692    def items(self):
693        """Return a list of items."""
694        result = [(key, self._mapping[key]) for key in list(self._queue)]
695        result.reverse()
696        return result
697
698    def iteritems(self):
699        """Iterate over all items."""
700        return iter(self.items())
701
702    def values(self):
703        """Return a list of all values."""
704        return [x[1] for x in self.items()]
705
706    def itervalue(self):
707        """Iterate over all values."""
708        return iter(self.values())
709
710    def keys(self):
711        """Return a list of all keys ordered by most recent usage."""
712        return list(self)
713
714    def iterkeys(self):
715        """Iterate over all keys in the cache dict, ordered by
716        the most recent usage.
717        """
718        return reversed(tuple(self._queue))
719
720    __iter__ = iterkeys
721
722    def __reversed__(self):
723        """Iterate over the values in the cache dict, oldest items
724        coming first.
725        """
726        return iter(tuple(self._queue))
727
728    __copy__ = copy
729
730
731# register the LRU cache as mutable mapping if possible
732try:
733    from collections import MutableMapping
734    MutableMapping.register(LRUCache)
735except ImportError:
736    pass
737
738
739class Cycler(object):
740    """A cycle helper for templates."""
741
742    def __init__(self, *items):
743        if not items:
744            raise RuntimeError('at least one item has to be provided')
745        self.items = items
746        self.reset()
747
748    def reset(self):
749        """Resets the cycle."""
750        self.pos = 0
751
752    @property
753    def current(self):
754        """Returns the current item."""
755        return self.items[self.pos]
756
757    def next(self):
758        """Goes one item ahead and returns it."""
759        rv = self.current
760        self.pos = (self.pos + 1) % len(self.items)
761        return rv
762
763
764class Joiner(object):
765    """A joining helper for templates."""
766
767    def __init__(self, sep=u', '):
768        self.sep = sep
769        self.used = False
770
771    def __call__(self):
772        if not self.used:
773            self.used = True
774            return u''
775        return self.sep
776
777
778# we have to import it down here as the speedups module imports the
779# markup type which is define above.
780try:
781    from jinja2._speedups import escape, soft_unicode
782except ImportError:
783    def escape(s):
784        """Convert the characters &, <, >, ' and " in string s to HTML-safe
785        sequences.  Use this if you need to display text that might contain
786        such characters in HTML.  Marks return value as markup string.
787        """
788        if hasattr(s, '__html__'):
789            return s.__html__()
790        return Markup(unicode(s)
791            .replace('&', '&amp;')
792            .replace('>', '&gt;')
793            .replace('<', '&lt;')
794            .replace("'", '&#39;')
795            .replace('"', '&#34;')
796        )
797
798    def soft_unicode(s):
799        """Make a string unicode if it isn't already.  That way a markup
800        string is not converted back to unicode.
801        """
802        if not isinstance(s, unicode):
803            s = unicode(s)
804        return s
805
806
807# partials
808try:
809    from functools import partial
810except ImportError:
811    class partial(object):
812        def __init__(self, _func, *args, **kwargs):
813            self._func = _func
814            self._args = args
815            self._kwargs = kwargs
816        def __call__(self, *args, **kwargs):
817            kwargs.update(self._kwargs)
818            return self._func(*(self._args + args), **kwargs)
819