Repository: M3t0r/tpl

Issue:
Fix trailing newline issue
In https://github.com/M3t0r/tpl/blob/feceeed182f1c2553b827d8f431f6be800204250/tpl/__init__.py#L72 `tpl` always adds a trailing newline instead of respecting what the template has. I added this to have nicer output on the command line, but it turns out Jinja has a setting if it should keep the last newline, if any. It's called `keep_trailing_newline` and is part of the [`jinja2.Environment`](http://jinja.pocoo.org/docs/2.10/api/#jinja2.Environment).

We should add tests to see if `tpl` only prints a newline if one is actually present in the template.

Discussion:

Patch:
diff --git a/tpl/__init__.py b/tpl/__init__.py
index 96b6c41..32297a0 100644
--- a/tpl/__init__.py
+++ b/tpl/__init__.py
@@ -61,15 +61,19 @@ def main(*args):
     for data in loaded_data:
         collated_data = merge_data(collated_data, data)
 
+    # set up Jinja2 environment
+    j_env = jinja2.Environment(
+        keep_trailing_newline=True
+    )
+
     # create template
     with open_file(arguments[0]) as template_stream:
-        template = jinja2.Template(template_stream.read())
+        template = j_env.from_string(template_stream.read())
         template.filename = arguments[0]
 
     # and render to output
     with open_file(arguments[1], "w") as output:
         template.stream(collated_data).dump(output)
-        output.write("\n")  # does the template eat this or the dump call?
 
     return os.EX_OK
 


Test patch:
diff --git a/tests/cli/test_faulty_invocations.py b/tests/cli/test_faulty_invocations.py
index d81fbd8..d538ec6 100644
--- a/tests/cli/test_faulty_invocations.py
+++ b/tests/cli/test_faulty_invocations.py
@@ -9,7 +9,7 @@ def test_key_does_not_exist(cli):
         cli.path_for_content("{{FOO}}"),
         env={}
     )
-    assert p.stdout == "\n"
+    assert p.stdout == ""
 
 
 def test_corrupt_yaml(cli):
diff --git a/tests/cli/test_standard_usecases.py b/tests/cli/test_standard_usecases.py
index c3c9426..90d0c68 100644
--- a/tests/cli/test_standard_usecases.py
+++ b/tests/cli/test_standard_usecases.py
@@ -3,12 +3,12 @@ from . import cli
 
 def test_source_environment(cli):
     p = cli("-e", cli.path_for_content("{{FOO}}"), env={"FOO": "bar"})
-    assert p.stdout == "bar\n"
+    assert p.stdout == "bar"
 
 
 def test_unicode_var(cli):
     p = cli("-e", cli.path_for_content("{{FOO}}"), env={"FOO": "🐍"})
-    assert p.stdout == "🐍\n"
+    assert p.stdout == "🐍"
 
 
 def test_shadowing_json_env(cli):
@@ -18,7 +18,7 @@ def test_shadowing_json_env(cli):
         cli.path_for_content("{{FOO}}"),
         env={"FOO": "env"}
     )
-    assert p.stdout == "env\n"
+    assert p.stdout == "env"
 
 
 def test_shadowing_yaml_env(cli):
@@ -28,7 +28,7 @@ def test_shadowing_yaml_env(cli):
         cli.path_for_content("{{FOO}}"),
         env={"FOO": "env"}
     )
-    assert p.stdout == "env\n"
+    assert p.stdout == "env"
 
 
 def test_yaml_flow_style(cli):
@@ -36,7 +36,7 @@ def test_yaml_flow_style(cli):
         "--yaml", cli.path_for_content('{"FOO": "yaml"}'),
         cli.path_for_content("{{FOO}}")
     )
-    assert p.stdout == "yaml\n"
+    assert p.stdout == "yaml"
 
 
 def test_environment_by_default(cli):
@@ -44,7 +44,7 @@ def test_environment_by_default(cli):
         cli.path_for_content("{{FOO}}"),
         env={"FOO": "bar"}
     )
-    assert p.stdout == "bar\n"
+    assert p.stdout == "bar"
 
 
 def test_sub_dict_shadowing(cli):
@@ -53,7 +53,7 @@ def test_sub_dict_shadowing(cli):
         "--json", cli.path_for_json({"FOO": {"BAR": "second"}}),
         cli.path_for_content("{{FOO['BAR']}}")
     )
-    assert p.stdout == "second\n"
+    assert p.stdout == "second"
 
 
 def test_sub_dict_merging(cli):
@@ -62,7 +62,7 @@ def test_sub_dict_merging(cli):
         "--json", cli.path_for_json({"merge": {"BAR": "bar"}}),
         cli.path_for_content("{{merge['FOO']}}{{merge['BAR']}}")
     )
-    assert p.stdout == "foobar\n"
+    assert p.stdout == "foobar"
 
 
 def test_second_sub_dict_shadowing(cli):
@@ -71,7 +71,7 @@ def test_second_sub_dict_shadowing(cli):
         "--json", cli.path_for_json({"merge": {"deeper": {"overwritten": "bar"}}}),
         cli.path_for_content("{{merge.deeper.overwritten}}")
     )
-    assert p.stdout == "bar\n"
+    assert p.stdout == "bar"
 
 
 def test_second_sub_dict_merging(cli):
@@ -80,7 +80,7 @@ def test_second_sub_dict_merging(cli):
         "--json", cli.path_for_json({"merge": {"deeper": {"BAR": "bar"}}}),
         cli.path_for_content("{{merge.deeper.FOO}}{{merge.deeper.BAR}}")
     )
-    assert p.stdout == "foobar\n"
+    assert p.stdout == "foobar"
 
 
 def test_shadowing_of_dict(cli):
@@ -89,4 +89,29 @@ def test_shadowing_of_dict(cli):
         "--json", cli.path_for_json({"merge": 'bar'}),
         cli.path_for_content("{{merge}}")
     )
+    assert p.stdout == "bar"
+
+
+def test_keep_no_newline_at_end(cli):
+    p = cli(cli.path_for_content("{{FOO}}"), env={"FOO": "bar"})
+    assert p.stdout == "bar"
+
+
+def test_keep_one_newline_at_end(cli):
+    p = cli(cli.path_for_content("{{FOO}}\n"), env={"FOO": "bar"})
     assert p.stdout == "bar\n"
+
+
+def test_keep_two_newlines_at_end(cli):
+    p = cli(cli.path_for_content("{{FOO}}\n\n"), env={"FOO": "bar"})
+    assert p.stdout == "bar\n\n"
+
+
+def test_keep_one_newline_at_beginning(cli):
+    p = cli(cli.path_for_content("\n{{FOO}}"), env={"FOO": "bar"})
+    assert p.stdout == "\nbar"
+
+
+def test_keep_two_newlines_at_beginning(cli):
+    p = cli(cli.path_for_content("\n\n{{FOO}}"), env={"FOO": "bar"})
+    assert p.stdout == "\n\nbar"