]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Merge remote-tracking branch 'kristi/master'
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 19 May 2013 10:18:19 +0000 (11:18 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 19 May 2013 10:18:19 +0000 (11:18 +0100)
1  2 
CHANGES
docs/templates.rst
jinja2/defaults.py
jinja2/environment.py
jinja2/ext.py
jinja2/lexer.py
jinja2/testsuite/lexnparse.py

diff --cc CHANGES
index c6f95bcc351a99e496890c7d312ffca81ab0abf4,6539c5fd021dd742e8bd66df4fff2edafad83dfc..036558a65970469986a52d9aa1820536678ebae4
+++ 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
  -----------
index 881e1f52a32d12b81462b7470bec0b3e96b371ba,c1ae657a355c5bab3c86b81f19d67ce9de6e020d..6c8eee3f6e91c5ef49bafe3efb40367fd270122e
@@@ -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::
+     <div>
+         {% if True %}
+             yay
+         {% endif %}
+     </div>
+ gets rendered with blank lines inside the div::
+     <div>
+     
+             yay
+     
+     </div>
+ But with both `trim_blocks` and `lstrip_blocks` enabled, the lines with the 
+ template blocks are removed while preserving the whitespace of the contents::
+     
+     <div>
+             yay
+     </div>
+ You can manually disable the `lstrip_blocks` behavior by putting a
+ plus sign (``+``) at the start of a block::
+     <div>
+             {%+ if something %}yay{% endif %}
+     </div>
+ 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.
index 8216450c12edc376ff73cfa3b86d57231df6f8b8,559a2ef5f28c4ce2e68f9da7d58a9160c02a8e60..a27cb80cbf060e73c963de71d5f595e0453830f5
@@@ -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
index 23d00aa8a094f20da8aea309f0cccbce99f1d54d,33286b7e607f1f3088cadb7e2335581678b9e6ce..074c5aa2d73ecd875e15986546d7e4f562d0144b
  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,
          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,
              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 651ee983725c32f4729982caaa33d306e31953db,206756fe7921f13fcffdde556476d1b011aa2bd0..639f159c952d88f17d41be3c59ae48a687edc49c
      :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 5594e0a64b6cac9a7185c18c36651452cae6479e,e488601083ba6af2e4a1cbbc0d45fd67febc6633..b39a738722775a36dee2c73edec0d31fa6e2a1cc
@@@ -391,8 -383,8 +391,9 @@@ def get_lexer(environment)
             environment.line_statement_prefix,
             environment.line_comment_prefix,
             environment.trim_blocks,
 -           environment.newline_sequence)
+            environment.lstrip_blocks,
 +           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 = {
index 7022e23680986d7e4ab889cf37a2254a99690d58,a2d3307441b0080b496d0d3b6d0c1e016fc3f82c..d2473cf01479283787eb8e05dda354e71680f0eb
@@@ -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('''\
+     <!-- I'm a comment, I'm not interesting -->
+     <? for item in seq -?>
+         <?= item ?>
+     <?- endfor ?>''')
+         assert tmpl.render(seq=range(5)) == '01234'
+     def test_php_syntax(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)) == ''.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('''\
+     <!-- 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(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('''\
+ <!--# I'm a comment, I'm not interesting -->\
+ <!-- for item in seq --->
+     ${item}
+ <!--- endfor -->''')
+         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))