python 3 port: manual fixes, remove 2to3 from setup.py, remove fixers
diff --git a/custom_fixers/__init__.py b/custom_fixers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/custom_fixers/__init__.py
+++ /dev/null
diff --git a/custom_fixers/fix_alt_unicode.py b/custom_fixers/fix_alt_unicode.py
deleted file mode 100644
index 96a81c1..0000000
--- a/custom_fixers/fix_alt_unicode.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from lib2to3 import fixer_base
-from lib2to3.fixer_util import Name, BlankLine
-
-
-class FixAltUnicode(fixer_base.BaseFix):
-    PATTERN = """
-    func=funcdef< 'def' name='__unicode__'
-                  parameters< '(' NAME ')' > any+ >
-    """
-
-    def transform(self, node, results):
-        name = results['name']
-        name.replace(Name('__str__', prefix=name.prefix))
diff --git a/custom_fixers/fix_broken_reraising.py b/custom_fixers/fix_broken_reraising.py
deleted file mode 100644
index fd0ea68..0000000
--- a/custom_fixers/fix_broken_reraising.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from lib2to3 import fixer_base, pytree
-from lib2to3.fixer_util import Name, BlankLine, Name, Attr, ArgList
-
-
-class FixBrokenReraising(fixer_base.BaseFix):
-    PATTERN = """
-    raise_stmt< 'raise' any ',' val=any ',' tb=any >
-    """
-
-    # run before the broken 2to3 checker with the same goal
-    # tries to rewrite it with a rule that does not work out for jinja
-    run_order = 1
-
-    def transform(self, node, results):
-        tb = results['tb'].clone()
-        tb.prefix = ''
-        with_tb = Attr(results['val'].clone(), Name('with_traceback')) + \
-                  [ArgList([tb])]
-        new = pytree.Node(self.syms.simple_stmt, [Name("raise")] + with_tb)
-        new.prefix = node.prefix
-        return new
diff --git a/custom_fixers/fix_xrange2.py b/custom_fixers/fix_xrange2.py
deleted file mode 100644
index 5d35e50..0000000
--- a/custom_fixers/fix_xrange2.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from lib2to3 import fixer_base
-from lib2to3.fixer_util import Name, BlankLine
-
-
-# whyever this is necessary..
-
-class FixXrange2(fixer_base.BaseFix):
-    PATTERN = "'xrange'"
-
-    def transform(self, node, results):
-        node.replace(Name('range', prefix=node.prefix))
diff --git a/jinja2/_markupsafe/__init__.py b/jinja2/_markupsafe/__init__.py
index 6aae11a..a353ff3 100644
--- a/jinja2/_markupsafe/__init__.py
+++ b/jinja2/_markupsafe/__init__.py
@@ -9,11 +9,13 @@
     :license: BSD, see LICENSE for more details.
 """
 import re
-from itertools import imap
 import six
 from six.moves import map
 from six.moves import zip
-
+try:
+    unichr = unichr  # py2
+except NameError:
+    unichr = chr  # py3
 
 __all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
 
@@ -22,7 +24,7 @@
 _entity_re = re.compile(r'&([^;]+);')
 
 
-class Markup(unicode):
+class Markup(six.text_type):
     r"""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.  :class:`Markup` is a direct
@@ -71,56 +73,56 @@
         if hasattr(base, '__html__'):
             base = base.__html__()
         if encoding is None:
-            return unicode.__new__(cls, base)
-        return unicode.__new__(cls, base, encoding, errors)
+            return six.text_type.__new__(cls, base)
+        return six.text_type.__new__(cls, base, encoding, errors)
 
     def __html__(self):
         return self
 
     def __add__(self, other):
-        if hasattr(other, '__html__') or isinstance(other, basestring):
+        if hasattr(other, '__html__') or isinstance(other, six.string_types):
             return self.__class__(six.text_type(self) + six.text_type(escape(other)))
         return NotImplemented
 
     def __radd__(self, other):
-        if hasattr(other, '__html__') or isinstance(other, basestring):
+        if hasattr(other, '__html__') or isinstance(other, six.string_types):
             return self.__class__(six.text_type(escape(other)) + six.text_type(self))
         return NotImplemented
 
     def __mul__(self, num):
         if isinstance(num, (int, long)):
-            return self.__class__(unicode.__mul__(self, num))
+            return self.__class__(six.text_type.__mul__(self, num))
         return NotImplemented
     __rmul__ = __mul__
 
     def __mod__(self, arg):
         if isinstance(arg, tuple):
-            arg = tuple(imap(_MarkupEscapeHelper, arg))
+            arg = tuple(map(_MarkupEscapeHelper, arg))
         else:
             arg = _MarkupEscapeHelper(arg)
-        return self.__class__(unicode.__mod__(self, arg))
+        return self.__class__(six.text_type.__mod__(self, arg))
 
     def __repr__(self):
         return '%s(%s)' % (
             self.__class__.__name__,
-            unicode.__repr__(self)
+            six.text_type.__repr__(self)
         )
 
     def join(self, seq):
-        return self.__class__(unicode.join(self, imap(escape, seq)))
-    join.__doc__ = unicode.join.__doc__
+        return self.__class__(six.text_type.join(self, map(escape, seq)))
+    join.__doc__ = six.text_type.join.__doc__
 
     def split(self, *args, **kwargs):
-        return map(self.__class__, unicode.split(self, *args, **kwargs))
-    split.__doc__ = unicode.split.__doc__
+        return map(self.__class__, six.text_type.split(self, *args, **kwargs))
+    split.__doc__ = six.text_type.split.__doc__
 
     def rsplit(self, *args, **kwargs):
-        return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
-    rsplit.__doc__ = unicode.rsplit.__doc__
+        return map(self.__class__, six.text_type.rsplit(self, *args, **kwargs))
+    rsplit.__doc__ = six.text_type.rsplit.__doc__
 
     def splitlines(self, *args, **kwargs):
