binding: Updates Jinja2 from 2.7.1 to 2.8.

New feature in 2.8:
- block assignment
  {% set var_name %} content {% endset %}

Manually edited files:
  README.chromium
  get_jinja2.sh

BUG=

Review-Url: https://codereview.chromium.org/2316103002
Cr-Original-Commit-Position: refs/heads/master@{#417191}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: a34bd686422a4af68b566c2d045b978524bd2cbf
diff --git a/Jinja2-2.7.1.tar.gz.md5 b/Jinja2-2.7.1.tar.gz.md5
deleted file mode 100644
index 5c9e757..0000000
--- a/Jinja2-2.7.1.tar.gz.md5
+++ /dev/null
@@ -1 +0,0 @@
-282aed153e69f970d6e76f78ed9d027a  Jinja2-2.7.1.tar.gz
diff --git a/Jinja2-2.7.1.tar.gz.sha512 b/Jinja2-2.7.1.tar.gz.sha512
deleted file mode 100644
index 44b486d..0000000
--- a/Jinja2-2.7.1.tar.gz.sha512
+++ /dev/null
@@ -1 +0,0 @@
-c5d4262f6dfec77c74496f0b3afd88a37fc0573133810cfdc29fadbd9d02bb7af10b2a3ddf3075f8b682629cd41a949dcbccb293b84b0aeff9090b0aa9669e02  Jinja2-2.7.1.tar.gz
diff --git a/Jinja2-2.8.tar.gz.md5 b/Jinja2-2.8.tar.gz.md5
new file mode 100644
index 0000000..a0eb1b2
--- /dev/null
+++ b/Jinja2-2.8.tar.gz.md5
@@ -0,0 +1 @@
+edb51693fe22c53cee5403775c71a99e  Jinja2-2.8.tar.gz
diff --git a/Jinja2-2.8.tar.gz.sha512 b/Jinja2-2.8.tar.gz.sha512
new file mode 100644
index 0000000..88e4ea6
--- /dev/null
+++ b/Jinja2-2.8.tar.gz.sha512
@@ -0,0 +1 @@
+2e80d6d9ad10dafcce1e6dd24493f5dffc43a17f71a30a650415638e12d3a3891738ebacc569701129214026d062d91a2b10e4f7a2c7b85d801dde26ded1bebb  Jinja2-2.8.tar.gz
diff --git a/README.chromium b/README.chromium
index 9ab2426..684ff8e 100644
--- a/README.chromium
+++ b/README.chromium
@@ -1,7 +1,7 @@
 Name: Jinja2 Python Template Engine
 Short Name: jinja2
 URL: http://jinja.pocoo.org/
-Version: 2.7.1
+Version: 2.8
 License: BSD 3-clause License
 License File: NOT_SHIPPED
 Security Critical: no
@@ -9,9 +9,9 @@
 Description:
 Template engine for code generation in Blink.
 
-Source: https://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.7.1.tar.gz
-MD5: 282aed153e69f970d6e76f78ed9d027a
-SHA-1: a9b24d887f2be772921b3ee30a0b9d435cffadda
+Source: https://pypi.python.org/packages/f2/2f/0b98b06a345a761bec91a079ccae392d282690c2d8272e708f4d10829e22/Jinja2-2.8.tar.gz
+MD5: edb51693fe22c53cee5403775c71a99e
+SHA-1: 4a33c1a0fd585eba2507e8c274a9cd113b1d13ab
 
 Local Modifications:
 This only includes the jinja2 directory from the tarball and the LICENSE and
diff --git a/__init__.py b/__init__.py
index 6fa11c3..029fb2e 100644
--- a/__init__.py
+++ b/__init__.py
@@ -27,7 +27,7 @@
     :license: BSD, see LICENSE for more details.
 """
 __docformat__ = 'restructuredtext en'
-__version__ = '2.7.1'
+__version__ = '2.8'
 
 # high level interface
 from jinja2.environment import Environment, Template
@@ -42,7 +42,8 @@
      MemcachedBytecodeCache
 
 # undefined types
-from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
+     make_logging_undefined
 
 # exceptions
 from jinja2.exceptions import TemplateError, UndefinedError, \
@@ -65,5 +66,5 @@
     'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
     'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
     'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
-    'evalcontextfilter', 'evalcontextfunction'
+    'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
 ]
diff --git a/_compat.py b/_compat.py
index 8fa8a49..143962f 100644
--- a/_compat.py
+++ b/_compat.py
@@ -22,6 +22,7 @@
     range_type = range
     text_type = str
     string_types = (str,)
+    integer_types = (int,)
 
     iterkeys = lambda d: iter(d.keys())
     itervalues = lambda d: iter(d.values())
@@ -51,6 +52,7 @@
     text_type = unicode
     range_type = xrange
     string_types = (str, unicode)
+    integer_types = (int, long)
 
     iterkeys = lambda d: d.iterkeys()
     itervalues = lambda d: d.itervalues()
@@ -82,12 +84,6 @@
             return filename.encode('utf-8')
         return filename
 
-try:
-    next = next
-except NameError:
-    def next(it):
-        return it.next()
-
 
 def with_metaclass(meta, *bases):
     # This requires a bit of explanation: the basic idea is to make a
@@ -110,41 +106,6 @@
 
 
 try:
-    from collections import Mapping as mapping_types
-except ImportError:
-    import UserDict
-    mapping_types = (UserDict.UserDict, UserDict.DictMixin, dict)
-
-
-# common types.  These do exist in the special types module too which however
-# does not exist in IronPython out of the box.  Also that way we don't have
-# to deal with implementation specific stuff here
-class _C(object):
-    def method(self): pass
-def _func():
-    yield None
-function_type = type(_func)
-generator_type = type(_func())
-method_type = type(_C().method)
-code_type = type(_C.method.__code__)
-try:
-    raise TypeError()
-except TypeError:
-    _tb = sys.exc_info()[2]
-    traceback_type = type(_tb)
-    frame_type = type(_tb.tb_frame)
-
-
-try:
     from urllib.parse import quote_from_bytes as url_quote
 except ImportError:
     from urllib import quote as url_quote
-
-
-try:
-    from thread import allocate_lock
-except ImportError:
-    try:
-        from threading import Lock as allocate_lock
-    except ImportError:
-        from dummy_thread import allocate_lock
diff --git a/bccache.py b/bccache.py
index f2f9db6..f5bd314 100644
--- a/bccache.py
+++ b/bccache.py
@@ -15,7 +15,10 @@
     :license: BSD.
 """
 from os import path, listdir
+import os
 import sys
+import stat
+import errno
 import marshal
 import tempfile
 import fnmatch
@@ -85,7 +88,12 @@
         if self.checksum != checksum:
             self.reset()
             return
-        self.code = marshal_load(f)
+        # if marshal_load fails then we need to reload
+        try:
+            self.code = marshal_load(f)
+        except (EOFError, ValueError, TypeError):
+            self.reset()
+            return
 
     def write_bytecode(self, f):
         """Dump the bytecode into the file or file like object passed."""
@@ -189,7 +197,9 @@
     two arguments: The directory where the cache items are stored and a
     pattern string that is used to build the filename.
 
-    If no directory is specified the system temporary items folder is used.
+    If no directory is specified a default cache directory is selected.  On
+    Windows the user's temp directory is used, on UNIX systems a directory
+    is created for the user in the system temp directory.
 
     The pattern can be used to have multiple separate caches operate on the
     same directory.  The default pattern is ``'__jinja2_%s.cache'``.  ``%s``
@@ -202,10 +212,51 @@
 
     def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
         if directory is None:
-            directory = tempfile.gettempdir()
+            directory = self._get_default_cache_dir()
         self.directory = directory
         self.pattern = pattern
 
+    def _get_default_cache_dir(self):
+        def _unsafe_dir():
+            raise RuntimeError('Cannot determine safe temp directory.  You '
+                               'need to explicitly provide one.')
+
+        tmpdir = tempfile.gettempdir()
+
+        # On windows the temporary directory is used specific unless
+        # explicitly forced otherwise.  We can just use that.
+        if os.name == 'nt':
+            return tmpdir
+        if not hasattr(os, 'getuid'):
+            _unsafe_dir()
+
+        dirname = '_jinja2-cache-%d' % os.getuid()
+        actual_dir = os.path.join(tmpdir, dirname)
+
+        try:
+            os.mkdir(actual_dir, stat.S_IRWXU)
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+        try:
+            os.chmod(actual_dir, stat.S_IRWXU)
+            actual_dir_stat = os.lstat(actual_dir)
+            if actual_dir_stat.st_uid != os.getuid() \
+               or not stat.S_ISDIR(actual_dir_stat.st_mode) \
+               or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
+                _unsafe_dir()
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+
+        actual_dir_stat = os.lstat(actual_dir)
+        if actual_dir_stat.st_uid != os.getuid() \
+           or not stat.S_ISDIR(actual_dir_stat.st_mode) \
+           or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
+            _unsafe_dir()
+
+        return actual_dir
+
     def _get_cache_filename(self, bucket):
         return path.join(self.directory, self.pattern % bucket.key)
 
diff --git a/compiler.py b/compiler.py
index 75a60b8..fad007b 100644
--- a/compiler.py
+++ b/compiler.py
@@ -16,7 +16,7 @@
 from jinja2.visitor import NodeVisitor
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.utils import Markup, concat, escape
-from jinja2._compat import range_type, next, text_type, string_types, \
+from jinja2._compat import range_type, text_type, string_types, \
      iteritems, NativeStringIO, imap
 
 
@@ -57,7 +57,8 @@
     """Generate the python source for a node tree."""
     if not isinstance(node, nodes.Template):
         raise TypeError('Can\'t compile non template nodes')
-    generator = CodeGenerator(environment, name, filename, stream, defer_init)
+    generator = environment.code_generator_class(environment, name, filename,
+                                                 stream, defer_init)
     generator.visit(node)
     if stream is None:
         return generator.stream.getvalue()
@@ -347,6 +348,9 @@
     def visit_FilterBlock(self, node):
         self.visit(node.filter)
 
+    def visit_AssignBlock(self, node):
+        """Stop visiting at block assigns."""
+
     def visit_Scope(self, node):
         """Stop visiting at scopes."""
 
@@ -1215,8 +1219,17 @@
         if self.has_known_extends and frame.require_output_check:
             return
 
+        allow_constant_finalize = True
         if self.environment.finalize:
-            finalize = lambda x: text_type(self.environment.finalize(x))
+            func = self.environment.finalize
+            if getattr(func, 'contextfunction', False) or \
+               getattr(func, 'evalcontextfunction', False):
+                allow_constant_finalize = False
+            elif getattr(func, 'environmentfunction', False):
+                finalize = lambda x: text_type(
+                    self.environment.finalize(self.environment, x))
+            else:
+                finalize = lambda x: text_type(self.environment.finalize(x))
         else:
             finalize = text_type
 
@@ -1232,6 +1245,8 @@
         body = []
         for child in node.nodes:
             try:
+                if not allow_constant_finalize:
+                    raise nodes.Impossible()
                 const = child.as_const(frame.eval_ctx)
             except nodes.Impossible:
                 body.append(child)
@@ -1287,6 +1302,9 @@
                         self.write('to_string(')
                     if self.environment.finalize is not None:
                         self.write('environment.finalize(')
+                        if getattr(self.environment.finalize,
+                                   "contextfunction", False):
+                            self.write('context, ')
                         close += 1
                     self.visit(item, frame)
                     self.write(')' * close)
@@ -1309,7 +1327,6 @@
                     arguments.append(item)
             self.writeline('yield ')
             self.write(repr(concat(format)) + ' % (')
-            idx = -1
             self.indent()
             for argument in arguments:
                 self.newline(argument)
@@ -1323,6 +1340,15 @@
                     close += 1
                 if self.environment.finalize is not None:
                     self.write('environment.finalize(')
+                    if getattr(self.environment.finalize,
+                               'contextfunction', False):
+                        self.write('context, ')
+                    elif getattr(self.environment.finalize,
+                               'evalcontextfunction', False):
+                        self.write('context.eval_ctx, ')
+                    elif getattr(self.environment.finalize,
+                               'environmentfunction', False):
+                        self.write('environment, ')
                     close += 1
                 self.visit(argument, frame)
                 self.write(')' * close + ', ')
@@ -1332,42 +1358,62 @@
         if outdent_later:
             self.outdent()
 
-    def visit_Assign(self, node, frame):
-        self.newline(node)
+    def make_assignment_frame(self, frame):
         # toplevel assignments however go into the local namespace and
         # the current template's context.  We create a copy of the frame
         # here and add a set so that the Name visitor can add the assigned
         # names here.
-        if frame.toplevel:
-            assignment_frame = frame.copy()
-            assignment_frame.toplevel_assignments = set()
+        if not frame.toplevel:
+            return frame
+        assignment_frame = frame.copy()
+        assignment_frame.toplevel_assignments = set()
+        return assignment_frame
+
+    def export_assigned_vars(self, frame, assignment_frame):
+        if not frame.toplevel:
+            return
+        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))
+            self.writeline('context.vars[%r] = l_%s' % (name, name))
         else:
