Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add `keep_trailing_newline` to configure final endline stripping #170

Merged
merged 1 commit into from

4 participants

@wking

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

On 2011-02-28, Armin Ronacher wrote:

I will update the documentation for sure and consider adding a flag
that controls the trailing one.

Here's the flag, all done up with a bow and a unittest ;).

@wking wking Add `keep_trailing_newline` to configure final endline stripping
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
7e912c6
@gforcada

a :+1: I'm also interested in that.

@wking

Since there's a lot of Jinja work going on at the moment, maybe someone can merge this? :D.

@mitsuhiko mitsuhiko merged commit 7e912c6 into from
@dpursehouse

Is it intentional/expected that keep_trailing_newline is only effective when passed into the constructor, and not when invoking render()?

For example, I have a template file that has a trailing newline:

>>> from jinja2 import Template
>>> from os.path import abspath, expanduser
>>> data = open(abspath(expanduser("~/jinjatest.txt"))).read()
>>> print "[%s]" % data
[line 1
line 2
]

When it gets rendered, the newline is removed:

>>> template = Template(data)
>>> print "[%s]" % template.render()
[line 1
line 2]

When rendering with keep_trailing_newline=True it is also removed:

>>> print "[%s]" % template.render(keep_trailing_newline=True)
[line 1
line 2]

But if I pass keep_trailing_newline=True when constructing the Template, it works as expected:

>>> template = Template(data, keep_trailing_newline=True)
>>> print "[%s]" % template.render()
[line 1
line 2
]
@wking wking deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 11, 2013
  1. @wking

    Add `keep_trailing_newline` to configure final endline stripping

    wking authored
    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
This page is out of date. Refresh to see the latest.
View
2  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.
View
4 docs/templates.rst
@@ -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
View
1  jinja2/defaults.py
@@ -22,6 +22,7 @@
LINE_COMMENT_PREFIX = None
TRIM_BLOCKS = False
NEWLINE_SEQUENCE = '\n'
+KEEP_TRAILING_NEWLINE = False
# default filters, tests and namespace
View
14 jinja2/environment.py
@@ -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 @@ def __init__(self,
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 @@ def __init__(self,
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 @@ def __new__(cls, source,
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 @@ def __new__(cls, source,
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
View
4 jinja2/ext.py
@@ -589,7 +589,9 @@ def getbool(options, key, default=False):
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
)
View
13 jinja2/lexer.py
@@ -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 @@ def __init__(self, environment):
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 @@ def tokeniter(self, source, name, filename=None, state=None):
"""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']
View
13 jinja2/testsuite/lexnparse.py
@@ -82,6 +82,19 @@ def test_normalizing(self):
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):
Something went wrong with that request. Please try again.