Merge pull request #315 from lazka/fix-iter-length

Fix loop context length calculation for iterators. Fixes #244
diff --git a/.travis.yml b/.travis.yml
index e96e9b7..3d3fdad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@
   - "2.6"
   - "2.7"
   - "3.3"
+  - "3.4"
 
 install:
   - "python setup.py install"
diff --git a/CHANGES b/CHANGES
index 459fde7..41fbe57 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,8 @@
 - Added support for `followsymlinks` to the file system loader.
 - The truncate filter now counts the length.
 - Added equalto filter that helps with select filters.
+- Changed cache keys to use absolute file names if available
+  instead of load names.
 
 Version 2.7.2
 -------------
diff --git a/Makefile b/Makefile
index 266180e..31945ce 100644
--- a/Makefile
+++ b/Makefile
@@ -14,8 +14,8 @@
 	$(MAKE) -C docs html dirhtml latex
 	$(MAKE) -C docs/_build/latex all-pdf
 	cd docs/_build/; mv html jinja-docs; zip -r jinja-docs.zip jinja-docs; mv jinja-docs html
-	scp -r docs/_build/dirhtml/* pocoo.org:/var/www/jinja.pocoo.org/docs/
-	scp -r docs/_build/latex/Jinja2.pdf pocoo.org:/var/www/jinja.pocoo.org/docs/jinja-docs.pdf
-	scp -r docs/_build/jinja-docs.zip pocoo.org:/var/www/jinja.pocoo.org/docs/
+	scp -r docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/
+	scp -r docs/_build/latex/Jinja2.pdf flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/jinja-docs.pdf
+	scp -r docs/_build/jinja-docs.zip flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/
 
 .PHONY: test
diff --git a/jinja2/bccache.py b/jinja2/bccache.py
index 09ff845..7b368aa 100644
--- a/jinja2/bccache.py
+++ b/jinja2/bccache.py
@@ -211,25 +211,34 @@
         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 == 'n':
+        if os.name == 'nt':
             return tmpdir
         if not hasattr(os, 'getuid'):
-            raise RuntimeError('Cannot determine safe temp directory.  You '
-                               'need to explicitly provide one.')
+            _unsafe_dir()
 
         dirname = '_jinja2-cache-%d' % os.getuid()
         actual_dir = os.path.join(tmpdir, dirname)
+
+        # 448 == 0700
         try:
-            # 448 == 0700
             os.mkdir(actual_dir, 448)
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
-
+        try:
+            os.chmod(actual_dir, 448)
+            if os.stat(actual_dir).st_uid != os.getuid():
+                _unsafe_dir()
+        except OSError:
+            _unsafe_dir()
         return actual_dir
 
     def _get_cache_filename(self, bucket):
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 45fabad..d65059a 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -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
 
 
@@ -111,13 +111,13 @@
     Here 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 ``'#}'``.
@@ -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
@@ -330,7 +330,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 +551,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 +603,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 +634,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')
 
@@ -757,14 +758,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
@@ -1131,7 +1141,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/jinja2/filters.py b/jinja2/filters.py
index 41f91d9..882f915 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -477,16 +477,11 @@
         return s
     elif killwords:
         return s[:length - len(end)] + end
-    words = s.split(' ')
-    result = []
-    m = 0
-    for word in words:
-        m += len(word) + 1
-        if m > (length - len(end)):
-            break
-        result.append(word)
-    result.append(end)
-    return u' '.join(result)
+
+    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,
diff --git a/jinja2/testsuite/loader.py b/jinja2/testsuite/loader.py
index a7350aa..73a7f30 100644
--- a/jinja2/testsuite/loader.py
+++ b/jinja2/testsuite/loader.py
@@ -115,7 +115,8 @@
         log = []
         self.reg_env = Environment(loader=prefix_loader)
         if zip is not None:
-            self.archive = tempfile.mkstemp(suffix='.zip')[1]
+            fd, self.archive = tempfile.mkstemp(suffix='.zip')
+            os.close(fd)
         else:
             self.archive = tempfile.mkdtemp()
         self.reg_env.compile_templates(self.archive, zip=zip,
diff --git a/scripts/make-release.py b/scripts/make-release.py
index 2c2cbb6..c28eb9f 100644
--- a/scripts/make-release.py
+++ b/scripts/make-release.py
@@ -16,6 +16,10 @@
 from datetime import datetime, date
 from subprocess import Popen, PIPE
 
+try:
+    import wheel
+except ImportError:
+    wheel = None
 
 _date_strip_re = re.compile(r'(?<=\d)(st|nd|rd|th)')
 
@@ -88,7 +92,10 @@
 
 
 def build_and_upload():
-    Popen([sys.executable, 'setup.py', 'release', 'sdist', 'upload']).wait()
+    cmd = [sys.executable, 'setup.py', 'release', 'sdist', 'upload']
+    if wheel is not None:
+        cmd.insert(4, 'bdist_wheel')
+    Popen(cmd).wait()
 
 
 def fail(message, *args):
@@ -140,6 +147,10 @@
     if not git_is_clean():
         fail('You have uncommitted changes in git')
 
+    if wheel is None:
+        print ('Warning: You need to install the wheel package '
+               'to upload a wheel distribution.')
+
     set_init_version(version)
     set_setup_version(version)
     make_git_commit('Bump version number to %s', version)
diff --git a/setup.cfg b/setup.cfg
index 2d74c58..b0c20ba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,6 @@
+[wheel]
+universal = 1
+
 [egg_info]
 tag_build = dev
 tag_date = true
diff --git a/setup.py b/setup.py
index d4acf24..6943f53 100644
--- a/setup.py
+++ b/setup.py
@@ -40,21 +40,6 @@
 from setuptools import setup
 
 
-# ignore the old '--with-speedups' flag
-try:
-    speedups_pos = sys.argv.index('--with-speedups')
-except ValueError:
-    pass
-else:
-    del sys.argv[speedups_pos]
-    sys.stderr.write('*' * 74 + '\n')
-    sys.stderr.write('WARNING:\n')
-    sys.stderr.write('  the --with-speedups flag is deprecated\n')
-    sys.stderr.write('  For the actual speedups install the MarkupSafe '
-                     'package.\n')
-    sys.stderr.write('*' * 74 + '\n')
-
-
 setup(
     name='Jinja2',
     version='2.8-dev',
diff --git a/tox.ini b/tox.ini
index 4bad758..1209f05 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py26, py27, pypy, py33
+envlist = py26, py27, pypy, py33, py34
 
 [testenv]
 commands = python run-tests.py