diff --git a/docs/switching.rst b/docs/switching.rst index ba3cfb1ff..ee49df7ec 100644 --- a/docs/switching.rst +++ b/docs/switching.rst @@ -177,9 +177,25 @@ operator. Here are some examples:: Loops ~~~~~ -For loops work very similar to Django, the only incompatibility is that in -Jinja2 the special variable for the loop context is called `loop` and not -`forloop` like in Django. +For loops work very similar to Django. Notably, in Jinja2 the special variable for +the loop context is called `loop` and not `forloop` like in Django. + +In addition, the Django `empty` argument is called `else` in Jinja2. For example, the +Django template:: + + {% for item in items %} + {{item}} + {% empty %} + No items! + {% endfor %} + +would be handled in Flask as:: + + {% for item in items %} + {{item}} + {% else %} + No items! + {% endfor %} Cycle ~~~~~ diff --git a/jinja2/compiler.py b/jinja2/compiler.py index b21cb3865..20eb8a64d 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -1068,6 +1068,7 @@ def visit_For(self, node, frame): # make sure the loop variable is a special one and raise a template # assertion error if a loop tries to write to loop if extended_loop: + self.writeline('l_loop = None') loop_frame.identifiers.add_special('loop') for name in node.find_all(nodes.Name): if name.ctx == 'store' and name.name == 'loop': diff --git a/jinja2/filters.py b/jinja2/filters.py index 8dd6ff07f..570263c5d 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -176,7 +176,12 @@ def do_title(s): """Return a titlecased version of the value. I.e. words will start with uppercase letters, all remaining characters are lowercase. """ - return soft_unicode(s).title() + rv = [] + for item in re.compile(r'([-\s]+)(?u)').split(s): + if not item: + continue + rv.append(item[0].upper() + item[1:]) + return ''.join(rv) def do_dictsort(value, case_sensitive=False, by='key'): diff --git a/jinja2/parser.py b/jinja2/parser.py index 21253389d..f01728dca 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -308,6 +308,8 @@ def parse_signature(self, node): while self.stream.current.type != 'rparen': if args: self.stream.expect('comma') + if self.stream.current.type == 'rparen': + break arg = self.parse_assign_target(name_only=True) arg.set_ctx('param') if self.stream.skip_if('assign'): diff --git a/jinja2/testsuite/core_tags.py b/jinja2/testsuite/core_tags.py index 2b5f58016..4ad14ed2d 100644 --- a/jinja2/testsuite/core_tags.py +++ b/jinja2/testsuite/core_tags.py @@ -24,6 +24,10 @@ def test_simple(self): tmpl = env.from_string('{% for item in seq %}{{ item }}{% endfor %}') assert tmpl.render(seq=range(10)) == '0123456789' + def test_embedded_for_in_else(self): + tmpl = env.from_string('{% for item in seq %}{{ loop.index0 }}{% else %}{% for i in range(3) %}{{ i }}{% endfor %}{% endfor %}') + assert tmpl.render(seq=[]) == '012' + def test_else(self): tmpl = env.from_string('{% for item in seq %}XXX{% else %}...{% endfor %}') assert tmpl.render() == '...' @@ -222,6 +226,12 @@ def test_arguments(self): {{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''') assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d' + def test_trailing_comma(self): + tmpl = self.env.from_string('''\ +{% macro m(a, b,) %}{{ a }}|{{ b }}{% endmacro %} +{{ m(1,2,) }}''') + assert tmpl.render() == '1|2' + def test_varargs(self): tmpl = self.env.from_string('''\ {% macro test() %}{{ varargs|join('|') }}{% endmacro %}\ diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py index 94bc02af6..b037e2400 100644 --- a/jinja2/testsuite/filters.py +++ b/jinja2/testsuite/filters.py @@ -193,6 +193,16 @@ def test_string(self): def test_title(self): tmpl = env.from_string('''{{ "foo bar"|title }}''') assert tmpl.render() == "Foo Bar" + tmpl = env.from_string('''{{ "foo's bar"|title }}''') + assert tmpl.render() == "Foo's Bar" + tmpl = env.from_string('''{{ "foo bar"|title }}''') + assert tmpl.render() == "Foo Bar" + tmpl = env.from_string('''{{ "f bar f"|title }}''') + assert tmpl.render() == "F Bar F" + tmpl = env.from_string('''{{ "foo-bar"|title }}''') + assert tmpl.render() == "Foo-Bar" + tmpl = env.from_string('''{{ "foo\tbar"|title }}''') + assert tmpl.render() == "Foo\tBar" def test_truncate(self): tmpl = env.from_string(