-            assignment_frame = frame
+            self.writeline('context.vars.update({')
+            for idx, name in enumerate(assignment_frame.toplevel_assignments):
+                if idx:
+                    self.write(', ')
+                self.write('%r: l_%s' % (name, name))
+            self.write('})')
+        if public_names:
+            if len(public_names) == 1:
+                self.writeline('context.exported_vars.add(%r)' %
+                               public_names[0])
+            else:
+                self.writeline('context.exported_vars.update((%s))' %
+                               ', '.join(imap(repr, public_names)))
+
+    def visit_Assign(self, node, frame):
+        self.newline(node)
+        assignment_frame = self.make_assignment_frame(frame)
         self.visit(node.target, assignment_frame)
         self.write(' = ')
         self.visit(node.node, frame)
+        self.export_assigned_vars(frame, assignment_frame)
 
-        # make sure toplevel assignments are added to the context.
-        if frame.toplevel:
-            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))
-                self.writeline('context.vars[%r] = l_%s' % (name, name))
-            else:
-                self.writeline('context.vars.update({')
-                for idx, name in enumerate(assignment_frame.toplevel_assignments):
-                    if idx:
-                        self.write(', ')
-                    self.write('%r: l_%s' % (name, name))
-                self.write('})')
-            if public_names:
-                if len(public_names) == 1:
-                    self.writeline('context.exported_vars.add(%r)' %
-                                   public_names[0])
-                else:
-                    self.writeline('context.exported_vars.update((%s))' %
-                                   ', '.join(imap(repr, public_names)))
+    def visit_AssignBlock(self, node, frame):
+        block_frame = frame.inner()
+        block_frame.inspect(node.body)
+        aliases = self.push_scope(block_frame)
+        self.pull_locals(block_frame)
+        self.buffer(block_frame)
+        self.blockvisit(node.body, block_frame)
+        self.pop_scope(aliases, block_frame)
+
+        assignment_frame = self.make_assignment_frame(frame)
+        self.newline(node)
+        self.visit(node.target, assignment_frame)
+        self.write(' = concat(%s)' % block_frame.buffer)
+        self.export_assigned_vars(frame, assignment_frame)
 
     # -- Expression Visitors
 
