Merge pull request #78 from SimonSapin/patch-2

Typo fix
diff --git a/CHANGES b/CHANGES
index 84da684..6539c5f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,12 @@
   separately in order to work in combination with module loaders as
   advertised.
 - Fixed filesizeformat.
+- Added a non-silent option for babel extraction.
+- Added `urlencode` filter that automatically quotes values for
+  URL safe usage with utf-8 as only supported encoding.  If applications
+  want to change this encoding they can override the filter.
+- Accessing `last` on the loop context no longer causes the iterator
+  to be consumed into a list.
 
 Version 2.6
 -----------
diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc
index 35d6d23..2f5c5e8 100644
--- a/docs/contents.rst.inc
+++ b/docs/contents.rst.inc
@@ -23,7 +23,7 @@
    changelog
 
 If you can't find the information you're looking for, have a look at the
-index of try to find it using the search function:
+index or try to find it using the search function:
 
 * :ref:`genindex`
 * :ref:`search`
diff --git a/docs/extensions.rst b/docs/extensions.rst
index c6b6ec9..3878d8c 100644
--- a/docs/extensions.rst
+++ b/docs/extensions.rst
@@ -34,7 +34,7 @@
 
 After enabling dummy `_` function that forwards calls to `gettext` is added
 to the environment globals.  An internationalized application then has to
-provide at least an `gettext` and optoinally a `ngettext` function into the
+provide at least an `gettext` and optionally a `ngettext` function into the
 namespace.  Either globally or for each rendering.
 
 Environment Methods
@@ -219,7 +219,7 @@
 useful, another one would be fragment caching.
 
 When writing extensions you have to keep in mind that you are working with the
-Jinja2 template compiler which does not validate the node tree you are possing
+Jinja2 template compiler which does not validate the node tree you are passing
 to it.  If the AST is malformed you will get all kinds of compiler or runtime
 errors that are horrible to debug.  Always make sure you are using the nodes
 you create correctly.  The API documentation below shows which nodes exist and
diff --git a/docs/index.rst b/docs/index.rst
index 7225e1c..c8964f6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -28,7 +28,7 @@
 .. include:: contents.rst.inc
 
 If you can't find the information you're looking for, have a look at the
-index of try to find it using the search function:
+index or try to find it using the search function:
 
 * :ref:`genindex`
 * :ref:`search`
diff --git a/docs/integration.rst b/docs/integration.rst
index 1875711..e5c76dc 100644
--- a/docs/integration.rst
+++ b/docs/integration.rst
@@ -38,6 +38,15 @@
 of import paths as `extensions` value.  The i18n extension is added
 automatically.
 
+.. versionchanged:: 2.7
+
+   Until 2.7 template syntax errors were always ignored.  This was done
+   since many people are dropping non template html files into the
+   templates folder and it would randomly fail.  The assumption was that
+   testsuites will catch syntax errors in templates anyways.  If you don't
+   want that behavior you can add ``silent=false`` to the settings and
+   exceptions are propagated.
+
 .. _mapping file: http://babel.edgewall.org/wiki/Documentation/messages.html#extraction-method-mapping-and-configuration
 
 Pylons
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 5ba6efd..206756f 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -552,6 +552,10 @@
        The `newstyle_gettext` flag can be set to `True` to enable newstyle
        gettext calls.
 
+    .. versionchanged:: 2.7
+       A `silent` option can now be provided.  If set to `False` template
+       syntax errors are propagated instead of being ignored.
+
     :param fileobj: the file-like object the messages should be extracted from
     :param keywords: a list of keywords (i.e. function names) that should be
                      recognized as translation functions
@@ -571,8 +575,10 @@
         extensions.add(InternationalizationExtension)
 
     def getbool(options, key, default=False):
-        options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true')
+        return options.get(key, str(default)).lower() in \
+            ('1', 'on', 'yes', 'true')
 
