Merge remote-tracking branch 'ThomasWaldmann/sprint-branch' into sprint-branch
diff --git a/CHANGES b/CHANGES
index 7b204da..e4aa398 100644
--- a/CHANGES
+++ b/CHANGES
@@ -17,6 +17,9 @@
to be consumed into a list.
- Python requirement changed: 2.6, 2.7 or >= 3.3 are required now,
supported by same source code, using the "six" compatibility library.
+- Allow `contextfunction` and other decorators to be applied to `__call__`.
+- Added support for changing from newline to different signs in the `wordwrap`
+ filter.
Version 2.6
-----------
diff --git a/docs/faq.rst b/docs/faq.rst
index d066bff..5c80d33 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -174,7 +174,7 @@
Python 2.3 support you either have to use `Jinja 1`_ or other templating
engines that still support 2.3.
-My Macros are overriden by something
+My Macros are overridden by something
------------------------------------
In some situations the Jinja scoping appears arbitrary:
diff --git a/docs/templates.rst b/docs/templates.rst
index 790b95e..e121033 100644
--- a/docs/templates.rst
+++ b/docs/templates.rst
@@ -862,7 +862,7 @@
</dl>
<p>{{ textarea('comment') }}</p>
-Macros and variables starting with one ore more underscores are private and
+Macros and variables starting with one or more underscores are private and
cannot be imported.
.. versionchanged:: 2.4
diff --git a/docs/tricks.rst b/docs/tricks.rst
index 566575e..5427a3e 100644
--- a/docs/tricks.rst
+++ b/docs/tricks.rst
@@ -75,7 +75,7 @@
<ul id="navigation">
{% for href, id, caption in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif
- %}><a href="{{ href|e }}">{{ caption|e }}</a>/li>
+ %}><a href="{{ href|e }}">{{ caption|e }}</a></li>
{% endfor %}
</ul>
...
diff --git a/ext/Vim/jinja.vim b/ext/Vim/jinja.vim
index 894dcc4..503aec6 100644
--- a/ext/Vim/jinja.vim
+++ b/ext/Vim/jinja.vim
@@ -49,14 +49,14 @@
syn keyword jinjaStatement containedin=jinjaTagBlock contained block skipwhite nextgroup=jinjaBlockName
" Variable Names
-syn match jinjaVariable containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained skipwhite /[a-zA-Z_][a-zA-Z0-9_]*/
+syn match jinjaVariable containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[a-zA-Z_][a-zA-Z0-9_]*/
syn keyword jinjaSpecial containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained false true none False True None loop super caller varargs kwargs
" Filters
-syn match jinjaOperator "|" containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained nextgroup=jinjaFilter
-syn match jinjaFilter contained skipwhite /[a-zA-Z_][a-zA-Z0-9_]*/
-syn match jinjaFunction contained skipwhite /[a-zA-Z_][a-zA-Z0-9_]*/
-syn match jinjaBlockName contained skipwhite /[a-zA-Z_][a-zA-Z0-9_]*/
+syn match jinjaOperator "|" containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained skipwhite nextgroup=jinjaFilter
+syn match jinjaFilter contained /[a-zA-Z_][a-zA-Z0-9_]*/
+syn match jinjaFunction contained /[a-zA-Z_][a-zA-Z0-9_]*/
+syn match jinjaBlockName contained /[a-zA-Z_][a-zA-Z0-9_]*/
" Jinja template constants
syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/"/ skip=/\\"/ end=/"/
@@ -73,7 +73,7 @@
syn region jinjaNested matchgroup=jinjaOperator start="(" end=")" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
syn region jinjaNested matchgroup=jinjaOperator start="\[" end="\]" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
syn region jinjaNested matchgroup=jinjaOperator start="{" end="}" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
-syn region jinjaTagBlock matchgroup=jinjaTagDelim start=/{%-\?/ end=/-\?%}/ skipwhite containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment
+syn region jinjaTagBlock matchgroup=jinjaTagDelim start=/{%-\?/ end=/-\?%}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment
syn region jinjaVarBlock matchgroup=jinjaVarDelim start=/{{-\?/ end=/-\?}}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment
@@ -86,10 +86,10 @@
" Block start keywords. A bit tricker. We only highlight at the start of a
" tag block and only if the name is not followed by a comma or equals sign
" which usually means that we have to deal with an assignment.
-syn match jinjaStatement containedin=jinjaTagBlock contained skipwhite /\({%-\?\s*\)\@<=\<[a-zA-Z_][a-zA-Z0-9_]*\>\(\s*[,=]\)\@!/
+syn match jinjaStatement containedin=jinjaTagBlock contained /\({%-\?\s*\)\@<=\<[a-zA-Z_][a-zA-Z0-9_]*\>\(\s*[,=]\)\@!/
" and context modifiers
-syn match jinjaStatement containedin=jinjaTagBlock contained /\<with\(out\)\?\s\+context\>/ skipwhite
+syn match jinjaStatement containedin=jinjaTagBlock contained /\<with\(out\)\?\s\+context\>/
" Define the default highlighting.
diff --git a/ext/django2jinja/django2jinja.py b/ext/django2jinja/django2jinja.py
index 6d9e76c..ad9627f 100644
--- a/ext/django2jinja/django2jinja.py
+++ b/ext/django2jinja/django2jinja.py
@@ -609,7 +609,7 @@
@node(core_tags.WithNode)
def with_block(writer, node):
writer.warn('with block expanded into set statement. This could cause '
- 'variables following that block to be overriden.', node)
+ 'variables following that block to be overridden.', node)
writer.start_block()
writer.write('set %s = ' % node.name)
writer.node(node.var)
diff --git a/jinja2/_compat.py b/jinja2/_compat.py
index a738757..6318f0b 100644
--- a/jinja2/_compat.py
+++ b/jinja2/_compat.py
@@ -17,3 +17,8 @@
unichr = unichr # py2
except NameError:
unichr = chr # py3
+
+try:
+ range_type = xrange
+except NameError:
+ range_type = range
diff --git a/jinja2/_markupsafe/__init__.py b/jinja2/_markupsafe/__init__.py
index 49775f6..5ffb57d 100644
--- a/jinja2/_markupsafe/__init__.py
+++ b/jinja2/_markupsafe/__init__.py
@@ -11,7 +11,6 @@
import re
import six
from six.moves import map
-from six.moves import zip
from jinja2._compat import unichr
__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
diff --git a/jinja2/bccache.py b/jinja2/bccache.py
index 6638bbb..b7b637f 100644
--- a/jinja2/bccache.py
+++ b/jinja2/bccache.py
@@ -19,6 +19,7 @@
import marshal
import tempfile
from six.moves import cPickle as pickle
+from six import BytesIO
import fnmatch
try:
from hashlib import sha1
@@ -28,12 +29,10 @@
# marshal works better on 3.x, one hack less required
-if sys.version_info[0] > 3:
- from io import BytesIO
+if sys.version_info[0] >= 3:
marshal_dump = marshal.dump
marshal_load = marshal.load
else:
- from cStringIO import StringIO as BytesIO
def marshal_dump(code, f):
if isinstance(f, file):
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 1b1b5a7..ec66908 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -15,10 +15,9 @@
from jinja2.visitor import NodeVisitor
from jinja2.exceptions import TemplateAssertionError
from jinja2.utils import Markup, concat, escape, is_python_keyword
+from jinaj2._compat import range_type
import six
-from six.moves import cStringIO as StringIO
-from six.moves import map, zip
-from six.moves import xrange
+from six.moves import cStringIO as StringIO, map
operators = {
@@ -32,14 +31,6 @@
'notin': 'not in'
}
-try:
- exec('(0 if 0 else 0)')
-except SyntaxError:
- have_condexpr = False
-else:
- have_condexpr = True
-
-
# what method to iterate over items do we want to use for dict iteration
# in generated code? on 2.x let's go with iteritems, on 3.x with items
if hasattr(dict, 'iteritems'):
@@ -76,7 +67,8 @@
"""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, float, complex, xrange, Markup) + six.string_types):
+ 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:
@@ -669,16 +661,16 @@
# it without aliasing all the variables.
# this could be fixed in Python 3 where we have the nonlocal
# keyword or if we switch to bytecode generation
- overriden_closure_vars = (
+ overridden_closure_vars = (
func_frame.identifiers.undeclared &
func_frame.identifiers.declared &
(func_frame.identifiers.declared_locally |
func_frame.identifiers.declared_parameter)
)
- if overriden_closure_vars:
+ if overridden_closure_vars:
self.fail('It\'s not possible to set and access variables '
'derived from an outer scope! (affects: %s)' %
- ', '.join(sorted(overriden_closure_vars)), node.lineno)
+ ', '.join(sorted(overridden_closure_vars)), node.lineno)
# remove variables from a closure from the frame's undeclared
# identifiers.
@@ -1561,22 +1553,13 @@
'expression on %s evaluated to false and '
'no else section was defined.' % self.position(node)))
- if not have_condexpr:
- self.write('((')
- self.visit(node.test, frame)
- self.write(') and (')
- self.visit(node.expr1, frame)
- self.write(',) or (')
- write_expr2()
- self.write(',))[0]')
- else:
- self.write('(')
- self.visit(node.expr1, frame)
- self.write(' if ')
- self.visit(node.test, frame)
- self.write(' else ')
- write_expr2()
- self.write(')')
+ self.write('(')
+ self.visit(node.expr1, frame)
+ self.write(' if ')
+ self.visit(node.test, frame)
+ self.write(' else ')
+ write_expr2()
+ self.write(')')
def visit_Call(self, node, frame, forward_caller=False):
if self.environment.sandboxed:
diff --git a/jinja2/environment.py b/jinja2/environment.py
index d19ae15..58573a2 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -1069,7 +1069,7 @@
"""
close = False
if isinstance(fp, six.string_types):
- fp = file(fp, 'w')
+ fp = open(fp, encoding is None and 'w' or 'wb')
close = True
try:
if encoding is not None:
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
index f38d347..813b7fb 100644
--- a/jinja2/exceptions.py
+++ b/jinja2/exceptions.py
@@ -9,7 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
import six
-from six.moves import map, zip
+from six.moves import map
class TemplateError(Exception):
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 0652a34..495e643 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -10,11 +10,10 @@
:copyright: (c) 2010 by the Jinja Team.
:license: BSD.
"""
-from collections import deque
from jinja2 import nodes
from jinja2.defaults import *
from jinja2.environment import Environment
-from jinja2.runtime import Undefined, concat
+from jinja2.runtime import concat
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
from jinja2.utils import contextfunction, import_string, Markup
import six
diff --git a/jinja2/filters.py b/jinja2/filters.py
index d137fc5..9da1195 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -18,7 +18,7 @@
from jinja2.runtime import Undefined
from jinja2.exceptions import FilterArgumentError
import six
-from six.moves import map, zip
+from six.moves import map
_word_re = re.compile(r'\w+(?u)')
@@ -472,15 +472,23 @@
return u' '.join(result)
@environmentfilter
-def do_wordwrap(environment, s, width=79, break_long_words=True):
+def do_wordwrap(environment, s, width=79, break_long_words=True,
+ wrapstring=None):
"""
Return a copy of the string passed to the filter wrapped after
``79`` characters. You can override this default using the first
parameter. If you set the second parameter to `false` Jinja will not
- split words apart if they are longer than `width`.
+ split words apart if they are longer than `width`. By default, the newlines
+ will be the default newlines for the environment, but this can be changed
+ using the wrapstring keyword argument.
+
+ .. versionadded:: 2.7
+ Added support for the `wrapstring` parameter.
"""
+ if not wrapstring:
+ wrapstring = environment.newline_sequence
import textwrap
- return environment.newline_sequence.join(textwrap.wrap(s, width=width, expand_tabs=False,
+ return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False,
replace_whitespace=False,
break_long_words=break_long_words))
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 809dad0..08fe872 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -19,7 +19,7 @@
except ImportError:
from sha import new as sha1
from jinja2.exceptions import TemplateNotFound
-from jinja2.utils import LRUCache, open_if_exists, internalcode
+from jinja2.utils import open_if_exists, internalcode
def split_template_path(template):
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 520e88f..953027c 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -12,7 +12,7 @@
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
from jinja2.lexer import describe_token, describe_token_expr
import six
-from six.moves import map, zip
+from six.moves import map
#: statements that callinto
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index a11095e..9742ece 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -10,8 +10,8 @@
"""
from itertools import chain
from jinja2.nodes import EvalContext, _context_function_types
-from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
- concat, internalcode, object_type_repr
+from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
+ internalcode, object_type_repr
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
import six
@@ -176,6 +176,17 @@
"""
if __debug__:
__traceback_hide__ = True
+
+ # Allow callable classes to take a context
+ if hasattr(__obj, '__call__'):
+ fn = __obj.__call__
+ for fn_type in ('contextfunction',
+ 'evalcontextfunction',
+ 'environmentfunction'):
+ if hasattr(fn, fn_type):
+ __obj = fn
+ break
+
if isinstance(__obj, _context_function_types):
if getattr(__obj, 'contextfunction', 0):
args = (__self,) + args
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index b880250..ed145d5 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -115,7 +115,7 @@
"""Test if the attribute given is an internal python attribute. For
example this function returns `True` for the `func_code` attribute of
python objects. This is useful if the environment method
- :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden.
+ :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
>>> from jinja2.sandbox import is_internal_attribute
>>> is_internal_attribute(lambda: None, "func_code")
diff --git a/jinja2/testsuite/debug.py b/jinja2/testsuite/debug.py
index 17ac991..2588a83 100644
--- a/jinja2/testsuite/debug.py
+++ b/jinja2/testsuite/debug.py
@@ -8,7 +8,6 @@
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
-import sys
import unittest
from jinja2.testsuite import JinjaTestCase, filesystem_loader
diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py
index e74881d..7f6b314 100644
--- a/jinja2/testsuite/filters.py
+++ b/jinja2/testsuite/filters.py
@@ -13,7 +13,7 @@
from jinja2 import Markup, Environment
import six
-from six.moves import map, zip
+from six.moves import map
env = Environment()
diff --git a/jinja2/testsuite/regression.py b/jinja2/testsuite/regression.py
index bac4d60..4198259 100644
--- a/jinja2/testsuite/regression.py
+++ b/jinja2/testsuite/regression.py
@@ -15,8 +15,6 @@
from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
TemplateNotFound, PrefixLoader
import six
-from six.moves import map
-from six.moves import zip
env = Environment()
@@ -250,6 +248,19 @@
else:
assert False, 'expected error here'
+ def test_contextfunction_callable_classes(self):
+ from jinja2.utils import contextfunction
+ class CallableClass(object):
+ @contextfunction
+ def __call__(self, ctx):
+ return ctx.resolve('hello')
+
+ tpl = Template("""{{ callableclass() }}""")
+ output = tpl.render(callableclass = CallableClass(), hello = 'TEST')
+ expected = 'TEST'
+
+ self.assert_equal(output, expected)
+
def suite():
suite = unittest.TestSuite()