diff --git a/debug.py b/debug.py
index 815cc18..3252748 100644
--- a/debug.py
+++ b/debug.py
@@ -12,10 +12,10 @@
 """
 import sys
 import traceback
-from types import TracebackType
+from types import TracebackType, CodeType
 from jinja2.utils import missing, internal_code
 from jinja2.exceptions import TemplateSyntaxError
-from jinja2._compat import iteritems, reraise, code_type
+from jinja2._compat import iteritems, reraise, PY2
 
 # on pypy we can take advantage of transparent proxies
 try:
@@ -245,12 +245,21 @@
                 location = 'block "%s"' % function[6:]
             else:
                 location = 'template'
-        code = code_type(0, code.co_nlocals, code.co_stacksize,
-                         code.co_flags, code.co_code, code.co_consts,
-                         code.co_names, code.co_varnames, filename,
-                         location, code.co_firstlineno,
-                         code.co_lnotab, (), ())
-    except:
+
+        if PY2:
+            code = CodeType(0, code.co_nlocals, code.co_stacksize,
+                            code.co_flags, code.co_code, code.co_consts,
+                            code.co_names, code.co_varnames, filename,
+                            location, code.co_firstlineno,
+                            code.co_lnotab, (), ())
+        else:
+            code = CodeType(0, code.co_kwonlyargcount,
+                            code.co_nlocals, code.co_stacksize,
+                            code.co_flags, code.co_code, code.co_consts,
+                            code.co_names, code.co_varnames, filename,
+                            location, code.co_firstlineno,
+                            code.co_lnotab, (), ())
+    except Exception as e:
         pass
 
     # execute the code and catch the new traceback
@@ -273,11 +282,15 @@
     import ctypes
     from types import TracebackType
 
-    # figure out side of _Py_ssize_t
-    if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
-        _Py_ssize_t = ctypes.c_int64
+    if PY2:
+        # figure out size of _Py_ssize_t for Python 2:
+        if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
+            _Py_ssize_t = ctypes.c_int64
+        else:
+            _Py_ssize_t = ctypes.c_int
     else:
-        _Py_ssize_t = ctypes.c_int
+        # platform ssize_t on Python 3
+        _Py_ssize_t = ctypes.c_ssize_t
 
     # regular python
     class _PyObject(ctypes.Structure):
diff --git a/defaults.py b/defaults.py
index a27cb80..3717a72 100644
--- a/defaults.py
+++ b/defaults.py
@@ -32,7 +32,7 @@
 from jinja2.tests import TESTS as DEFAULT_TESTS
 DEFAULT_NAMESPACE = {
     'range':        range_type,
-    'dict':         lambda **kw: kw,
+    'dict':         dict,
     'lipsum':       generate_lorem_ipsum,
     'cycler':       Cycler,
     'joiner':       Joiner
diff --git a/environment.py b/environment.py
index 45fabad..8b2572b 100644
--- a/environment.py
+++ b/environment.py
@@ -21,8 +21,8 @@
 from jinja2.parser import Parser
 from jinja2.nodes import EvalContext
 from jinja2.optimizer import optimize
-from jinja2.compiler import generate
-from jinja2.runtime import Undefined, new_context
+from jinja2.compiler import generate, CodeGenerator
+from jinja2.runtime import Undefined, new_context, Context
 from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
      TemplatesNotFound, TemplateRuntimeError
 from jinja2.utils import import_string, LRUCache, Markup, missing, \
@@ -90,13 +90,13 @@
 def _environment_sanity_check(environment):
     """Perform a sanity check on the environment."""
     assert issubclass(environment.undefined, Undefined), 'undefined must ' \
-           'be a subclass of undefined because filters depend on it.'
+        'be a subclass of undefined because filters depend on it.'
     assert environment.block_start_string != \
-           environment.variable_start_string != \
-           environment.comment_start_string, 'block, variable and comment ' \
-           'start strings must be different'
+        environment.variable_start_string != \
+        environment.comment_start_string, 'block, variable and comment ' \
+        'start strings must be different'
     assert environment.newline_sequence in ('\r', '\r\n', '\n'), \
-           'newline_sequence set to unknown line ending string.'
+        'newline_sequence set to unknown line ending string.'
     return environment
 
 
@@ -108,16 +108,16 @@
     Modifications on environments after the first template was loaded
     will lead to surprising effects and undefined behavior.
 
-    Here the possible initialization parameters:
+    Here are the possible initialization parameters:
 
         `block_start_string`
-            The string marking the begin of a block.  Defaults to ``'{%'``.
+            The string marking the beginning of a block.  Defaults to ``'{%'``.
 
         `block_end_string`
             The string marking the end of a block.  Defaults to ``'%}'``.
 
         `variable_start_string`
-            The string marking the begin of a print statement.
+            The string marking the beginning of a print statement.
             Defaults to ``'{{'``.
 
         `variable_end_string`
@@ -125,7 +125,7 @@
             ``'}}'``.
 
         `comment_start_string`
-            The string marking the begin of a comment.  Defaults to ``'{#'``.
+            The string marking the beginning of a comment.  Defaults to ``'{#'``.
 
         `comment_end_string`
             The string marking the end of a comment.  Defaults to ``'#}'``.
@@ -136,7 +136,7 @@
 
         `line_comment_prefix`
             If given and a string, this will be used as prefix for line based
-            based comments.  See also :ref:`line-statements`.
+            comments.  See also :ref:`line-statements`.
 
             .. versionadded:: 2.2
 
@@ -180,7 +180,7 @@
 
         `autoescape`
             If set to true the XML/HTML autoescaping feature is enabled by
-            default.  For more details about auto escaping see
+            default.  For more details about autoescaping see
             :class:`~jinja2.utils.Markup`.  As of Jinja 2.4 this can also
             be a callable that is passed the template name and has to
             return `True` or `False` depending on autoescape should be
@@ -193,12 +193,15 @@
             The template loader for this environment.
 
         `cache_size`
-            The size of the cache.  Per default this is ``50`` which means
-            that if more than 50 templates are loaded the loader will clean
+            The size of the cache.  Per default this is ``400`` which means
+            that if more than 400 templates are loaded the loader will clean
             out the least recently used template.  If the cache size is set to
             ``0`` templates are recompiled all the time, if the cache size is
             ``-1`` the cache will not be cleaned.
 
+            .. versionchanged:: 2.8
+               The cache size was increased to 400 from a low 50.
+
         `auto_reload`
             Some loaders load templates from locations where the template
             sources may change (ie: file system or database).  If
@@ -235,6 +238,14 @@
     exception_handler = None
     exception_formatter = None
 
+    #: the class that is used for code generation.  See
+    #: :class:`~jinja2.compiler.CodeGenerator` for more information.
+    code_generator_class = CodeGenerator
+
+    #: the context class thatis used for templates.  See
+    #: :class:`~jinja2.runtime.Context` for more information.
+    context_class = Context
+
     def __init__(self,
                  block_start_string=BLOCK_START_STRING,
                  block_end_string=BLOCK_END_STRING,
@@ -254,7 +265,7 @@
                  finalize=None,
                  autoescape=False,
                  loader=None,
-                 cache_size=50,
+                 cache_size=400,
                  auto_reload=True,
                  bytecode_cache=None):
         # !!Important notice!!
@@ -330,7 +341,7 @@
                 loader=missing, cache_size=missing, auto_reload=missing,
                 bytecode_cache=missing):
         """Create a new overlay environment that shares all the data with the