+    silent = getbool(options, 'silent', True)
     environment = Environment(
         options.get('block_start_string', BLOCK_START_STRING),
         options.get('block_end_string', BLOCK_END_STRING),
@@ -596,6 +602,8 @@
         node = environment.parse(source)
         tokens = list(environment.lex(environment.preprocess(source)))
     except TemplateSyntaxError, e:
+        if not silent:
+            raise
         # skip templates with syntax errors
         return
 
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 352b166..8dd6ff0 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -13,7 +13,8 @@
 from random import choice
 from operator import itemgetter
 from itertools import imap, groupby
-from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
+from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
+     unicode_urlencode
 from jinja2.runtime import Undefined
 from jinja2.exceptions import FilterArgumentError
 
@@ -70,6 +71,26 @@
     return escape(unicode(value))
 
 
+def do_urlencode(value):
+    """Escape strings for use in URLs (uses UTF-8 encoding).  It accepts both
+    dictionaries and regular strings as well as pairwise iterables.
+
+    .. versionadded:: 2.7
+    """
+    itemiter = None
+    if isinstance(value, dict):
+        itemiter = value.iteritems()
+    elif not isinstance(value, basestring):
+        try:
+            itemiter = iter(value)
+        except TypeError:
+            pass
+    if itemiter is None:
+        return unicode_urlencode(value)
+    return u'&'.join(unicode_urlencode(k) + '=' +
+                     unicode_urlencode(v) for k, v in itemiter)
+
+
 @evalcontextfilter
 def do_replace(eval_ctx, s, old, new, count=None):
     """Return a copy of the value with all occurrences of a substring
@@ -797,5 +818,6 @@
     'round':                do_round,
     'groupby':              do_groupby,
     'safe':                 do_mark_safe,
-    'xmlattr':              do_xmlattr
+    'xmlattr':              do_xmlattr,
+    'urlencode':            do_urlencode
 }
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index a4a47a2..5c39984 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -30,6 +30,8 @@
 #: the identity function.  Useful for certain things in the environment
 identity = lambda x: x
 
+_last_iteration = object()
+
 
 def markup_join(seq):
     """Concatenation that escapes if necessary and converts to unicode."""
@@ -270,6 +272,7 @@
     def __init__(self, iterable, recurse=None):
         self._iterator = iter(iterable)
         self._recurse = recurse
+        self._after = self._safe_next()
         self.index0 = -1
 
         # try to get the length of the iterable early.  This must be done
@@ -288,7 +291,7 @@
         return args[self.index0 % len(args)]
 
     first = property(lambda x: x.index0 == 0)
-    last = property(lambda x: x.index0 + 1 == x.length)
+    last = property(lambda x: x._after is _last_iteration)
     index = property(lambda x: x.index0 + 1)
     revindex = property(lambda x: x.length - x.index0)
     revindex0 = property(lambda x: x.length - x.index)
@@ -299,6 +302,12 @@
     def __iter__(self):
         return LoopContextIterator(self)
 
+    def _safe_next(self):
+        try:
+            return next(self._iterator)
+        except StopIteration:
+            return _last_iteration
+
     @internalcode
     def loop(self, iterable):
         if self._recurse is None:
@@ -344,7 +353,11 @@
     def next(self):
         ctx = self.context
         ctx.index0 += 1
-        return next(ctx._iterator), ctx
+        if ctx._after is _last_iteration:
+            raise StopIteration()
+        next_elem = ctx._after
+        ctx._after = ctx._safe_next()
+        return next_elem, ctx
 
 
 class Macro(object):
diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py
index 770bdf2..94bc02a 100644
--- a/jinja2/testsuite/filters.py
+++ b/jinja2/testsuite/filters.py
@@ -367,6 +367,18 @@
         tmpl = env.from_string('{{ "<div>foo</div>" }}')
         assert tmpl.render() == '&lt;div&gt;foo&lt;/div&gt;'
 
+    def test_urlencode(self):
+        env = Environment(autoescape=True)
+        tmpl = env.from_string('{{ "Hello, world!"|urlencode }}')
+        assert tmpl.render() == 'Hello%2C%20world%21'
+        tmpl = env.from_string('{{ o|urlencode }}')
+        assert tmpl.render(o=u"Hello, world\u203d") == "Hello%2C%20world%E2%80%BD"
+        assert tmpl.render(o=(("f", 1),)) == "f=1"
+        assert tmpl.render(o=(('f', 1), ("z", 2))) == "f=1&amp;z=2"
+        assert tmpl.render(o=((u"\u203d", 1),)) == "%E2%80%BD=1"
+        assert tmpl.render(o={u"\u203d": 1}) == "%E2%80%BD=1"
+        assert tmpl.render(o={0: 1}) == "0=1"
+
 
 def suite():
     suite = unittest.TestSuite()
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 49e9e9a..568c63f 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -12,6 +12,10 @@
 import sys
 import errno
 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:
     from dummy_thread import allocate_lock
@@ -63,7 +67,7 @@
     del _test_gen_bug, _error
 
 
-# for python 2.x we create outselves a next() function that does the
+# for python 2.x we create ourselves a next() function that does the
 # basics without exception catching.
 try:
     next = next
@@ -128,7 +132,7 @@
 
 
 def evalcontextfunction(f):
-    """This decoraotr can be used to mark a function or method as an eval
+    """This decorator can be used to mark a function or method as an eval
     context callable.  This is similar to the :func:`contextfunction`
     but instead of passing the context, an evaluation context object is
     passed.  For more information about the eval context, see
@@ -191,7 +195,7 @@
 
 
 def import_string(import_name, silent=False):
-    """Imports an object based on a string.  This use useful if you want to
+    """Imports an object based on a string.  This is useful if you want to
     use import paths as endpoints or something similar.  An import path can
     be specified either in dotted notation (``xml.sax.saxutils.escape``)
     or with a colon as object delimiter (``xml.sax.saxutils:escape``).
@@ -349,6 +353,21 @@
     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
 
 
+def unicode_urlencode(obj, charset='utf-8'):
+    """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.
+
+    If non strings are provided they are converted to their unicode
+    representation first.
+    """
+    if not isinstance(obj, basestring):
+        obj = unicode(obj)
+    if isinstance(obj, unicode):
+        obj = obj.encode(charset)
+    return unicode(url_quote(obj))
+
+
 class LRUCache(object):
     """A simple LRU Cache implementation."""
 
@@ -393,7 +412,7 @@
         return (self.capacity,)
 
     def copy(self):
-        """Return an shallow copy of the instance."""
+        """Return a shallow copy of the instance."""
         rv = self.__class__(self.capacity)
         rv._mapping.update(self._mapping)
         rv._queue = deque(self._queue)
@@ -443,7 +462,7 @@
         """Get an item from the cache. Moves the item up so that it has the
         highest priority then.
 
-        Raise an `KeyError` if it does not exist.
+        Raise a `KeyError` if it does not exist.
         """
         rv = self._mapping[key]
         if self._queue[-1] != key:
@@ -478,7 +497,7 @@
 
     def __delitem__(self, key):
         """Remove an item from the cache dict.
-        Raise an `KeyError` if it does not exist.
+        Raise a `KeyError` if it does not exist.
         """
         self._wlock.acquire()
         try:
@@ -579,7 +598,7 @@
 
 # try markupsafe first, if that fails go with Jinja2's bundled version
 # of markupsafe.  Markupsafe was previously Jinja2's implementation of
-# the Markup object but was moved into a separate package in a patchleve
+# the Markup object but was moved into a separate package in a patchlevel
 # release
 try:
     from markupsafe import Markup, escape, soft_unicode