-        return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
-    splitlines.__doc__ = unicode.splitlines.__doc__
+        return map(self.__class__, six.text_type.splitlines(self, *args, **kwargs))
+    splitlines.__doc__ = six.text_type.splitlines.__doc__
 
     def unescape(self):
         r"""Unescape markup again into an unicode string.  This also resolves
@@ -167,7 +169,7 @@
         return rv
 
     def make_wrapper(name):
-        orig = getattr(unicode, name)
+        orig = getattr(six.text_type, name)
         def func(self, *args, **kwargs):
             args = _escape_argspec(list(args), enumerate(args))
             _escape_argspec(kwargs, six.iteritems(kwargs))
@@ -183,16 +185,16 @@
         locals()[method] = make_wrapper(method)
 
     # new in python 2.5
-    if hasattr(unicode, 'partition'):
+    if hasattr(six.text_type, 'partition'):
         partition = make_wrapper('partition'),
         rpartition = make_wrapper('rpartition')
 
     # new in python 2.6
-    if hasattr(unicode, 'format'):
+    if hasattr(six.text_type, 'format'):
         format = make_wrapper('format')
 
     # not in python 3
-    if hasattr(unicode, '__getslice__'):
+    if hasattr(six.text_type, '__getslice__'):
         __getslice__ = make_wrapper('__getslice__')
 
     del method, make_wrapper
@@ -201,7 +203,7 @@
 def _escape_argspec(obj, iterable):
     """Helper for various string-wrapped functions."""
     for key, value in iterable:
-        if hasattr(value, '__html__') or isinstance(value, basestring):
+        if hasattr(value, '__html__') or isinstance(value, six.string_types):
             obj[key] = escape(value)
     return obj
 
diff --git a/jinja2/_markupsafe/_native.py b/jinja2/_markupsafe/_native.py
index 97065bb..389be74 100644
--- a/jinja2/_markupsafe/_native.py
+++ b/jinja2/_markupsafe/_native.py
@@ -41,6 +41,6 @@
     """Make a string unicode if it isn't already.  That way a markup
     string is not converted back to unicode.
     """
-    if not isinstance(s, unicode):
+    if not isinstance(s, six.text_type):
         s = six.text_type(s)
     return s
diff --git a/jinja2/bccache.py b/jinja2/bccache.py
index 0b0ccad..7c91188 100644
--- a/jinja2/bccache.py
+++ b/jinja2/bccache.py
@@ -18,7 +18,7 @@
 import sys
 import marshal
 import tempfile
-import cPickle as pickle
+from six.moves import cPickle as pickle
 import fnmatch
 try:
     from hashlib import sha1
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 35f7d02..4ee39f8 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -8,17 +8,16 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
-from cStringIO import StringIO
 from itertools import chain
 from copy import deepcopy
 from jinja2 import nodes
 from jinja2.nodes import EvalContext
 from jinja2.visitor import NodeVisitor
 from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat, escape, is_python_keyword, next
+from jinja2.utils import Markup, concat, escape, is_python_keyword
 import six
-from six.moves import map
-from six.moves import zip
+from six.moves import cStringIO as StringIO
+from six.moves import map, zip
 
 
 operators = {
@@ -72,8 +71,11 @@
     """Does the node have a safe representation?"""
     if value is None or value is NotImplemented or value is Ellipsis:
         return True
-    if isinstance(value, (bool, int, long, float, complex, basestring,
-                          xrange, Markup)):
+    try:
+        range_type = xrange
+    except NameError:
+        range_type = range
+    if isinstance(value, (bool, int, float, complex, range_type, Markup) + six.string_types):
         return True
     if isinstance(value, (tuple, list, set, frozenset)):
         for item in value:
@@ -933,7 +935,7 @@
 
         func_name = 'get_or_select_template'
         if isinstance(node.template, nodes.Const):
-            if isinstance(node.template.value, basestring):
+            if isinstance(node.template.value, six.string_types):
                 func_name = 'get_template'
             elif isinstance(node.template.value, (tuple, list)):
                 func_name = 'select_template'
@@ -1221,7 +1223,7 @@
         if self.environment.finalize:
             finalize = lambda x: six.text_type(self.environment.finalize(x))
         else:
-            finalize = unicode
+            finalize = six.text_type
 
         # if we are inside a frame that requires output checking, we do so
         outdent_later = False
@@ -1355,7 +1357,7 @@
             public_names = [x for x in assignment_frame.toplevel_assignments
                             if not x.startswith('_')]
             if len(assignment_frame.toplevel_assignments) == 1:
-                name = next(iter(assignment_frame.toplevel_assignments))
+                name = six.advance_iterator(iter(assignment_frame.toplevel_assignments))
                 self.writeline('context.vars[%r] = l_%s' % (name, name))
             else:
                 self.writeline('context.vars.update({')
diff --git a/jinja2/debug.py b/jinja2/debug.py
index c653e36..55c12fe 100644
--- a/jinja2/debug.py
+++ b/jinja2/debug.py
@@ -190,7 +190,7 @@
     # reraise it unchanged.
     # XXX: can we backup here?  when could this happen?
     if not frames:
-        raise exc_info[0], exc_info[1], exc_info[2]
+        six.reraise(exc_info[0], exc_info[1], exc_info[2])
 
     return ProcessedTraceback(exc_info[0], exc_info[1], frames)
 
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
index d2d4544..97b17ee 100644
--- a/jinja2/defaults.py
+++ b/jinja2/defaults.py
@@ -8,6 +8,7 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
+from six.moves import xrange
 from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
 
 
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 71f022f..a8149c7 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -23,9 +23,7 @@
      concat, consume, internalcode, _encode_filename
 import six
 from functools import reduce
-from six.moves import filter
-from six.moves import map
-from six.moves import zip
+from six.moves import filter, map, zip
 
 
 # for direct template usage we have up to ten living environments
@@ -76,7 +74,7 @@
     """
     result = {}
     for extension in extensions:
-        if isinstance(extension, basestring):
+        if isinstance(extension, six.string_types):
             extension = import_string(extension)
         result[extension.identifier] = extension(environment)
     return result
@@ -357,7 +355,7 @@
         try:
             return obj[argument]
         except (TypeError, LookupError):
-            if isinstance(argument, basestring):
+            if isinstance(argument, six.string_types):
                 try:
                     attr = str(argument)
                 except Exception:
@@ -479,7 +477,7 @@
         """
         source_hint = None
         try:
-            if isinstance(source, basestring):
+            if isinstance(source, six.string_types):
                 source_hint = source
                 source = self._parse(source, name, filename)
             if self.optimized:
@@ -676,7 +674,7 @@
         if self.exception_handler is not None:
             self.exception_handler(traceback)
         exc_type, exc_value, tb = traceback.standard_exc_info
-        raise exc_type, exc_value, tb
+        six.reraise(exc_type, exc_value, tb)
 
     def join_path(self, template, parent):
         """Join a template with the parent.  By default all the lookups are
@@ -763,7 +761,7 @@
 
         .. versionadded:: 2.3
         """
-        if isinstance(template_name_or_list, basestring):
+        if isinstance(template_name_or_list, six.string_types):
             return self.get_template(template_name_or_list, parent, globals)
         elif isinstance(template_name_or_list, Template):
             return template_name_or_list
@@ -1008,12 +1006,9 @@
         return Markup(concat(self._body_stream))
 
     def __str__(self):
-        return six.text_type(self).encode('utf-8')
+        s = self.__unicode__()
+        return s if six.PY3 else s.encode('utf-8')
 
-    # unicode goes after __str__ because we configured 2to3 to rename
-    # __unicode__ to __str__.  because the 2to3 tree is not designed to
-    # remove nodes from it, we leave the above __str__ around and let
-    # it override at runtime.
     def __unicode__(self):
         return concat(self._body_stream)
 
@@ -1044,7 +1039,7 @@
         return rv
 
 
-class TemplateStream(object):
+class TemplateStream(six.Iterator):
     """A template stream works pretty much like an ordinary python generator
     but it can buffer multiple items to reduce the number of total iterations.
     Per default the output is unbuffered which means that for every unbuffered