-        current environment except of cache and the overridden attributes.
+        current environment except for cache and the overridden attributes.
         Extensions cannot be removed for an overlayed environment.  An overlayed
         environment automatically gets all the extensions of the environment it
         is linked to plus optional extra extensions.
@@ -551,7 +562,7 @@
             return self._compile(source, filename)
         except TemplateSyntaxError:
             exc_info = sys.exc_info()
-        self.handle_exception(exc_info, source_hint=source)
+        self.handle_exception(exc_info, source_hint=source_hint)
 
     def compile_expression(self, source, undefined_to_none=True):
         """A handy helper method that returns a callable that accepts keyword
@@ -603,8 +614,8 @@
                           ignore_errors=True, py_compile=False):
         """Finds all the templates the loader can find, compiles them
         and stores them in `target`.  If `zip` is `None`, instead of in a
-        zipfile, the templates will be will be stored in a directory.
-        By default a deflate zip algorithm is used, to switch to
+        zipfile, the templates will be stored in a directory.
+        By default a deflate zip algorithm is used. To switch to
         the stored algorithm, `zip` can be set to ``'stored'``.
 
         `extensions` and `filter_func` are passed to :meth:`list_templates`.
@@ -634,7 +645,8 @@
                 warn(Warning('py_compile has no effect on pypy or Python 3'))
                 py_compile = False
             else:
-                import imp, marshal
+                import imp
+                import marshal
                 py_header = imp.get_magic() + \
                     u'\xff\xff\xff\xff'.encode('iso-8859-15')
 
@@ -716,7 +728,7 @@
             filter_func = lambda x: '.' in x and \
                                     x.rsplit('.', 1)[1] in extensions
         if filter_func is not None:
-            x = ifilter(filter_func, x)
+            x = list(ifilter(filter_func, x))
         return x
 
     def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
@@ -757,14 +769,23 @@
     def _load_template(self, name, globals):
         if self.loader is None:
             raise TypeError('no loader for this environment specified')
+        try:
+            # use abs path for cache key
+            cache_key = self.loader.get_source(self, name)[1]
+        except RuntimeError:
+            # if loader does not implement get_source()
+            cache_key = None
+        # if template is not file, use name for cache key
+        if cache_key is None:
+            cache_key = name
         if self.cache is not None:
-            template = self.cache.get(name)
-            if template is not None and (not self.auto_reload or \
+            template = self.cache.get(cache_key)
+            if template is not None and (not self.auto_reload or
                                          template.is_up_to_date):
                 return template
         template = self.loader.load(self, name, globals)
         if self.cache is not None:
-            self.cache[name] = template
+            self.cache[cache_key] = template
         return template
 
     @internalcode
@@ -866,13 +887,12 @@
     and compatible settings.
 
     >>> template = Template('Hello {{ name }}!')
-    >>> template.render(name='John Doe')
-    u'Hello John Doe!'
-
+    >>> template.render(name='John Doe') == u'Hello John Doe!'
+    True
     >>> stream = template.stream(name='John Doe')
-    >>> stream.next()
-    u'Hello John Doe!'
-    >>> stream.next()
+    >>> next(stream) == u'Hello John Doe!'
+    True
+    >>> next(stream)
     Traceback (most recent call last):
         ...
     StopIteration
@@ -1019,10 +1039,10 @@
         exported template variables from the Python layer:
 
         >>> t = Template('{% macro foo() %}42{% endmacro %}23')
-        >>> unicode(t.module)
-        u'23'
-        >>> t.module.foo()
-        u'42'
+        >>> str(t.module)
+        '23'
+        >>> t.module.foo() == u'42'
+        True
         """
         if self._module is not None:
             return self._module
@@ -1131,7 +1151,9 @@
         """
         close = False
         if isinstance(fp, string_types):
-            fp = open(fp, encoding is None and 'w' or 'wb')
+            if encoding is None:
+                encoding = 'utf-8'
+            fp = open(fp, 'wb')
             close = True
         try:
             if encoding is not None:
diff --git a/ext.py b/ext.py
index c2df12d..562ab50 100644
--- a/ext.py
+++ b/ext.py
@@ -20,7 +20,7 @@
 from jinja2.runtime import concat
 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
 from jinja2.utils import contextfunction, import_string, Markup
-from jinja2._compat import next, with_metaclass, string_types, iteritems
+from jinja2._compat import with_metaclass, string_types, iteritems
 
 
 # the only real useful gettext functions for a Jinja template.  Note
diff --git a/filters.py b/filters.py
index fd0db04..e5c7a1a 100644
--- a/filters.py
+++ b/filters.py
@@ -18,7 +18,7 @@
      unicode_urlencode
 from jinja2.runtime import Undefined
 from jinja2.exceptions import FilterArgumentError
-from jinja2._compat import next, imap, string_types, text_type, iteritems
+from jinja2._compat import imap, string_types, text_type, iteritems
 
 
 _word_re = re.compile(r'\w+(?u)')
@@ -94,7 +94,8 @@
     if itemiter is None:
         return unicode_urlencode(value)
     return u'&'.join(unicode_urlencode(k) + '=' +
-                     unicode_urlencode(v) for k, v in itemiter)
+                     unicode_urlencode(v, for_qs=True)
+                     for k, v in itemiter)
 
 
 @evalcontextfilter
@@ -183,7 +184,7 @@
     uppercase letters, all remaining characters are lowercase.
     """
     rv = []
-    for item in re.compile(r'([-\s]+)(?u)').split(s):
+    for item in re.compile(r'([-\s]+)(?u)').split(soft_unicode(s)):
         if not item:
             continue
         rv.append(item[0].upper() + item[1:].lower())
@@ -204,8 +205,7 @@
             sort the dict by key, case sensitive
 
         {% for item in mydict|dictsort(false, 'value') %}
-            sort the dict by key, case insensitive, sorted
-            normally and ordered by value.
+            sort the dict by value, case insensitive
     """
     if by == 'key':
         pos = 0
@@ -409,7 +409,8 @@
 
 
 @evalcontextfilter
-def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
+def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
+              target=None):
     """Converts URLs in plain text into clickable links.
 
     If you pass the filter an additional integer it will shorten the urls
@@ -420,8 +421,18 @@
 
         {{ mytext|urlize(40, true) }}
             links are shortened to 40 chars and defined with rel="nofollow"
+
+    If *target* is specified, the ``target`` attribute will be added to the
+    ``<a>`` tag:
+
+    .. sourcecode:: jinja
+
+       {{ mytext|urlize(40, target='_blank') }}
+
+    .. versionchanged:: 2.8+
+       The *target* parameter was added.
     """
-    rv = urlize(value, trim_url_limit, nofollow)
+    rv = urlize(value, trim_url_limit, nofollow, target)
     if eval_ctx.autoescape:
         rv = Markup(rv)
     return rv
@@ -456,25 +467,22 @@
 
     .. sourcecode:: jinja
 
-        {{ "foo bar"|truncate(5) }}
+        {{ "foo bar baz"|truncate(9) }}
             -> "foo ..."
-        {{ "foo bar"|truncate(5, True) }}
-            -> "foo b..."
+        {{ "foo bar baz"|truncate(9, True) }}
+            -> "foo ba..."
+
     """
     if len(s) <= length:
         return s
     elif killwords:
-        return s[:length] + end
-    words = s.split(' ')
-    result = []
-    m = 0
-    for word in words:
-        m += len(word) + 1
-        if m > length:
-            break
-        result.append(word)
-    result.append(end)
-    return u' '.join(result)
+        return s[:length - len(end)] + end
+
+    result = s[:length - len(end)].rsplit(' ', 1)[0]
+    if len(result) < length:
+        result += ' '
+    return result + end
+
 
 @environmentfilter
 def do_wordwrap(environment, s, width=79, break_long_words=True,
@@ -503,13 +511,16 @@
     return len(_word_re.findall(s))
 
 
-def do_int(value, default=0):
+def do_int(value, default=0, base=10):
     """Convert the value into an integer. If the
     conversion doesn't work it will return ``0``. You can
