]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Add `keep_trailing_newline` to configure final endline stripping 170/head
authorW. Trevor King <wking@tremily.us>
Fri, 11 Jan 2013 13:23:24 +0000 (08:23 -0500)
committerW. Trevor King <wking@tremily.us>
Fri, 11 Jan 2013 13:47:30 +0000 (08:47 -0500)
This option defaults to False for compatibility with the existing
behaviour.  I've added the option because I expect I won't remember to
keep an extra trailing newline in my templates, and some non-HTML
templates *need* that last newline.

See also:
https://groups.google.com/d/msg/pocoo-libs/6DylMqq1voI/GXTbZJ1Tr-sJ

CHANGES
docs/templates.rst
jinja2/defaults.py
jinja2/environment.py
jinja2/ext.py
jinja2/lexer.py
jinja2/testsuite/lexnparse.py

diff --git a/CHANGES b/CHANGES
index 6539c5fd021dd742e8bd66df4fff2edafad83dfc..43f5e8ff08cba51ff07f28ca29e5fee8cc4ba57f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -13,6 +13,8 @@ Version 2.7
 - 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.
 
index 790b95e7c24908ff083291ad8e338ca6396b0685..3526a55f9e7ae2c007888b47b22b87efa9f5a2cc 100644 (file)
@@ -156,7 +156,9 @@ 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).
+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
 sign (``-``) to the start or end of an block (for example a for tag), a
index d2d45443adde9d94b1319bc4ffa54a5abbe99baf..887262f2495e83be61eb38e43188e3f5a02d572a 100644 (file)
@@ -22,6 +22,7 @@ LINE_STATEMENT_PREFIX = None
 LINE_COMMENT_PREFIX = None
 TRIM_BLOCKS = False
 NEWLINE_SEQUENCE = '\n'
+KEEP_TRAILING_NEWLINE = False
 
 
 # default filters, tests and namespace
index 1b5dc40dccfe7f3bc63d8424de6bc72902c1e141..fb04e64c32a7704dc65dbc7459a3b3cc50063191 100644 (file)
@@ -140,6 +140,13 @@ class Environment(object):
             useful default for Linux and OS X systems as well as web
             applications.
 
+        `keep_trailing_newline`
+            Preserve the trailing newline when rendering templates.
+            The default is ``False``, which causes a single newline,
+            if present, to be stripped from the end of the template.
+
+            .. versionadded:: 2.7
+
         `extensions`
             List of Jinja extensions to use.  This can either be import paths
             as strings or extension classes.  For more information have a
@@ -225,6 +232,7 @@ class Environment(object):
                  line_comment_prefix=LINE_COMMENT_PREFIX,
                  trim_blocks=TRIM_BLOCKS,
                  newline_sequence=NEWLINE_SEQUENCE,
+                 keep_trailing_newline=KEEP_TRAILING_NEWLINE,
                  extensions=(),
                  optimized=True,
                  undefined=Undefined,
@@ -256,6 +264,7 @@ class Environment(object):
         self.line_comment_prefix = line_comment_prefix
         self.trim_blocks = trim_blocks
         self.newline_sequence = newline_sequence
+        self.keep_trailing_newline = keep_trailing_newline
 
         # runtime information
         self.undefined = undefined
@@ -821,6 +830,7 @@ class Template(object):
                 line_comment_prefix=LINE_COMMENT_PREFIX,
                 trim_blocks=TRIM_BLOCKS,
                 newline_sequence=NEWLINE_SEQUENCE,
+                keep_trailing_newline=KEEP_TRAILING_NEWLINE,
                 extensions=(),
                 optimized=True,
                 undefined=Undefined,
@@ -830,8 +840,8 @@ class Template(object):
             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, frozenset(extensions), optimized, undefined,
-            finalize, autoescape, None, 0, False, None)
+            newline_sequence, keep_trailing_newline, frozenset(extensions),
+            optimized, undefined, finalize, autoescape, None, 0, False, None)
         return env.from_string(source, template_class=cls)
 
     @classmethod
index 206756fe7921f13fcffdde556476d1b011aa2bd0..650ddd2b6bba06c71e9498282f7333227b22586d 100644 (file)
@@ -589,7 +589,9 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         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),
+        NEWLINE_SEQUENCE,
+        getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
+        frozenset(extensions),
         cache_size=0,
         auto_reload=False
     )
index 69865d08aec79897d41aa5a00803993636ad2f7e..0eb19f74706e9b2d2e3824924471db4bf7996d51 100644 (file)
@@ -383,7 +383,8 @@ def get_lexer(environment):
            environment.line_statement_prefix,
            environment.line_comment_prefix,
            environment.trim_blocks,
-           environment.newline_sequence)
+           environment.newline_sequence,
+           environment.keep_trailing_newline)
     lexer = _lexer_cache.get(key)
     if lexer is None:
         lexer = Lexer(environment)
@@ -426,6 +427,7 @@ class Lexer(object):
         block_suffix_re = environment.trim_blocks and '\\n?' or ''
 
         self.newline_sequence = environment.newline_sequence
+        self.keep_trailing_newline = environment.keep_trailing_newline
 
         # global lexing rules
         self.rules = {
@@ -549,7 +551,14 @@ class Lexer(object):
         """This method tokenizes the text and returns the tokens in a
         generator.  Use this method if you just want to tokenize a template.
         """
-        source = '\n'.join(unicode(source).splitlines())
+        source = unicode(source)
+        lines = source.splitlines()
+        if self.keep_trailing_newline and source:
+            for newline in ('\r\n', '\r', '\n'):
+                if source.endswith(newline):
+                    lines.append('')
+                    break
+        source = '\n'.join(lines)
         pos = 0
         lineno = 1
         stack = ['root']
index 77b76ec1656d9e0826f7a4994e0238dcabfa7151..3bf788ec1fc7a2b8b0e1e8f9bc8c9a29ea077be2 100644 (file)
@@ -82,6 +82,19 @@ class LexerTestCase(JinjaTestCase):
             result = tmpl.render()
             assert result.replace(seq, 'X') == '1X2X3X4'
 
+    def test_trailing_newline(self):
+        for keep in [True, False]:
+            env = Environment(keep_trailing_newline=keep)
+            for template,expected in [
+                    ('', {}),
+                    ('no\nnewline', {}),
+                    ('with\nnewline\n', {False: 'with\nnewline'}),
+                    ('with\nseveral\n\n\n', {False: 'with\nseveral\n\n'}),
+                    ]:
+                tmpl = env.from_string(template)
+                expect = expected.get(keep, template)
+                result = tmpl.render()
+                assert result == expect, (keep, template, result, expect)
 
 class ParserTestCase(JinjaTestCase):