@@ -1069,7 +1064,7 @@
             Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
         """
         close = False
-        if isinstance(fp, basestring):
+        if isinstance(fp, six.string_types):
             fp = file(fp, 'w')
             close = True
         try:
@@ -1088,7 +1083,7 @@
 
     def disable_buffering(self):
         """Disable the output buffering."""
-        self._next = self._gen.next
+        self._next = lambda: six.next(self._gen)
         self.buffered = False
 
     def enable_buffering(self, size=5):
@@ -1116,12 +1111,12 @@
                 c_size = 0
 
         self.buffered = True
-        self._next = generator(self._gen.next).next
+        self._next = lambda: six.next(generator(lambda: six.next(self._gen)))
 
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         return self._next()
 
 
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
index 9002521..f38d347 100644
--- a/jinja2/exceptions.py
+++ b/jinja2/exceptions.py
@@ -9,8 +9,7 @@
     :license: BSD, see LICENSE for more details.
 """
 import six
-from six.moves import map
-from six.moves import zip
+from six.moves import map, zip
 
 
 class TemplateError(Exception):
@@ -66,7 +65,7 @@
     def __init__(self, names=(), message=None):
         if message is None:
             message = u'none of the templates given were found: ' + \
-                      u', '.join(map(unicode, names))
+                      u', '.join(map(six.text_type, names))
         TemplateNotFound.__init__(self, names and names[-1] or None, message)
         self.templates = list(names)
 
@@ -86,12 +85,9 @@
         self.translated = False
 
     def __str__(self):
-        return six.text_type(self).encode('utf-8')
+        s = self.__unicode__()
+        return s if six.PY3 else s.encode('utf-8')
 
-    # unicode goes after __str__ because we configured 2to3 to rename
-    # __unicode__ to __str__.  because the 2to3 tree is not designed to
-    # remove nodes from it, we leave the above __str__ around and let
-    # it override at runtime.
     def __unicode__(self):
         # for translated errors we only return the message
         if self.translated:
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 984a4d9..0652a34 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -16,7 +16,7 @@
 from jinja2.environment import Environment
 from jinja2.runtime import Undefined, concat
 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
-from jinja2.utils import contextfunction, import_string, Markup, next
+from jinja2.utils import contextfunction, import_string, Markup
 import six
 
 
@@ -35,7 +35,7 @@
         return rv
 
 
-class Extension(object):
+class Extension(six.with_metaclass(ExtensionRegistry, object)):
     """Extensions can be used to add extra functionality to the Jinja template
     system at the parser level.  Custom extensions are bound to an environment
     but may not store environment specific data on `self`.  The reason for
@@ -206,13 +206,13 @@
             self.environment.globals.pop(key, None)
 
     def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
-        if isinstance(source, basestring):
+        if isinstance(source, six.string_types):
             source = self.environment.parse(source)
         return extract_from_ast(source, gettext_functions)
 
     def parse(self, parser):
         """Parse a translatable tag."""
-        lineno = next(parser.stream).lineno
+        lineno = six.advance_iterator(parser.stream).lineno
         num_called_num = False
 
         # find all the variables referenced.  Additionally a variable can be
@@ -236,7 +236,7 @@
 
             # expressions
             if parser.stream.current.type == 'assign':
-                next(parser.stream)
+                six.advance_iterator(parser.stream)
                 variables[name.value] = var = parser.parse_expression()
             else:
                 variables[name.value] = var = nodes.Name(name.value, 'load')
@@ -262,7 +262,7 @@
         # if we have a pluralize block, we parse that too
         if parser.stream.current.test('name:pluralize'):
             have_plural = True
-            next(parser.stream)
+            six.advance_iterator(parser.stream)
             if parser.stream.current.type != 'block_end':
                 name = parser.stream.expect('name')
                 if name.value not in variables:
@@ -273,10 +273,10 @@
                 num_called_num = name.value == 'num'
             parser.stream.expect('block_end')
             plural_names, plural = self._parse_block(parser, False)
-            next(parser.stream)
+            six.advance_iterator(parser.stream)
             referenced.update(plural_names)
         else:
-            next(parser.stream)
+            six.advance_iterator(parser.stream)
 
         # register free names as simple name expressions
         for var in referenced:
@@ -301,15 +301,15 @@
         while 1:
             if parser.stream.current.type == 'data':
                 buf.append(parser.stream.current.value.replace('%', '%%'))
-                next(parser.stream)
+                six.advance_iterator(parser.stream)
             elif parser.stream.current.type == 'variable_begin':
-                next(parser.stream)
+                six.advance_iterator(parser.stream)
                 name = parser.stream.expect('name').value
                 referenced.append(name)
                 buf.append('%%(%s)s' % name)
                 parser.stream.expect('variable_end')
             elif parser.stream.current.type == 'block_begin':
-                next(parser.stream)
+                six.advance_iterator(parser.stream)
                 if parser.stream.current.test('name:endtrans'):
                     break
                 elif parser.stream.current.test('name:pluralize'):
@@ -382,7 +382,7 @@
     tags = set(['do'])
 
     def parse(self, parser):
-        node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
+        node = nodes.ExprStmt(lineno=six.advance_iterator(parser.stream).lineno)
         node.node = parser.parse_tuple()
         return node
 
@@ -392,7 +392,7 @@
     tags = set(['break', 'continue'])
 
     def parse(self, parser):
-        token = next(parser.stream)
+        token = six.advance_iterator(parser.stream)
         if token.value == 'break':
             return nodes.Break(lineno=token.lineno)
         return nodes.Continue(lineno=token.lineno)
@@ -403,7 +403,7 @@
     tags = set(['with'])
 
     def parse(self, parser):
-        node = nodes.Scope(lineno=next(parser.stream).lineno)
+        node = nodes.Scope(lineno=six.advance_iterator(parser.stream).lineno)
         assignments = []
         while parser.stream.current.type != 'block_end':
             lineno = parser.stream.current.lineno
@@ -424,7 +424,7 @@
     tags = set(['autoescape'])
 
     def parse(self, parser):
-        node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
+        node = nodes.ScopedEvalContextModifier(lineno=six.advance_iterator(parser.stream).lineno)
         node.options = [
             nodes.Keyword('autoescape', parser.parse_expression())
         ]
@@ -477,7 +477,7 @@
         strings = []
         for arg in node.args:
             if isinstance(arg, nodes.Const) and \
-               isinstance(arg.value, basestring):
+               isinstance(arg.value, six.string_types):
                 strings.append(arg.value)
             else:
                 strings.append(None)
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 43387ae..d137fc5 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -12,14 +12,13 @@
 import math
 from random import choice
 from operator import itemgetter
-from itertools import imap, groupby
+from itertools import groupby
 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
      unicode_urlencode
 from jinja2.runtime import Undefined
 from jinja2.exceptions import FilterArgumentError
 import six
-from six.moves import map
-from six.moves import zip
+from six.moves import map, zip
 
 
 _word_re = re.compile(r'\w+(?u)')
@@ -57,7 +56,7 @@
     passed object with the rules of the environment.  Dots are allowed
     to access attributes of attributes.
     """