-    override this default using the first parameter.
+    override this default using the first parameter. You
+    can also override the default base (10) in the second
+    parameter, which handles input with prefixes such as
+    0b, 0o and 0x for bases 2, 8 and 16 respectively.
     """
     try:
-        return int(value)
+        return int(value, base)
     except (TypeError, ValueError):
         # this quirk is necessary so that "42.23"|int gives 42.
         try:
@@ -612,7 +623,6 @@
         {%- endfor %}
         </table>
     """
-    result = []
     tmp = []
     for item in value:
         if len(tmp) == linecount:
@@ -753,7 +763,7 @@
 
 
 def do_reverse(value):
-    """Reverse the object or return an iterator the iterates over it the other
+    """Reverse the object or return an iterator that iterates over it the other
     way round.
     """
     if isinstance(value, string_types):
@@ -772,7 +782,7 @@
 @environmentfilter
 def do_attr(environment, obj, name):
     """Get an attribute of an object.  ``foo|attr("bar")`` works like
-    ``foo["bar"]`` just that always an attribute is returned and items are not
+    ``foo.bar`` just that always an attribute is returned and items are not
     looked up.
 
     See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
@@ -842,14 +852,15 @@
 
 @contextfilter
 def do_select(*args, **kwargs):
-    """Filters a sequence of objects by appying a test to either the object
-    or the attribute and only selecting the ones with the test succeeding.
+    """Filters a sequence of objects by applying a test to the object and only
+    selecting the ones with the test succeeding.
 
     Example usage:
 
     .. sourcecode:: jinja
 
         {{ numbers|select("odd") }}
+        {{ numbers|select("odd") }}
 
     .. versionadded:: 2.7
     """
@@ -858,8 +869,8 @@
 
 @contextfilter
 def do_reject(*args, **kwargs):
-    """Filters a sequence of objects by appying a test to either the object
-    or the attribute and rejecting the ones with the test succeeding.
+    """Filters a sequence of objects by applying a test to the object and
+    rejecting the ones with the test succeeding.
 
     Example usage:
 
@@ -874,8 +885,8 @@
 
 @contextfilter
 def do_selectattr(*args, **kwargs):
-    """Filters a sequence of objects by appying a test to either the object
-    or the attribute and only selecting the ones with the test succeeding.
+    """Filters a sequence of objects by applying a test to an attribute of an
+    object and only selecting the ones with the test succeeding.
 
     Example usage:
 
@@ -891,8 +902,8 @@
 
 @contextfilter
 def do_rejectattr(*args, **kwargs):
-    """Filters a sequence of objects by appying a test to either the object
-    or the attribute and rejecting the ones with the test succeeding.
+    """Filters a sequence of objects by applying a test to an attribute of an
+    object or the attribute and rejecting the ones with the test succeeding.
 
     .. sourcecode:: jinja
 
@@ -933,55 +944,53 @@
 
 
 FILTERS = {
+    'abs':                  abs,
     'attr':                 do_attr,
-    'replace':              do_replace,
-    'upper':                do_upper,
-    'lower':                do_lower,
-    'escape':               escape,
-    'e':                    escape,
-    'forceescape':          do_forceescape,
+    'batch':                do_batch,
     'capitalize':           do_capitalize,
-    'title':                do_title,
-    'default':              do_default,
-    'd':                    do_default,
-    'join':                 do_join,
-    'count':                len,
-    'dictsort':             do_dictsort,
-    'sort':                 do_sort,
-    'length':               len,
-    'reverse':              do_reverse,
     'center':               do_center,
-    'indent':               do_indent,
-    'title':                do_title,
-    'capitalize':           do_capitalize,
+    'count':                len,
+    'd':                    do_default,
+    'default':              do_default,
+    'dictsort':             do_dictsort,
+    'e':                    escape,
+    'escape':               escape,
+    'filesizeformat':       do_filesizeformat,
     'first':                do_first,
+    'float':                do_float,
+    'forceescape':          do_forceescape,
+    'format':               do_format,
+    'groupby':              do_groupby,
+    'indent':               do_indent,
+    'int':                  do_int,
+    'join':                 do_join,
     'last':                 do_last,
+    'length':               len,
+    'list':                 do_list,
+    'lower':                do_lower,
     'map':                  do_map,
+    'pprint':               do_pprint,
     'random':               do_random,
     'reject':               do_reject,
     'rejectattr':           do_rejectattr,
-    'filesizeformat':       do_filesizeformat,
-    'pprint':               do_pprint,
-    'truncate':             do_truncate,
-    'wordwrap':             do_wordwrap,
-    'wordcount':            do_wordcount,
-    'int':                  do_int,
-    'float':                do_float,
-    'string':               soft_unicode,
-    'list':                 do_list,
-    'urlize':               do_urlize,
-    'format':               do_format,
-    'trim':                 do_trim,
-    'striptags':            do_striptags,
+    'replace':              do_replace,
+    'reverse':              do_reverse,
+    'round':                do_round,
+    'safe':                 do_mark_safe,
     'select':               do_select,
     'selectattr':           do_selectattr,
     'slice':                do_slice,
-    'batch':                do_batch,
+    'sort':                 do_sort,
+    'string':               soft_unicode,
+    'striptags':            do_striptags,
     'sum':                  do_sum,
-    'abs':                  abs,
-    'round':                do_round,
-    'groupby':              do_groupby,
-    'safe':                 do_mark_safe,
+    'title':                do_title,
+    'trim':                 do_trim,
+    'truncate':             do_truncate,
+    'upper':                do_upper,
+    'urlencode':            do_urlencode,
+    'urlize':               do_urlize,
+    'wordcount':            do_wordcount,
+    'wordwrap':             do_wordwrap,
     'xmlattr':              do_xmlattr,
-    'urlencode':            do_urlencode
 }
diff --git a/get_jinja2.sh b/get_jinja2.sh
index 9502146..0018349 100755
--- a/get_jinja2.sh
+++ b/get_jinja2.sh
@@ -7,15 +7,14 @@
 # Download page:
 # https://pypi.python.org/pypi/Jinja2
 PACKAGE='Jinja2'
-VERSION='2.7.1'
+VERSION='2.8'
+SRC_URL='https://pypi.python.org/packages/f2/2f/0b98b06a345a761bec91a079ccae392d282690c2d8272e708f4d10829e22/Jinja2-2.8.tar.gz'
 PACKAGE_DIR='jinja2'
 
 CHROMIUM_FILES="README.chromium OWNERS get_jinja2.sh"
 EXTRA_FILES='LICENSE AUTHORS'
 REMOVE_FILES='testsuite'
 
-SRC_URL='https://pypi.python.org/packages/source/'
-SRC_URL+="${PACKAGE:0:1}/$PACKAGE/$PACKAGE-$VERSION.tar.gz"
 FILENAME="$(basename $SRC_URL)"
 MD5_FILENAME="$FILENAME.md5"
 SHA512_FILENAME="$FILENAME.sha512"
@@ -29,7 +28,7 @@
 
 function check_hashes {
   # Hashes generated via:
-  # FILENAME=Jinja2-2.7.1.tar.gz
+  # FILENAME=Jinja2-2.8.tar.gz
   # md5sum "$FILENAME" > "$FILENAME.md5"
   # sha512sum "$FILENAME" > "$FILENAME.sha512"
   # unset FILENAME
diff --git a/lexer.py b/lexer.py
index a501285..c8dac21 100644
--- a/lexer.py
+++ b/lexer.py
@@ -20,8 +20,8 @@
 from collections import deque
 from jinja2.exceptions import TemplateSyntaxError
 from jinja2.utils import LRUCache
-from jinja2._compat import next, iteritems, implements_iterator, text_type, \
-     intern
+from jinja2._compat import iteritems, implements_iterator, text_type, \
+     intern, PY2
 
 
 # cache for the lexers. Exists in order to be able to have multiple
@@ -136,8 +136,8 @@
 
 ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
                             TOKEN_COMMENT_END, TOKEN_WHITESPACE,
-                            TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
-                            TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
+                            TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END,
+                            TOKEN_LINECOMMENT])
 ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
                              TOKEN_COMMENT, TOKEN_LINECOMMENT])
 
@@ -578,10 +578,11 @@
                 # we do that for support of semi broken APIs
                 # as datetime.datetime.strftime.  On python 3 this
                 # call becomes a noop thanks to 2to3
-                try:
-                    value = str(value)
-                except UnicodeError:
-                    pass
+                if PY2:
+                    try:
+                        value = value.encode('ascii')
+                    except UnicodeError:
+                        pass
             elif token == 'integer':
                 value = int(value)
             elif token == 'float':
diff --git a/loaders.py b/loaders.py
index a9a2625..44aa392 100644
--- a/loaders.py
+++ b/loaders.py
@@ -141,20 +141,28 @@
 
     The loader takes the path to the templates as string, or if multiple
     locations are wanted a list of them which is then looked up in the
-    given order:
+    given order::
 
     >>> loader = FileSystemLoader('/path/to/templates')
     >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
 
     Per default the template encoding is ``'utf-8'`` which can be changed
     by setting the `encoding` parameter to something else.
+
+    To follow symbolic links, set the *followlinks* parameter to ``True``::
+
+    >>> loader = FileSystemLoader('/path/to/templates', followlinks=True)
+
+    .. versionchanged:: 2.8+
+       The *followlinks* parameter was added.
     """
 
