From: Armin Ronacher Date: Sun, 19 May 2013 10:18:19 +0000 (+0100) Subject: Merge remote-tracking branch 'kristi/master' X-Git-Tag: 2.7~48 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a65f1eb9065d78fcd0b4871782bbe008b5f8ab7b;p=thirdparty%2Fjinja.git Merge remote-tracking branch 'kristi/master' --- a65f1eb9065d78fcd0b4871782bbe008b5f8ab7b diff --cc CHANGES index c6f95bcc,6539c5fd..036558a6 --- a/CHANGES +++ b/CHANGES @@@ -13,16 -13,8 +13,17 @@@ Version 2. - 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. +- Added `keep-trailing-newline` configuration to environments and + templates to optionally preserve the final trailing newline. - Accessing `last` on the loop context no longer causes the iterator 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. +- Added support for ignoring memcache errors silently. ++- Added support for keeping the trailing newline in templates. Version 2.6 ----------- diff --cc docs/templates.rst index 881e1f52,c1ae657a..6c8eee3f --- a/docs/templates.rst +++ b/docs/templates.rst @@@ -155,12 -155,46 +155,46 @@@ Whitespace Contro In the default configuration, a single trailing newline is stripped if present, and whitespace is not further modified by the template engine. Each whitespace (spaces, tabs, newlines etc.) is returned unchanged. If the --application configures Jinja to `trim_blocks` the first newline after a a - template tag is removed automatically (like in PHP). To keep the - single trailing newline when it is present, configure Jinja to - `keep_trailing_newline`. - - But you can also strip whitespace in templates by hand. If you put an minus ++application configures Jinja to `trim_blocks` the first newline after a + template tag is removed automatically (like in PHP). The `lstrip_blocks` + option can also be set to strip tabs and spaces from the beginning of + line to the start of a block. (Nothing will be stripped if there are + other characters before the start of the block.) + + With both `trim_blocks` and `lstrip_blocks` enabled you can put block tags + on their own lines, and the entire block line will be removed when + rendered, preserving the whitespace of the contents. For example, + without the `trim_blocks` and `lstrip_blocks` options, this template:: + +
+ {% if True %} + yay + {% endif %} +
+ + gets rendered with blank lines inside the div:: + +
+ + yay + +
+ + But with both `trim_blocks` and `lstrip_blocks` enabled, the lines with the + template blocks are removed while preserving the whitespace of the contents:: + +
+ yay +
+ + You can manually disable the `lstrip_blocks` behavior by putting a + plus sign (``+``) at the start of a block:: + +
+ {%+ if something %}yay{% endif %} +
+ + You can also strip whitespace in templates by hand. If you put an minus sign (``-``) to the start or end of an block (for example a for tag), a comment or variable expression you can remove the whitespaces after or before that block:: @@@ -175,6 -209,6 +209,10 @@@ a list of numbers from ``1`` to ``9`` t If :ref:`line-statements` are enabled they strip leading whitespace automatically up to the beginning of the line. ++Jinja2 by default also removes trailing newlines. To keep the single ++trailing newline when it is present, configure Jinja to ++`keep_trailing_newline`. ++ .. admonition:: Note You must not use a whitespace between the tag and the minus sign. diff --cc jinja2/defaults.py index 8216450c,559a2ef5..a27cb80c --- a/jinja2/defaults.py +++ b/jinja2/defaults.py @@@ -22,8 -21,8 +22,9 @@@ COMMENT_END_STRING = '#} LINE_STATEMENT_PREFIX = None LINE_COMMENT_PREFIX = None TRIM_BLOCKS = False + LSTRIP_BLOCKS = False NEWLINE_SEQUENCE = '\n' +KEEP_TRAILING_NEWLINE = False # default filters, tests and namespace diff --cc jinja2/environment.py index 23d00aa8,33286b7e..074c5aa2 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@@ -11,12 -11,7 +11,12 @@@ import os import sys from jinja2 import nodes -from jinja2.defaults import * +from jinja2.defaults import BLOCK_START_STRING, \ + BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ + COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ + LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ + DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE, \ - KEEP_TRAILING_NEWLINE ++ KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS from jinja2.lexer import get_lexer, TokenStream from jinja2.parser import Parser from jinja2.optimizer import optimize @@@ -239,8 -228,8 +243,9 @@@ class Environment(object) line_statement_prefix=LINE_STATEMENT_PREFIX, line_comment_prefix=LINE_COMMENT_PREFIX, trim_blocks=TRIM_BLOCKS, + lstrip_blocks=LSTRIP_BLOCKS, newline_sequence=NEWLINE_SEQUENCE, + keep_trailing_newline=KEEP_TRAILING_NEWLINE, extensions=(), optimized=True, undefined=Undefined, @@@ -271,8 -260,8 +276,9 @@@ self.line_statement_prefix = line_statement_prefix self.line_comment_prefix = line_comment_prefix self.trim_blocks = trim_blocks + self.lstrip_blocks = lstrip_blocks self.newline_sequence = newline_sequence + self.keep_trailing_newline = keep_trailing_newline # runtime information self.undefined = undefined @@@ -837,8 -823,8 +844,9 @@@ class Template(object) line_statement_prefix=LINE_STATEMENT_PREFIX, line_comment_prefix=LINE_COMMENT_PREFIX, trim_blocks=TRIM_BLOCKS, + lstrip_blocks=LSTRIP_BLOCKS, newline_sequence=NEWLINE_SEQUENCE, + keep_trailing_newline=KEEP_TRAILING_NEWLINE, extensions=(), optimized=True, undefined=Undefined, @@@ -848,8 -834,9 +856,9 @@@ block_start_string, block_end_string, variable_start_string, variable_end_string, comment_start_string, comment_end_string, line_statement_prefix, line_comment_prefix, trim_blocks, - newline_sequence, keep_trailing_newline, frozenset(extensions), - optimized, undefined, finalize, autoescape, None, 0, False, None) - lstrip_blocks, - newline_sequence, frozenset(extensions), optimized, undefined, - finalize, autoescape, None, 0, False, None) ++ lstrip_blocks, newline_sequence, keep_trailing_newline, ++ frozenset(extensions), optimized, undefined, finalize, autoescape, ++ None, 0, False, None) return env.from_string(source, template_class=cls) @classmethod diff --cc jinja2/ext.py index 651ee983,206756fe..639f159c --- a/jinja2/ext.py +++ b/jinja2/ext.py @@@ -10,13 -10,13 +10,17 @@@ :copyright: (c) 2010 by the Jinja Team. :license: BSD. """ -from collections import deque from jinja2 import nodes --from jinja2.defaults import * ++from jinja2.defaults import BLOCK_START_STRING, \ ++ BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ ++ COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ ++ LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ ++ KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS 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, next +from jinja2.utils import contextfunction, import_string, Markup +import six # the only real useful gettext functions for a Jinja template. Note @@@ -589,9 -589,7 +593,10 @@@ def babel_extract(fileobj, keywords, co options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, getbool(options, 'trim_blocks', TRIM_BLOCKS), - NEWLINE_SEQUENCE, frozenset(extensions), ++ getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), + NEWLINE_SEQUENCE, + getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), + frozenset(extensions), cache_size=0, auto_reload=False ) diff --cc jinja2/lexer.py index 5594e0a6,e4886010..b39a7387 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@@ -391,8 -383,8 +391,9 @@@ def get_lexer(environment) environment.line_statement_prefix, environment.line_comment_prefix, environment.trim_blocks, + environment.lstrip_blocks, - environment.newline_sequence) + environment.newline_sequence, + environment.keep_trailing_newline) lexer = _lexer_cache.get(key) if lexer is None: lexer = Lexer(environment) @@@ -434,8 -426,43 +435,44 @@@ class Lexer(object) # block suffix if trimming is enabled block_suffix_re = environment.trim_blocks and '\\n?' or '' + # strip leading spaces if lstrip_blocks is enabled + prefix_re = {} + if environment.lstrip_blocks: + # use '{%+' to manually disable lstrip_blocks behavior + no_lstrip_re = e('+') + # detect overlap between block and variable or comment strings + block_diff = c(r'^%s(.*)' % e(environment.block_start_string)) + # make sure we don't mistake a block for a variable or a comment + m = block_diff.match(environment.comment_start_string) + no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' + m = block_diff.match(environment.variable_start_string) + no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' + + # detect overlap between comment and variable strings + comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string)) + m = comment_diff.match(environment.variable_start_string) + no_variable_re = m and r'(?!%s)' % e(m.group(1)) or '' + + lstrip_re = r'^[ \t]*' + block_prefix_re = r'%s%s(?!%s)|%s\+?' % ( + lstrip_re, + e(environment.block_start_string), + no_lstrip_re, + e(environment.block_start_string), + ) + comment_prefix_re = r'%s%s%s|%s\+?' % ( + lstrip_re, + e(environment.comment_start_string), + no_variable_re, + e(environment.comment_start_string), + ) + prefix_re['block'] = block_prefix_re + prefix_re['comment'] = comment_prefix_re + else: + block_prefix_re = '%s' % e(environment.block_start_string) + self.newline_sequence = environment.newline_sequence + self.keep_trailing_newline = environment.keep_trailing_newline # global lexing rules self.rules = { diff --cc jinja2/testsuite/lexnparse.py index 7022e236,a2d33074..d2473cf0 --- a/jinja2/testsuite/lexnparse.py +++ b/jinja2/testsuite/lexnparse.py @@@ -418,9 -379,173 +418,174 @@@ class SyntaxTestCase(JinjaTestCase) assert tmpl.render(foo={'bar': 42}) == '42' + class LstripBlocksTestCase(JinjaTestCase): + + def test_lstrip(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {% if True %}\n {% endif %}''') + assert tmpl.render() == "\n" + + def test_lstrip_trim(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string(''' {% if True %}\n {% endif %}''') + assert tmpl.render() == "" + + def test_no_lstrip(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''') + assert tmpl.render() == " \n " + + def test_lstrip_endline(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' hello{% if True %}\n goodbye{% endif %}''') + assert tmpl.render() == " hello\n goodbye" + + def test_lstrip_inline(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {% if True %}hello {% endif %}''') + assert tmpl.render() == 'hello ' + + def test_lstrip_nested(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {% if True %}a {% if True %}b {% endif %}c {% endif %}''') + assert tmpl.render() == 'a b c ' + + def test_lstrip_left_chars(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' abc {% if True %} + hello{% endif %}''') + assert tmpl.render() == ' abc \n hello' + + def test_lstrip_embeded_strings(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {% set x = " {% str %} " %}{{ x }}''') + assert tmpl.render() == ' {% str %} ' + + def test_lstrip_preserve_leading_newlines(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string('''\n\n\n{% set hello = 1 %}''') + assert tmpl.render() == '\n\n\n' + + def test_lstrip_comment(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string(''' {# if True #} + hello + {#endif#}''') + assert tmpl.render() == '\nhello\n' + + def test_lstrip_angle_bracket_simple(self): + env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string(''' <% if True %>hello <% endif %>''') + assert tmpl.render() == 'hello ' + + def test_lstrip_angle_bracket_comment(self): + env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string(''' <%# if True %>hello <%# endif %>''') + assert tmpl.render() == 'hello ' + + def test_lstrip_angle_bracket(self): + env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + <%# regular comment %> + <% for item in seq %> + ${item} ## the rest of the stuff + <% endfor %>''') + assert tmpl.render(seq=range(5)) == \ + ''.join('%s\n' % x for x in range(5)) + + def test_lstrip_angle_bracket_compact(self): + env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + <%#regular comment%> + <%for item in seq%> + ${item} ## the rest of the stuff + <%endfor%>''') + assert tmpl.render(seq=range(5)) == \ + ''.join('%s\n' % x for x in range(5)) + + def test_php_syntax_with_manual(self): + env = Environment('', '', '', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + + + + ''') + assert tmpl.render(seq=range(5)) == '01234' + + def test_php_syntax(self): + env = Environment('', '', '', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + + + + ''') + assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5)) + + def test_php_syntax_compact(self): + env = Environment('', '', '', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + + + + ''') + assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5)) + + def test_erb_syntax(self): + env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>', + lstrip_blocks=True, trim_blocks=True) + #env.from_string('') + #for n,r in env.lexer.rules.iteritems(): + # print n + #print env.lexer.rules['root'][0][0].pattern + #print "'%s'" % tmpl.render(seq=range(5)) + tmpl = env.from_string('''\ + <%# I'm a comment, I'm not interesting %> + <% for item in seq %> + <%= item %> + <% endfor %> + ''') + assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5)) + + def test_erb_syntax_with_manual(self): + env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + <%# I'm a comment, I'm not interesting %> + <% for item in seq -%> + <%= item %> + <%- endfor %>''') + assert tmpl.render(seq=range(5)) == '01234' + + def test_erb_syntax_no_lstrip(self): + env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + <%# I'm a comment, I'm not interesting %> + <%+ for item in seq -%> + <%= item %> + <%- endfor %>''') + assert tmpl.render(seq=range(5)) == ' 01234' + + def test_comment_syntax(self): + env = Environment('', '${', '}', '', + lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string('''\ + \ + + ${item} + ''') + assert tmpl.render(seq=range(5)) == '01234' + def suite(): suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TokenStreamTestCase)) suite.addTest(unittest.makeSuite(LexerTestCase)) suite.addTest(unittest.makeSuite(ParserTestCase)) suite.addTest(unittest.makeSuite(SyntaxTestCase))