-    if not isinstance(attribute, basestring) or '.' not in attribute:
+    if not isinstance(attribute, six.string_types) or '.' not in attribute:
         return lambda x: environment.getitem(x, attribute)
     attribute = attribute.split('.')
     def attrgetter(item):
@@ -83,7 +82,7 @@
     itemiter = None
     if isinstance(value, dict):
         itemiter = six.iteritems(value)
-    elif not isinstance(value, basestring):
+    elif not isinstance(value, six.string_types):
         try:
             itemiter = iter(value)
         except TypeError:
@@ -213,7 +212,7 @@
                                   '"key" or "value"')
     def sort_func(item):
         value = item[pos]
-        if isinstance(value, basestring) and not case_sensitive:
+        if isinstance(value, six.string_types) and not case_sensitive:
             value = value.lower()
         return value
 
@@ -250,7 +249,7 @@
     """
     if not case_sensitive:
         def sort_func(item):
-            if isinstance(item, basestring):
+            if isinstance(item, six.string_types):
                 item = item.lower()
             return item
     else:
@@ -308,11 +307,11 @@
        The `attribute` parameter was added.
     """
     if attribute is not None:
-        value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
+        value = map(make_attrgetter(eval_ctx.environment, attribute), value)
 
     # no automatic escaping?  joining is a lot eaiser then
     if not eval_ctx.autoescape:
-        return six.text_type(d).join(imap(unicode, value))
+        return six.text_type(d).join(map(six.text_type, value))
 
     # if the delimiter doesn't have an html representation we check
     # if any of the items has.  If yes we do a coercion to Markup
@@ -331,7 +330,7 @@
         return d.join(value)
 
     # no html involved, to normal joining
-    return soft_unicode(d).join(imap(soft_unicode, value))
+    return soft_unicode(d).join(map(soft_unicode, value))
 
 
 def do_center(value, width=80):
@@ -717,7 +716,7 @@
        attributes.  Also the `start` parameter was moved on to the right.
     """
     if attribute is not None:
-        iterable = imap(make_attrgetter(environment, attribute), iterable)
+        iterable = map(make_attrgetter(environment, attribute), iterable)
     return sum(iterable, start)
 
 
@@ -744,7 +743,7 @@
     """Reverse the object or return an iterator the iterates over it the other
     way round.
     """
-    if isinstance(value, basestring):
+    if isinstance(value, six.string_types):
         return value[::-1]
     try:
         return reversed(value)
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
index 5d24718..d56bc67 100644
--- a/jinja2/lexer.py
+++ b/jinja2/lexer.py
@@ -18,7 +18,7 @@
 from operator import itemgetter
 from collections import deque
 from jinja2.exceptions import TemplateSyntaxError
-from jinja2.utils import LRUCache, next
+from jinja2.utils import LRUCache
 import six
 
 
@@ -46,6 +46,12 @@
 float_re = re.compile(r'(?<!\.)\d+\.\d+')
 newline_re = re.compile(r'(\r\n|\r|\n)')
 
+try:
+    intern = intern  # py2
+except NameError:
+    import sys
+    intern = sys.intern  # py3
+
 # internal the tokens and keep references to them
 TOKEN_ADD = intern('add')
 TOKEN_ASSIGN = intern('assign')
@@ -263,7 +269,7 @@
         )
 
 
-class TokenStreamIterator(object):
+class TokenStreamIterator(six.Iterator):
     """The iterator for tokenstreams.  Iterate over the stream
     until the eof token is reached.
     """
@@ -274,35 +280,36 @@
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         token = self.stream.current
         if token.type is TOKEN_EOF:
             self.stream.close()
             raise StopIteration()
-        next(self.stream)
+        six.advance_iterator(self.stream)
         return token
 
 
-class TokenStream(object):
+class TokenStream(six.Iterator):
     """A token stream is an iterable that yields :class:`Token`\s.  The
     parser however does not iterate over it but calls :meth:`next` to go
     one token ahead.  The current active token is stored as :attr:`current`.
     """
 
     def __init__(self, generator, name, filename):
-        self._next = iter(generator).next
+        self._iter = iter(generator)
         self._pushed = deque()
         self.name = name
         self.filename = filename
         self.closed = False
         self.current = Token(1, TOKEN_INITIAL, '')
-        next(self)
+        six.advance_iterator(self)
 
     def __iter__(self):
         return TokenStreamIterator(self)
 
-    def __nonzero__(self):
+    def __bool__(self):
         return bool(self._pushed) or self.current.type is not TOKEN_EOF
+    __nonzero__ = __bool__  # py2
 
     eos = property(lambda x: not x, doc="Are we at the end of the stream?")
 
@@ -312,7 +319,7 @@
 
     def look(self):
         """Look at the next token."""
-        old_token = next(self)
+        old_token = six.advance_iterator(self)
         result = self.current
         self.push(result)
         self.current = old_token
@@ -321,27 +328,27 @@
     def skip(self, n=1):
         """Got n tokens ahead."""
         for x in range(n):
-            next(self)
+            six.advance_iterator(self)
 
     def next_if(self, expr):
         """Perform the token test and return the token if it matched.
         Otherwise the return value is `None`.
         """
         if self.current.test(expr):
-            return next(self)
+            return six.advance_iterator(self)
 
     def skip_if(self, expr):
         """Like :meth:`next_if` but only returns `True` or `False`."""
         return self.next_if(expr) is not None
 
-    def next(self):
+    def __next__(self):
         """Go one token ahead and return the old one"""
         rv = self.current
         if self._pushed:
             self.current = self._pushed.popleft()
         elif self.current.type is not TOKEN_EOF:
             try:
-                self.current = self._next()
+                self.current = six.advance_iterator(self._iter)
             except StopIteration:
                 self.close()
         return rv
@@ -349,7 +356,7 @@
     def close(self):
         """Close the stream."""
         self.current = Token(self.current.lineno, TOKEN_EOF, '')
-        self._next = None
+        self._iter = None
         self.closed = True
 
     def expect(self, expr):
@@ -370,7 +377,7 @@
         try:
             return self.current
         finally:
-            next(self)
+            six.advance_iterator(self)
 
 
 def get_lexer(environment):
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index a274922..809dad0 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -154,7 +154,7 @@
     """
 
     def __init__(self, searchpath, encoding='utf-8'):