-    def __init__(self, searchpath, encoding='utf-8'):
+    def __init__(self, searchpath, encoding='utf-8', followlinks=False):
         if isinstance(searchpath, string_types):
             searchpath = [searchpath]
         self.searchpath = list(searchpath)
         self.encoding = encoding
+        self.followlinks = followlinks
 
     def get_source(self, environment, template):
         pieces = split_template_path(template)
@@ -169,6 +177,7 @@
                 f.close()
 
             mtime = path.getmtime(filename)
+
             def uptodate():
                 try:
                     return path.getmtime(filename) == mtime
@@ -180,7 +189,8 @@
     def list_templates(self):
         found = set()
         for searchpath in self.searchpath:
-            for dirpath, dirnames, filenames in os.walk(searchpath):
+            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
+            for dirpath, dirnames, filenames in walk_dir:
                 for filename in filenames:
                     template = os.path.join(dirpath, filename) \
                         [len(searchpath):].strip(os.path.sep) \
@@ -281,7 +291,7 @@
 
 class FunctionLoader(BaseLoader):
     """A loader that is passed a function which does the loading.  The
-    function becomes the name of the template passed and has to return either
+    function receives the name of the template and has to return either
     an unicode string with the template source, a tuple in the form ``(source,
     filename, uptodatefunc)`` or `None` if the template does not exist.
 
@@ -349,7 +359,7 @@
     def load(self, environment, name, globals=None):
         loader, local_name = self.get_loader(name)
         try:
-            return loader.load(environment, local_name)
+            return loader.load(environment, local_name, globals)
         except TemplateNotFound:
             # re-raise the exception with the correct fileame here.
             # (the one that includes the prefix)
diff --git a/meta.py b/meta.py
index 3110cff..3dbab7c 100644
--- a/meta.py
+++ b/meta.py
@@ -39,8 +39,8 @@
     >>> from jinja2 import Environment, meta
     >>> env = Environment()
     >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
-    >>> meta.find_undeclared_variables(ast)
-    set(['bar'])
+    >>> meta.find_undeclared_variables(ast) == set(['bar'])
+    True
 
     .. admonition:: Implementation
 
diff --git a/nodes.py b/nodes.py
index c5697e6..d32046c 100644
--- a/nodes.py
+++ b/nodes.py
@@ -12,16 +12,16 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
+import types
 import operator
 
 from collections import deque
 from jinja2.utils import Markup
-from jinja2._compat import next, izip, with_metaclass, text_type, \
-     method_type, function_type
+from jinja2._compat import izip, with_metaclass, text_type
 
 
 #: the types we support for context functions
-_context_function_types = (function_type, method_type)
+_context_function_types = (types.FunctionType, types.MethodType)
 
 
 _binop_to_func = {
@@ -347,6 +347,11 @@
     fields = ('target', 'node')
 
 
+class AssignBlock(Stmt):
+    """Assigns a block to a target."""
+    fields = ('target', 'body')
+
+
 class Expr(Node):
     """Baseclass for all expressions."""
     abstract = True
@@ -746,7 +751,7 @@
 
 
 class Sub(BinExpr):
-    """Substract the right from the left node."""
+    """Subtract the right from the left node."""
     operator = '-'
 
 
diff --git a/parser.py b/parser.py
index f60cd01..d24da18 100644
--- a/parser.py
+++ b/parser.py
@@ -11,10 +11,9 @@
 from jinja2 import nodes
 from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
 from jinja2.lexer import describe_token, describe_token_expr
-from jinja2._compat import next, imap
+from jinja2._compat import imap
 
 
-#: statements that callinto 
 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
                                  'macro', 'include', 'from', 'import',
                                  'set'])
@@ -169,9 +168,12 @@
         """Parse an assign statement."""
         lineno = next(self.stream).lineno
         target = self.parse_assign_target()
-        self.stream.expect('assign')
-        expr = self.parse_tuple()
-        return nodes.Assign(target, expr, lineno=lineno)
+        if self.stream.skip_if('assign'):
+            expr = self.parse_tuple()
+            return nodes.Assign(target, expr, lineno=lineno)
+        body = self.parse_statements(('name:endset',),
+                                     drop_needle=True)
+        return nodes.AssignBlock(target, body, lineno=lineno)
 
     def parse_for(self):
         """Parse a for loop."""
@@ -312,6 +314,8 @@
             arg.set_ctx('param')
             if self.stream.skip_if('assign'):
                 defaults.append(self.parse_expression())
+            elif defaults:
+                self.fail('non-default argument follows default argument')
             args.append(arg)
         self.stream.expect('rparen')
 
@@ -434,8 +438,8 @@
                 ops.append(nodes.Operand(token_type, self.parse_add()))
             elif self.stream.skip_if('name:in'):
                 ops.append(nodes.Operand('in', self.parse_add()))
-            elif self.stream.current.test('name:not') and \
-                 self.stream.look().test('name:in'):
+            elif (self.stream.current.test('name:not') and
+                  self.stream.look().test('name:in')):
                 self.stream.skip(2)
                 ops.append(nodes.Operand('notin', self.parse_add()))
             else:
@@ -771,7 +775,7 @@
             else:
                 ensure(dyn_args is None and dyn_kwargs is None)
                 if self.stream.current.type == 'name' and \
-                    self.stream.look().type == 'assign':
+                   self.stream.look().type == 'assign':
                     key = self.stream.current.value
                     self.stream.skip(2)
                     value = self.parse_expression()
@@ -824,11 +828,11 @@
         kwargs = []
         if self.stream.current.type == 'lparen':
             args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
-        elif self.stream.current.type in ('name', 'string', 'integer',
-                                          'float', 'lparen', 'lbracket',
-                                          'lbrace') and not \
-             self.stream.current.test_any('name:else', 'name:or',
-                                          'name:and'):
+        elif (self.stream.current.type in ('name', 'string', 'integer',
+                                           'float', 'lparen', 'lbracket',
+                                           'lbrace') and not
+              self.stream.current.test_any('name:else', 'name:or',
+                                           'name:and')):
             if self.stream.current.test('name:is'):
                 self.fail('You cannot chain multiple tests with is')
             args = [self.parse_expression()]
diff --git a/runtime.py b/runtime.py
index 7791c64..685a12d 100644
--- a/runtime.py
+++ b/runtime.py
@@ -8,13 +8,15 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD.
 """
