added autoescaping

--HG--
branch : trunk
diff --git a/jinja2/utils.py b/jinja2/utils.py
index b597ed0..af1066c 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -10,17 +10,20 @@
 """
 import re
 import string
+from functools import update_wrapper
+from itertools import imap
 
 
 def escape(obj, attribute=False):
     """HTML escape an object."""
     if hasattr(obj, '__html__'):
         return obj.__html__()
-    return unicode(obj) \
-        .replace('&', '&') \
-        .replace('>', '>') \
-        .replace('<', '&lt;') \
+    return Markup(unicode(obj)
+        .replace('&', '&amp;')
+        .replace('>', '&gt;')
+        .replace('<', '&lt;')
         .replace('"', '&quot;')
+    )
 
 
 def pformat(obj, verbose=False):
@@ -39,9 +42,9 @@
 _word_split_re = re.compile(r'(\s+)')
 
 _punctuation_re = re.compile(
-    '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' %  (
-        '|'.join([re.escape(p) for p in ('(', '<', '&lt;')]),
-        '|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '&gt;')])
+    '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
+        '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
+        '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
     )
 )
 
@@ -91,3 +94,94 @@
             if lead + middle + trail != word:
                 words[i] = lead + middle + trail
     return u''.join(words)
+
+
+class Markup(unicode):
+    """Marks a string as being safe for inclusion in HTML/XML output without
+    needing to be escaped.  This implements the `__html__` interface a couple
+    of frameworks and web applications use.
+
+    The `escape` function returns markup objects so that double escaping can't
+    happen.  If you want to use autoescaping in Jinja just set the finalizer
+    of the environment to `escape`.
+    """
+
+    __slots__ = ()
+
+    def __html__(self):
+        return self
+
+    def __add__(self, other):
+        if hasattr(other, '__html__') or isinstance(other, basestring):
+            return self.__class__(unicode(self) + unicode(escape(other)))
+        return NotImplemented
+
+    def __radd__(self, other):
+        if hasattr(other, '__html__') or isinstance(other, basestring):
+            return self.__class__(unicode(escape(other)) + unicode(self))
+        return NotImplemented
+
+    def __mul__(self, num):
+        if not isinstance(num, (int, long)):
+            return NotImplemented
+        return self.__class__(unicode.__mul__(self, num))
+    __rmul__ = __mul__
+
+    def __mod__(self, arg):
+        if isinstance(arg, tuple):
+            arg = tuple(imap(_MarkupEscapeHelper, arg))
+        else:
+            arg = _MarkupEscapeHelper(arg)
+        return self.__class__(unicode.__mod__(self, arg))
+
+    def __repr__(self):
+        return '%s(%s)' % (
+            self.__class__.__name__,
+            unicode.__repr__(self)
+        )
+
+    def join(self, seq):
+        return self.__class__(unicode.join(self, imap(escape, seq)))
+
+    def split(self, *args, **kwargs):
+        return map(self.__class__, unicode.split(self, *args, **kwargs))
+
+    def rsplit(self, *args, **kwargs):
+        return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
+
+    def splitlines(self, *args, **kwargs):
+        return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
+
+    def make_wrapper(name):
+        orig = getattr(unicode, name)
+        def func(self, *args, **kwargs):
+            args = list(args)
+            for idx, arg in enumerate(args):
+                if hasattr(arg, '__html__') or isinstance(arg, basestring):
+                    args[idx] = escape(arg)
+            for name, arg in kwargs.iteritems():
+                if hasattr(arg, '__html__') or isinstance(arg, basestring):
+                    kwargs[name] = escape(arg)
+            return self.__class__(orig(self, *args, **kwargs))
+        return update_wrapper(func, orig, ('__name__', '__doc__'))
+    for method in '__getitem__', '__getslice__', 'capitalize', \
+                  'title', 'lower', 'upper', 'replace', 'ljust', \
+                  'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
+                  'strip', 'translate', 'expandtabs', 'rpartition', \
+                  'swapcase', 'zfill':
+        locals()[method] = make_wrapper(method)
+    del method, make_wrapper
+
+
+class _MarkupEscapeHelper(object):
+    """Helper for Markup.__mod__"""
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
+    __unicode__ = lambda s: unicode(escape(s.obj))
+    __str__ = lambda s: str(escape(s.obj))
+    __repr__ = lambda s: str(repr(escape(s.obj)))
+    __int__ = lambda s: int(s.obj)
+    __float__ = lambda s: float(s.obj)