-        if isinstance(searchpath, basestring):
+        if isinstance(searchpath, six.string_types):
             searchpath = [searchpath]
         self.searchpath = list(searchpath)
         self.encoding = encoding
@@ -307,7 +307,7 @@
         rv = self.load_func(template)
         if rv is None:
             raise TemplateNotFound(template)
-        elif isinstance(rv, basestring):
+        elif isinstance(rv, six.string_types):
             return rv, None, None
         return rv
 
@@ -432,7 +432,7 @@
         # create a fake module that looks for the templates in the
         # path given.
         mod = _TemplateModule(package_name)
-        if isinstance(path, basestring):
+        if isinstance(path, six.string_types):
             path = [path]
         else:
             path = list(path)
diff --git a/jinja2/meta.py b/jinja2/meta.py
index 3a779a5..26ae0b9 100644
--- a/jinja2/meta.py
+++ b/jinja2/meta.py
@@ -11,7 +11,7 @@
 """
 from jinja2 import nodes
 from jinja2.compiler import CodeGenerator
-
+import six
 
 class TrackingCodeGenerator(CodeGenerator):
     """We abuse the code generator for introspection."""
@@ -77,7 +77,7 @@
                     # something const, only yield the strings and ignore
                     # non-string consts that really just make no sense
                     if isinstance(template_name, nodes.Const):
-                        if isinstance(template_name.value, basestring):
+                        if isinstance(template_name.value, six.string_types):
                             yield template_name.value
                     # something dynamic in there
                     else:
@@ -87,7 +87,7 @@
                 yield None
             continue
         # constant is a basestring, direct template name
-        if isinstance(node.template.value, basestring):
+        if isinstance(node.template.value, six.string_types):
             yield node.template.value
         # a tuple or list (latter *should* not happen) made of consts,
         # yield the consts that are strings.  We could warn here for
@@ -95,7 +95,7 @@
         elif isinstance(node, nodes.Include) and \
              isinstance(node.template.value, (tuple, list)):
             for template_name in node.template.value:
-                if isinstance(template_name, basestring):
+                if isinstance(template_name, six.string_types):
                     yield template_name
         # something else we don't care about, we could warn here
         else:
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 5651a41..c14f8e3 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -13,10 +13,10 @@
     :license: BSD, see LICENSE for more details.
 """
 import operator
-from itertools import chain, izip
 from collections import deque
 from jinja2.utils import Markup, MethodType, FunctionType
 import six
+from six.moves import zip
 
 
 #: the types we support for context functions
@@ -103,7 +103,7 @@
     return ctx
 
 
-class Node(object):
+class Node(six.with_metaclass(NodeType, object)):
     """Baseclass for all Jinja2 nodes.  There are a number of nodes available
     of different types.  There are three major types:
 
@@ -137,7 +137,7 @@
                     len(self.fields),
                     len(self.fields) != 1 and 's' or ''
                 ))
-            for name, arg in izip(self.fields, fields):
+            for name, arg in zip(self.fields, fields):
                 setattr(self, name, arg)
         for attr in self.attributes:
             setattr(self, attr, attributes.pop(attr, None))
diff --git a/jinja2/parser.py b/jinja2/parser.py
index b4b06fe..520e88f 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -10,10 +10,9 @@
 """
 from jinja2 import nodes
 from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
-from jinja2.utils import next
 from jinja2.lexer import describe_token, describe_token_expr
-from six.moves import map
-from six.moves import zip
+import six
+from six.moves import map, zip
 
 
 #: statements that callinto 
@@ -164,12 +163,12 @@
             self.fail_eof(end_tokens)
 
         if drop_needle:
-            next(self.stream)
+            six.advance_iterator(self.stream)
         return result
 
     def parse_set(self):
         """Parse an assign statement."""
-        lineno = next(self.stream).lineno
+        lineno = six.advance_iterator(self.stream).lineno
         target = self.parse_assign_target()
         self.stream.expect('assign')
         expr = self.parse_tuple()
@@ -187,7 +186,7 @@
             test = self.parse_expression()
         recursive = self.stream.skip_if('name:recursive')
         body = self.parse_statements(('name:endfor', 'name:else'))
-        if next(self.stream).value == 'endfor':
+        if six.advance_iterator(self.stream).value == 'endfor':
             else_ = []
         else:
             else_ = self.parse_statements(('name:endfor',), drop_needle=True)
@@ -201,7 +200,7 @@
             node.test = self.parse_tuple(with_condexpr=False)
             node.body = self.parse_statements(('name:elif', 'name:else',
                                                'name:endif'))
-            token = next(self.stream)
+            token = six.advance_iterator(self.stream)
             if token.test('name:elif'):
                 new_node = nodes.If(lineno=self.stream.current.lineno)
                 node.else_ = [new_node]
@@ -216,7 +215,7 @@
         return result
 
     def parse_block(self):
-        node = nodes.Block(lineno=next(self.stream).lineno)
+        node = nodes.Block(lineno=six.advance_iterator(self.stream).lineno)
         node.name = self.stream.expect('name').value
         node.scoped = self.stream.skip_if('name:scoped')
 
@@ -233,21 +232,21 @@
         return node
 
     def parse_extends(self):
-        node = nodes.Extends(lineno=next(self.stream).lineno)
+        node = nodes.Extends(lineno=six.advance_iterator(self.stream).lineno)
         node.template = self.parse_expression()
         return node
 
     def parse_import_context(self, node, default):
         if self.stream.current.test_any('name:with', 'name:without') and \
            self.stream.look().test('name:context'):
-            node.with_context = next(self.stream).value == 'with'
+            node.with_context = six.advance_iterator(self.stream).value == 'with'
             self.stream.skip()
         else:
             node.with_context = default
         return node
 
     def parse_include(self):
-        node = nodes.Include(lineno=next(self.stream).lineno)
+        node = nodes.Include(lineno=six.advance_iterator(self.stream).lineno)
         node.template = self.parse_expression()
         if self.stream.current.test('name:ignore') and \
            self.stream.look().test('name:missing'):
@@ -258,14 +257,14 @@
         return self.parse_import_context(node, True)
 
     def parse_import(self):
-        node = nodes.Import(lineno=next(self.stream).lineno)
+        node = nodes.Import(lineno=six.advance_iterator(self.stream).lineno)
         node.template = self.parse_expression()
         self.stream.expect('name:as')
         node.target = self.parse_assign_target(name_only=True).name
         return self.parse_import_context(node, False)
 
     def parse_from(self):