+import sys
+
 from itertools import chain
 from jinja2.nodes import EvalContext, _context_function_types
 from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
      internalcode, object_type_repr
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
      TemplateNotFound
-from jinja2._compat import next, imap, text_type, iteritems, \
+from jinja2._compat import imap, text_type, iteritems, \
      implements_iterator, implements_to_string, string_types, PY2
 
 
@@ -22,7 +24,7 @@
 __all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
            'TemplateRuntimeError', 'missing', 'concat', 'escape',
            'markup_join', 'unicode_join', 'to_string', 'identity',
-           'TemplateNotFound']
+           'TemplateNotFound', 'make_logging_undefined']
 
 #: the name of the function that is used to convert something into
 #: a string.  We can just use the text type here.
@@ -67,7 +69,8 @@
         for key, value in iteritems(locals):
             if key[:2] == 'l_' and value is not missing:
                 parent[key[2:]] = value
-    return Context(environment, parent, template_name, blocks)
+    return environment.context_class(environment, parent, template_name,
+                                     blocks)
 
 
 class TemplateReference(object):
@@ -171,7 +174,7 @@
         :func:`environmentfunction`.
         """
         if __debug__:
-            __traceback_hide__ = True
+            __traceback_hide__ = True  # noqa
 
         # Allow callable classes to take a context
         fn = __obj.__call__
@@ -339,10 +342,11 @@
             # if was not possible to get the length of the iterator when
             # the loop context was created (ie: iterating over a generator)
             # we have to convert the iterable into a sequence and use the
-            # length of that.
+            # length of that + the number of iterations so far.
             iterable = tuple(self._iterator)
             self._iterator = iter(iterable)
-            self._length = len(iterable) + self.index0 + 1
+            iterations_done = self.index0 + 2
+            self._length = len(iterable) + iterations_done
         return self._length
 
     def __repr__(self):
@@ -441,7 +445,7 @@
 @implements_to_string
 class Undefined(object):
     """The default undefined type.  This undefined type can be printed and