-        node = nodes.FromImport(lineno=next(self.stream).lineno)
+        node = nodes.FromImport(lineno=six.advance_iterator(self.stream).lineno)
         node.template = self.parse_expression()
         self.stream.expect('name:import')
         node.names = []
@@ -273,7 +272,7 @@
         def parse_context():
             if self.stream.current.value in ('with', 'without') and \
                self.stream.look().test('name:context'):
-                node.with_context = next(self.stream).value == 'with'
+                node.with_context = six.advance_iterator(self.stream).value == 'with'
                 self.stream.skip()
                 return True
             return False
@@ -318,7 +317,7 @@
         self.stream.expect('rparen')
 
     def parse_call_block(self):
-        node = nodes.CallBlock(lineno=next(self.stream).lineno)
+        node = nodes.CallBlock(lineno=six.advance_iterator(self.stream).lineno)
         if self.stream.current.type == 'lparen':
             self.parse_signature(node)
         else:
@@ -332,14 +331,14 @@
         return node
 
     def parse_filter_block(self):
-        node = nodes.FilterBlock(lineno=next(self.stream).lineno)
+        node = nodes.FilterBlock(lineno=six.advance_iterator(self.stream).lineno)
         node.filter = self.parse_filter(None, start_inline=True)
         node.body = self.parse_statements(('name:endfilter',),
                                           drop_needle=True)
         return node
 
     def parse_macro(self):
-        node = nodes.Macro(lineno=next(self.stream).lineno)
+        node = nodes.Macro(lineno=six.advance_iterator(self.stream).lineno)
         node.name = self.parse_assign_target(name_only=True).name
         self.parse_signature(node)
         node.body = self.parse_statements(('name:endmacro',),
@@ -347,7 +346,7 @@
         return node
 
     def parse_print(self):
-        node = nodes.Output(lineno=next(self.stream).lineno)
+        node = nodes.Output(lineno=six.advance_iterator(self.stream).lineno)
         node.nodes = []
         while self.stream.current.type != 'block_end':
             if node.nodes:
@@ -421,7 +420,7 @@
 
     def parse_not(self):
         if self.stream.current.test('name:not'):
-            lineno = next(self.stream).lineno
+            lineno = six.advance_iterator(self.stream).lineno
             return nodes.Not(self.parse_not(), lineno=lineno)
         return self.parse_compare()
 
@@ -432,7 +431,7 @@
         while 1:
             token_type = self.stream.current.type
             if token_type in _compare_operators:
-                next(self.stream)
+                six.advance_iterator(self.stream)
                 ops.append(nodes.Operand(token_type, self.parse_add()))
             elif self.stream.skip_if('name:in'):
                 ops.append(nodes.Operand('in', self.parse_add()))
@@ -451,7 +450,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_sub()
         while self.stream.current.type == 'add':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_sub()
             left = nodes.Add(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -461,7 +460,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_concat()
         while self.stream.current.type == 'sub':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_concat()
             left = nodes.Sub(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -471,7 +470,7 @@
         lineno = self.stream.current.lineno
         args = [self.parse_mul()]
         while self.stream.current.type == 'tilde':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             args.append(self.parse_mul())
         if len(args) == 1:
             return args[0]
@@ -481,7 +480,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_div()
         while self.stream.current.type == 'mul':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_div()
             left = nodes.Mul(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -491,7 +490,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_floordiv()
         while self.stream.current.type == 'div':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_floordiv()
             left = nodes.Div(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -501,7 +500,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_mod()
         while self.stream.current.type == 'floordiv':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_mod()
             left = nodes.FloorDiv(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -511,7 +510,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_pow()
         while self.stream.current.type == 'mod':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_pow()
             left = nodes.Mod(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -521,7 +520,7 @@
         lineno = self.stream.current.lineno
         left = self.parse_unary()
         while self.stream.current.type == 'pow':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             right = self.parse_unary()
             left = nodes.Pow(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -531,10 +530,10 @@
         token_type = self.stream.current.type
         lineno = self.stream.current.lineno
         if token_type == 'sub':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             node = nodes.Neg(self.parse_unary(False), lineno=lineno)
         elif token_type == 'add':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             node = nodes.Pos(self.parse_unary(False), lineno=lineno)
         else:
             node = self.parse_primary()
@@ -553,20 +552,20 @@
                 node = nodes.Const(None, lineno=token.lineno)
             else:
                 node = nodes.Name(token.value, 'load', lineno=token.lineno)
-            next(self.stream)
+            six.advance_iterator(self.stream)
         elif token.type == 'string':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             buf = [token.value]
             lineno = token.lineno
             while self.stream.current.type == 'string':
                 buf.append(self.stream.current.value)
-                next(self.stream)
+                six.advance_iterator(self.stream)
             node = nodes.Const(''.join(buf), lineno=lineno)
         elif token.type in ('integer', 'float'):
-            next(self.stream)
+            six.advance_iterator(self.stream)
             node = nodes.Const(token.value, lineno=token.lineno)
         elif token.type == 'lparen':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             node = self.parse_tuple(explicit_parentheses=True)
             self.stream.expect('rparen')
         elif token.type == 'lbracket':
@@ -688,10 +687,10 @@
         return node
 
     def parse_subscript(self, node):
-        token = next(self.stream)
+        token = six.advance_iterator(self.stream)
         if token.type == 'dot':
             attr_token = self.stream.current
-            next(self.stream)
+            six.advance_iterator(self.stream)
             if attr_token.type == 'name':
                 return nodes.Getattr(node, attr_token.value, 'load',
                                      lineno=token.lineno)
@@ -717,13 +716,13 @@
         lineno = self.stream.current.lineno
 
         if self.stream.current.type == 'colon':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             args = [None]
         else:
             node = self.parse_expression()
             if self.stream.current.type != 'colon':
                 return node
-            next(self.stream)
+            six.advance_iterator(self.stream)
             args = [node]
 
         if self.stream.current.type == 'colon':
@@ -734,7 +733,7 @@
             args.append(None)
 
         if self.stream.current.type == 'colon':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             if self.stream.current.type not in ('rbracket', 'comma'):
                 args.append(self.parse_expression())
             else:
@@ -764,11 +763,11 @@
                     break
             if self.stream.current.type == 'mul':
                 ensure(dyn_args is None and dyn_kwargs is None)
-                next(self.stream)
+                six.advance_iterator(self.stream)
                 dyn_args = self.parse_expression()
             elif self.stream.current.type == 'pow':
                 ensure(dyn_kwargs is None)
-                next(self.stream)
+                six.advance_iterator(self.stream)
                 dyn_kwargs = self.parse_expression()
             else:
                 ensure(dyn_args is None and dyn_kwargs is None)
@@ -794,11 +793,11 @@
     def parse_filter(self, node, start_inline=False):
         while self.stream.current.type == 'pipe' or start_inline:
             if not start_inline:
-                next(self.stream)
+                six.advance_iterator(self.stream)
             token = self.stream.expect('name')
             name = token.value
             while self.stream.current.type == 'dot':
-                next(self.stream)
+                six.advance_iterator(self.stream)
                 name += '.' + self.stream.expect('name').value
             if self.stream.current.type == 'lparen':
                 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
@@ -812,15 +811,15 @@
         return node
 
     def parse_test(self, node):
-        token = next(self.stream)
+        token = six.advance_iterator(self.stream)
         if self.stream.current.test('name:not'):
-            next(self.stream)
+            six.advance_iterator(self.stream)
             negated = True
         else:
             negated = False
         name = self.stream.expect('name').value
         while self.stream.current.type == 'dot':
-            next(self.stream)
+            six.advance_iterator(self.stream)
             name += '.' + self.stream.expect('name').value
         dyn_args = dyn_kwargs = None
         kwargs = []
@@ -863,14 +862,14 @@
                     if token.value:
                         add_data(nodes.TemplateData(token.value,
                                                     lineno=token.lineno))
-                    next(self.stream)
+                    six.advance_iterator(self.stream)
                 elif token.type == 'variable_begin':
-                    next(self.stream)
+                    six.advance_iterator(self.stream)
                     add_data(self.parse_tuple(with_condexpr=True))
                     self.stream.expect('variable_end')
                 elif token.type == 'block_begin':
                     flush_data()
-                    next(self.stream)
+                    six.advance_iterator(self.stream)
                     if end_tokens is not None and \
                        self.stream.current.test_any(*end_tokens):
                         return body
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 750b630..a11095e 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -8,13 +8,14 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD.
 """
-from itertools import chain, imap
+from itertools import chain
 from jinja2.nodes import EvalContext, _context_function_types
 from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
-     concat, internalcode, next, object_type_repr
+     concat, internalcode, object_type_repr
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
      TemplateNotFound
 import six
+from six.moves import map
 
 
 # these variables are exported to the template runtime
@@ -26,7 +27,10 @@
 #: the name of the function that is used to convert something into
 #: a string.  2to3 will adopt that automatically and the generated
 #: code can take advantage of it.
-to_string = unicode
+try:
+    to_string = unicode
+except NameError:
+    to_string = str
 
 #: the identity function.  Useful for certain things in the environment
 identity = lambda x: x
@@ -37,7 +41,7 @@
 def markup_join(seq):
     """Concatenation that escapes if necessary and converts to unicode."""
     buf = []
-    iterator = imap(soft_unicode, seq)
+    iterator = map(soft_unicode, seq)
     for arg in iterator:
         buf.append(arg)
         if hasattr(arg, '__html__'):
@@ -47,7 +51,7 @@
 
 def unicode_join(seq):
     """Simple args to unicode conversion and concatenation."""
-    return concat(imap(unicode, seq))
+    return concat(map(six.text_type, seq))
 
 
 def new_context(environment, template_name, blocks, vars=None,
@@ -305,7 +309,7 @@
 
     def _safe_next(self):
         try:
-            return next(self._iterator)
+            return six.advance_iterator(self._iterator)
         except StopIteration:
             return _last_iteration
 
@@ -341,7 +345,7 @@
         )
 
 
-class LoopContextIterator(object):
+class LoopContextIterator(six.Iterator):
     """The iterator for a loop context."""
     __slots__ = ('context',)
 
@@ -351,7 +355,7 @@
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         ctx = self.context
         ctx.index0 += 1
         if ctx._after is _last_iteration:
@@ -456,7 +460,7 @@
         if self._undefined_hint is None:
             if self._undefined_obj is missing:
                 hint = '%r is undefined' % self._undefined_name
-            elif not isinstance(self._undefined_name, basestring):
+            elif not isinstance(self._undefined_name, six.string_types):
                 hint = '%s has no element %r' % (
                     object_type_repr(self._undefined_obj),
                     self._undefined_name
@@ -484,12 +488,9 @@
         _fail_with_undefined_error
 
     def __str__(self):
-        return six.text_type(self).encode('utf-8')
+        s = self.__unicode__()
+        return s if six.PY3 else s.encode('utf-8')
 
-    # unicode goes after __str__ because we configured 2to3 to rename
-    # __unicode__ to __str__.  because the 2to3 tree is not designed to
-    # remove nodes from it, we leave the above __str__ around and let
-    # it override at runtime.
     def __unicode__(self):
         return u''
 
@@ -522,6 +523,10 @@
     """
     __slots__ = ()
 
+    def __str__(self):
+        s = self.__unicode__()
+        return s if six.PY3 else s.encode('utf-8')
+
     def __unicode__(self):
         if self._undefined_hint is None:
             if self._undefined_obj is missing:
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index b9b1716..b880250 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -13,6 +13,7 @@
     :license: BSD.
 """
 import operator
+import six
 from jinja2.environment import Environment
 from jinja2.exceptions import SecurityError
 from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
@@ -299,7 +300,7 @@
         try:
             return obj[argument]
         except (TypeError, LookupError):
-            if isinstance(argument, basestring):
+            if isinstance(argument, six.string_types):
                 try:
                     attr = str(argument)
                 except Exception:
diff --git a/jinja2/tests.py b/jinja2/tests.py
index 74017e5..140b5bf 100644
--- a/jinja2/tests.py
+++ b/jinja2/tests.py
@@ -87,7 +87,7 @@
 
 def test_string(value):
     """Return true if the object is a string."""
-    return isinstance(value, basestring)
+    return isinstance(value, six.string_types)
 
 
 def test_mapping(value):
@@ -100,7 +100,7 @@
 
 def test_number(value):
     """Return true if the variable is a number."""
-    return isinstance(value, (int, long, float, complex))
+    return isinstance(value, (int, float, complex))
 
 
 def test_sequence(value):
diff --git a/jinja2/testsuite/ext.py b/jinja2/testsuite/ext.py
index 65251a5..29f78b1 100644
--- a/jinja2/testsuite/ext.py
+++ b/jinja2/testsuite/ext.py
@@ -17,15 +17,8 @@
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.ext import Extension
 from jinja2.lexer import Token, count_newlines
-from jinja2.utils import next
 import six
-
-# 2.x / 3.x
-try:
-    from io import BytesIO
-except ImportError:
-    from StringIO import StringIO as BytesIO
-
+from six import BytesIO
 
 importable_object = 23
 
@@ -116,7 +109,7 @@
             self.attr('ext_attr'),
             nodes.ImportedName(__name__ + '.importable_object'),
             nodes.ContextReference()
-        ])]).set_lineno(next(parser.stream).lineno)
+        ])]).set_lineno(six.advance_iterator(parser.stream).lineno)
 
     def _dump(self, sandboxed, ext_attr, imported_object, context):
         return '%s|%s|%s|%s' % (
@@ -433,7 +426,7 @@
         '''
         tmpl = env.from_string(tmplsource)
         assert tmpl.render(val=True).split()[0] == 'Markup'
-        assert tmpl.render(val=False).split()[0] == unicode.__name__
+        assert tmpl.render(val=False).split()[0] == six.text_type.__name__
 
         # looking at the source we should see <testing> there in raw
         # (and then escaped as well)
diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py
index cd95463..e74881d 100644
--- a/jinja2/testsuite/filters.py
+++ b/jinja2/testsuite/filters.py
@@ -13,8 +13,7 @@
 
 from jinja2 import Markup, Environment
 import six
-from six.moves import map
-from six.moves import zip
+from six.moves import map, zip
 
 env = Environment()
 
@@ -301,6 +300,7 @@
                 self.value = value
             def __unicode__(self):
                 return six.text_type(self.value)
+            __str__ = __unicode__
         tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
         assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
 
diff --git a/jinja2/testsuite/lexnparse.py b/jinja2/testsuite/lexnparse.py
index e162463..e50b4f4 100644
--- a/jinja2/testsuite/lexnparse.py
+++ b/jinja2/testsuite/lexnparse.py
@@ -15,6 +15,7 @@
 
 from jinja2 import Environment, Template, TemplateSyntaxError, \
      UndefinedError, nodes
+from jinja2.lexer import Token, TokenStream, TOKEN_EOF, TOKEN_BLOCK_BEGIN, TOKEN_BLOCK_END
 import six
 
 env = Environment()
@@ -28,6 +29,30 @@
     jinja_string_repr = repr
 
 
+class TokenStreamTestCase(JinjaTestCase):
+    test_tokens = [Token(1, TOKEN_BLOCK_BEGIN, ''),
+                   Token(2, TOKEN_BLOCK_END, ''),
+                  ]
+
+    def test_simple(self):
+        ts = TokenStream(self.test_tokens, "foo", "bar")
+        assert ts.current.type is TOKEN_BLOCK_BEGIN
+        assert bool(ts)
+        assert not bool(ts.eos)
+        six.advance_iterator(ts)
+        assert ts.current.type is TOKEN_BLOCK_END
+        assert bool(ts)
+        assert not bool(ts.eos)
+        six.advance_iterator(ts)
+        assert ts.current.type is TOKEN_EOF
+        assert not bool(ts)
+        assert bool(ts.eos)
+
+    def test_iter(self):
+        token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")]
+        assert token_types == ['block_begin', 'block_end', ]
+
+
 class LexerTestCase(JinjaTestCase):
 
     def test_raw1(self):
@@ -382,6 +407,7 @@
 
 def suite():
     suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TokenStreamTestCase))
     suite.addTest(unittest.makeSuite(LexerTestCase))
     suite.addTest(unittest.makeSuite(ParserTestCase))
     suite.addTest(unittest.makeSuite(SyntaxTestCase))
diff --git a/jinja2/testsuite/regression.py b/jinja2/testsuite/regression.py
index 5c3633f..bac4d60 100644
--- a/jinja2/testsuite/regression.py
+++ b/jinja2/testsuite/regression.py
@@ -14,6 +14,7 @@
 
 from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
      TemplateNotFound, PrefixLoader
+import six
 from six.moves import map
 from six.moves import zip
 
@@ -120,7 +121,7 @@
 
         ''')
 
-        assert tmpl.render().split() == map(unicode, list(range(1, 11))) * 5
+        assert tmpl.render().split() == [six.text_type(x) for x in range(1, 11)] * 5
 
     def test_weird_inline_comment(self):
         env = Environment(line_statement_prefix='%')
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 121a008..ca0968b 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -12,6 +12,7 @@
 import sys
 import errno
 import six
+from six.moves import map
 try:
     from urllib.parse import quote_from_bytes as url_quote
 except ImportError:
@@ -19,16 +20,17 @@
 try:
     from thread import allocate_lock
 except ImportError:
-    from dummy_thread import allocate_lock
+    try:
+        from _thread import allocate_lock  # py 3
+    except ImportError:
+        from dummy_thread import allocate_lock
 from collections import deque
-from itertools import imap
-
 
 _word_split_re = re.compile(r'(\s+)')
 _punctuation_re = re.compile(
     '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
-        '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
-        '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
+        '|'.join(map(re.escape, ('(', '<', '&lt;'))),
+        '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
     )
 )
 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
@@ -62,21 +64,12 @@
                 # this hack is needed so that the current frame
                 # does not show up in the traceback.
                 exc_type, exc_value, tb = sys.exc_info()
-                raise exc_type, exc_value, tb.tb_next
+                six.reraise(exc_type, exc_value, tb.tb_next)
     else:
         concat = _concat
     del _test_gen_bug, _error
 
 
-# for python 2.x we create ourselves a next() function that does the
-# basics without exception catching.
-try:
-    next = next
-except NameError:
-    def next(x):
-        return six.advance_iterator(x)
-
-
 # if this python version is unable to deal with unicode filenames
 # when passed to encode we let this function encode it properly.
 # This is used in a couple of places.  As far as Jinja is concerned
@@ -363,9 +356,9 @@
     If non strings are provided they are converted to their unicode
     representation first.
     """
-    if not isinstance(obj, basestring):
+    if not isinstance(obj, six.string_types):
         obj = six.text_type(obj)
-    if isinstance(obj, unicode):
+    if isinstance(obj, six.text_type):
         obj = obj.encode(charset)
     return six.text_type(url_quote(obj))
 
@@ -563,7 +556,7 @@
     pass
 
 
-class Cycler(object):
+class Cycler(six.Iterator):
     """A cycle helper for templates."""
 
     def __init__(self, *items):
@@ -581,7 +574,7 @@
         """Returns the current item."""
         return self.items[self.pos]
 
-    def next(self):
+    def __next__(self):
         """Goes one item ahead and returns it."""
         rv = self.current
         self.pos = (self.pos + 1) % len(self.items)
diff --git a/setup.py b/setup.py
index 4147956..3551ac3 100644
--- a/setup.py
+++ b/setup.py
@@ -48,14 +48,6 @@
 )
 
 
-# tell distribute to use 2to3 with our own fixers.
-extra = {}
-if sys.version_info >= (3, 0):
-    extra.update(
-        use_2to3=True,
-        use_2to3_fixers=['custom_fixers']
-    )
-
 # ignore the old '--with-speedups' flag
 try:
     speedups_pos = sys.argv.index('--with-speedups')
@@ -105,6 +97,5 @@
     [babel.extractors]
     jinja2 = jinja2.ext:babel_extract[i18n]
     """,
-    features={'debugsupport': debugsupport},
-    **extra
+    features={'debugsupport': debugsupport}
 )