-    iterated over, but every other access will raise an :exc:`UndefinedError`:
+    iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`:
 
     >>> foo = Undefined(name='foo')
     >>> str(foo)
@@ -451,7 +455,7 @@
     >>> foo + 42
     Traceback (most recent call last):
       ...
-    UndefinedError: 'foo' is undefined
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     """
     __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
                  '_undefined_exception')
@@ -465,7 +469,7 @@
     @internalcode
     def _fail_with_undefined_error(self, *args, **kwargs):
         """Regular callback function for undefined objects that raises an
-        `UndefinedError` on call.
+        `jinja2.exceptions.UndefinedError` on call.
         """
         if self._undefined_hint is None:
             if self._undefined_obj is missing:
@@ -491,10 +495,10 @@
         return self._fail_with_undefined_error()
 
     __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
-    __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
-    __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
-    __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
-    __float__ = __complex__ = __pow__ = __rpow__ = \
+        __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
+        __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
+        __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
+        __float__ = __complex__ = __pow__ = __rpow__ = \
         _fail_with_undefined_error
 
     def __eq__(self, other):
@@ -518,11 +522,93 @@
 
     def __nonzero__(self):
         return False
+    __bool__ = __nonzero__
 
     def __repr__(self):
         return 'Undefined'
 
 
+def make_logging_undefined(logger=None, base=None):
+    """Given a logger object this returns a new undefined class that will
+    log certain failures.  It will log iterations and printing.  If no
+    logger is given a default logger is created.
+
+    Example::
+
+        logger = logging.getLogger(__name__)
+        LoggingUndefined = make_logging_undefined(
+            logger=logger,
+            base=Undefined
+        )
+
+    .. versionadded:: 2.8
+
+    :param logger: the logger to use.  If not provided, a default logger
+                   is created.
+    :param base: the base class to add logging functionality to.  This
+                 defaults to :class:`Undefined`.
+    """
+    if logger is None:
+        import logging
+        logger = logging.getLogger(__name__)
+        logger.addHandler(logging.StreamHandler(sys.stderr))
+    if base is None:
+        base = Undefined
+
+    def _log_message(undef):
+        if undef._undefined_hint is None:
+            if undef._undefined_obj is missing:
+                hint = '%s is undefined' % undef._undefined_name
+            elif not isinstance(undef._undefined_name, string_types):
+                hint = '%s has no element %s' % (
+                    object_type_repr(undef._undefined_obj),
+                    undef._undefined_name)
+            else:
+                hint = '%s has no attribute %s' % (
+                    object_type_repr(undef._undefined_obj),
+                    undef._undefined_name)
+        else:
+            hint = undef._undefined_hint
+        logger.warning('Template variable warning: %s', hint)
+
+    class LoggingUndefined(base):
+
+        def _fail_with_undefined_error(self, *args, **kwargs):
+            try:
+                return base._fail_with_undefined_error(self, *args, **kwargs)
+            except self._undefined_exception as e:
+                logger.error('Template variable error: %s', str(e))
+                raise e
+
+        def __str__(self):
+            rv = base.__str__(self)
+            _log_message(self)
+            return rv
+
+        def __iter__(self):
+            rv = base.__iter__(self)
+            _log_message(self)
+            return rv
+
+        if PY2:
+            def __nonzero__(self):
+                rv = base.__nonzero__(self)
+                _log_message(self)
+                return rv
+
+            def __unicode__(self):
+                rv = base.__unicode__(self)
+                _log_message(self)
+                return rv
+        else:
+            def __bool__(self):
+                rv = base.__bool__(self)
+                _log_message(self)
+                return rv
+
+    return LoggingUndefined
+
+
 @implements_to_string
 class DebugUndefined(Undefined):
     """An undefined that returns the debug info when printed.
@@ -535,7 +621,7 @@
     >>> foo + 42
     Traceback (most recent call last):
       ...
-    UndefinedError: 'foo' is undefined
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     """
     __slots__ = ()
 
@@ -560,15 +646,15 @@
     >>> str(foo)
     Traceback (most recent call last):
       ...
-    UndefinedError: 'foo' is undefined
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     >>> not foo
     Traceback (most recent call last):
       ...
-    UndefinedError: 'foo' is undefined
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     >>> foo + 42
     Traceback (most recent call last):
       ...
-    UndefinedError: 'foo' is undefined
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     """
     __slots__ = ()
     __iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \
diff --git a/sandbox.py b/sandbox.py
index da479c1..7e40ab3 100644
--- a/sandbox.py
+++ b/sandbox.py
@@ -12,19 +12,25 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD.
 """
+import types
 import operator
 from jinja2.environment import Environment
 from jinja2.exceptions import SecurityError
-from jinja2._compat import string_types, function_type, method_type, \
-     traceback_type, code_type, frame_type, generator_type, PY2
+from jinja2._compat import string_types, PY2
 
 
 #: maximum number of items a range may produce
 MAX_RANGE = 100000
 
 #: attributes of function objects that are considered unsafe.
-UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
-                                  'func_defaults', 'func_globals'])
+if PY2:
+    UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
+                                      'func_defaults', 'func_globals'])
+else:
+    # On versions > python 2 the special attributes on functions are gone,
+    # but they remain on methods and generators for whatever reason.
+    UNSAFE_FUNCTION_ATTRIBUTES = set()
+
 
 #: unsafe method attributes.  function attributes are unsafe for methods too
 UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
@@ -32,11 +38,6 @@
 #: unsafe generator attirbutes.
 UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
 
-# On versions > python 2 the special attributes on functions are gone,
-# but they remain on methods and generators for whatever reason.
-if not PY2:
-    UNSAFE_FUNCTION_ATTRIBUTES = set()
-
 import warnings
 
 # make sure we don't warn in python 2.6 about stuff we don't care about
@@ -124,26 +125,24 @@
     :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
 
     >>> from jinja2.sandbox import is_internal_attribute
-    >>> is_internal_attribute(lambda: None, "func_code")
-    True
-    >>> is_internal_attribute((lambda x:x).func_code, 'co_code')
+    >>> is_internal_attribute(str, "mro")
     True
     >>> is_internal_attribute(str, "upper")
     False
     """
-    if isinstance(obj, function_type):
+    if isinstance(obj, types.FunctionType):
         if attr in UNSAFE_FUNCTION_ATTRIBUTES:
             return True
-    elif isinstance(obj, method_type):
+    elif isinstance(obj, types.MethodType):
         if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
            attr in UNSAFE_METHOD_ATTRIBUTES:
             return True
     elif isinstance(obj, type):
         if attr == 'mro':
             return True
-    elif isinstance(obj, (code_type, traceback_type, frame_type)):
+    elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
         return True
-    elif isinstance(obj, generator_type):
+    elif isinstance(obj, types.GeneratorType):
         if attr in UNSAFE_GENERATOR_ATTRIBUTES:
             return True
     return attr.startswith('__')
diff --git a/tests.py b/tests.py
index 48a3e06..bb32349 100644
--- a/tests.py
+++ b/tests.py
@@ -9,9 +9,10 @@
     :license: BSD, see LICENSE for more details.
 """
 import re
+from collections import Mapping
 from jinja2.runtime import Undefined
-from jinja2._compat import text_type, string_types, mapping_types
-
+from jinja2._compat import text_type, string_types, integer_types
+import decimal
 
 number_re = re.compile(r'^-?\d+(\.\d+)?$')
 regex_type = type(number_re)
@@ -82,12 +83,12 @@
 
     .. versionadded:: 2.6
     """
-    return isinstance(value, mapping_types)
+    return isinstance(value, Mapping)
 
 
 def test_number(value):
     """Return true if the variable is a number."""
-    return isinstance(value, (int, float, complex))
+    return isinstance(value, integer_types + (float, complex, decimal.Decimal))
 
 
 def test_sequence(value):
@@ -102,6 +103,28 @@
     return True
 
 
+def test_equalto(value, other):
+    """Check if an object has the same value as another object:
+
+    .. sourcecode:: jinja
+
+        {% if foo.expression is equalto 42 %}
+            the foo attribute evaluates to the constant 42
+        {% endif %}
+
+    This appears to be a useless test as it does exactly the same as the
+    ``==`` operator, but it can be useful when used together with the
+    `selectattr` function:
+
+    .. sourcecode:: jinja
+
+        {{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
+
+    .. versionadded:: 2.8
+    """
+    return value == other
+
+
 def test_sameas(value, other):
     """Check if an object points to the same memory address than another
     object:
@@ -145,5 +168,6 @@
     'iterable':         test_iterable,
     'callable':         test_callable,
     'sameas':           test_sameas,
+    'equalto':          test_equalto,
     'escaped':          test_escaped
 }
diff --git a/utils.py b/utils.py
index ddc47da..cdd4cd3 100644
--- a/utils.py
+++ b/utils.py
@@ -11,8 +11,9 @@
 import re
 import errno
 from collections import deque
+from threading import Lock
 from jinja2._compat import text_type, string_types, implements_iterator, \
-     allocate_lock, url_quote
+     url_quote
 
 
 _word_split_re = re.compile(r'(\s+)')
@@ -149,7 +150,7 @@
     try:
         return open(filename, mode)
     except IOError as e:
-        if e.errno not in (errno.ENOENT, errno.EISDIR):
+        if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL):
             raise
 
 
@@ -182,7 +183,7 @@
         return pformat(obj)
 
 
-def urlize(text, trim_url_limit=None, nofollow=False):
+def urlize(text, trim_url_limit=None, nofollow=False, target=None):
     """Converts any URLs in text into clickable links. Works on http://,
     https:// and www. links. Links can have trailing punctuation (periods,
     commas, close-parens) and leading punctuation (opening parens) and
@@ -193,12 +194,18 @@
 
     If nofollow is True, the URLs in link text will get a rel="nofollow"
     attribute.
+
+    If target is not None, a target attribute will be added to the link.
     """
     trim_url = lambda x, limit=trim_url_limit: limit is not None \
                          and (x[:limit] + (len(x) >=limit and '...'
                          or '')) or x
     words = _word_split_re.split(text_type(escape(text)))
     nofollow_attr = nofollow and ' rel="nofollow"' or ''
+    if target is not None and isinstance(target, string_types):
+        target_attr = ' target="%s"' % target
+    else:
+        target_attr = ''
     for i, word in enumerate(words):
         match = _punctuation_re.match(word)
         if match:
@@ -213,12 +220,12 @@
                     middle.endswith('.net') or
                     middle.endswith('.com')
                 )):
-                middle = '<a href="http://%s"%s>%s</a>' % (middle,
-                    nofollow_attr, trim_url(middle))
+                middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
+                    nofollow_attr, target_attr, trim_url(middle))
             if middle.startswith('http://') or \
                middle.startswith('https://'):
-                middle = '<a href="%s"%s>%s</a>' % (middle,
-                    nofollow_attr, trim_url(middle))
+                middle = '<a href="%s"%s%s>%s</a>' % (middle,
+                    nofollow_attr, target_attr, trim_url(middle))
             if '@' in middle and not middle.startswith('www.') and \
                not ':' in middle and _simple_email_re.match(middle):
                 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
@@ -228,7 +235,7 @@
 
 
 def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
-    """Generate some lorem impsum for the template."""
+    """Generate some lorem ipsum for the template."""
     from jinja2.constants import LOREM_IPSUM_WORDS
     from random import choice, randrange
     words = LOREM_IPSUM_WORDS.split()
@@ -276,7 +283,7 @@
     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
 
 
-def unicode_urlencode(obj, charset='utf-8'):
+def unicode_urlencode(obj, charset='utf-8', for_qs=False):
     """URL escapes a single bytestring or unicode string with the
     given charset if applicable to URL safe quoting under all rules
     that need to be considered under all supported Python versions.
@@ -288,7 +295,11 @@
         obj = text_type(obj)
     if isinstance(obj, text_type):
         obj = obj.encode(charset)
-    return text_type(url_quote(obj))
+    safe = for_qs and b'' or b'/'
+    rv = text_type(url_quote(obj, safe))
+    if for_qs:
+        rv = rv.replace('%20', '+')
+    return rv
 
 
 class LRUCache(object):
@@ -309,7 +320,7 @@
         self._popleft = self._queue.popleft
         self._pop = self._queue.pop
         self._remove = self._queue.remove
-        self._wlock = allocate_lock()
+        self._wlock = Lock()
         self._append = self._queue.append
 
     def __getstate__(self):