Repository: PebbleTemplates/pebble
Pull request #226 (2016-09-22 23:58:14)

Issue #125 - Disable trimming of the first newline after a tag.
Adds the possibility to use ".enableNewLineTrimming(false)" when  building a PebbleEngine instance so the first newline following a Pebble tag is not trimmed. Trimming the first newline stays the default behavior.



Patch:
diff --git a/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java b/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
index 28e9f139..7d2b4e55 100644
--- a/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
+++ b/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
@@ -254,9 +254,11 @@ public class PebbleEngine {
 
         private List<Extension> userProvidedExtensions = new ArrayList<>();
 
-        private Syntax syntax = new Syntax.Builder().build();
+        private Syntax syntax;
 
         private boolean strictVariables = false;
+        
+        private boolean enableNewLineTrimming = true;
 
         private Locale defaultLocale;
 
@@ -333,6 +335,29 @@ public class PebbleEngine {
             this.strictVariables = strictVariables;
             return this;
         }
+        
+        /**
+         * Changes the <code>enableNewLineTrimming</code> setting of the PebbleEngine.
+         * The default value of this setting is "true".
+         * <p>
+         * If <code>enableNewLineTrimming</code> is set to false, then the
+         * first newline following a Pebble tag won't be trimmed. It is trimmed
+         * by default.
+         * </p>
+         * <p>
+         * For example, with <code>enableNewLineTrimming</code> left to true,
+         * <code>{{ key1\nkey2 }}</code> would result in
+         * <code>{{ val1val2 }}</code>. With <code>enableNewLineTrimming</code> 
+         * set to false, the result would be <code>{{ val1\nval2 }}</code>
+         * </p>
+         *
+         * @param enableNewLineTrimming Whether or not the newline should be trimmed.
+         * @return This builder object
+         */
+        public Builder enableNewLineTrimming(boolean enableNewLineTrimming) {
+            this.enableNewLineTrimming = enableNewLineTrimming;
+            return this;
+        }
 
         /**
          * Sets the Locale passed to all templates constructed by this PebbleEngine.
@@ -468,6 +493,10 @@ public class PebbleEngine {
                 templateCache = CacheBuilder.newBuilder().maximumSize(0).build();
                 tagCache = CacheBuilder.newBuilder().maximumSize(0).build();
             }
+            
+            if(syntax == null) {
+                syntax = new Syntax.Builder().setEnableNewLineTrimming(enableNewLineTrimming).build();
+            }
 
             return new PebbleEngine(loader, syntax, strictVariables, defaultLocale, tagCache, templateCache,
                     executorService, extensions);
diff --git a/src/main/java/com/mitchellbosecke/pebble/lexer/Syntax.java b/src/main/java/com/mitchellbosecke/pebble/lexer/Syntax.java
index 04c0370e..27dd5b6f 100644
--- a/src/main/java/com/mitchellbosecke/pebble/lexer/Syntax.java
+++ b/src/main/java/com/mitchellbosecke/pebble/lexer/Syntax.java
@@ -51,7 +51,7 @@ public final class Syntax {
 
     public Syntax(final String delimiterCommentOpen, final String delimiterCommentClose,
             final String delimiterExecuteOpen, final String delimiterExecuteClose, final String delimiterPrintOpen,
-            final String delimiterPrintClose, final String whitespaceTrim) {
+            final String delimiterPrintClose, final String whitespaceTrim, final boolean enableNewLineTrimming) {
         this.delimiterCommentClose = delimiterCommentClose;
         this.delimiterCommentOpen = delimiterCommentOpen;
         this.delimiterExecuteOpen = delimiterExecuteOpen;
@@ -59,13 +59,17 @@ public final class Syntax {
         this.delimiterPrintOpen = delimiterPrintOpen;
         this.delimiterPrintClose = delimiterPrintClose;
         this.whitespaceTrim = whitespaceTrim;
+        
+        // Do we trim the newline following a tag?
+        String newlineRegexSuffix = enableNewLineTrimming ? POSSIBLE_NEW_LINE : "";
 
         // regexes used to find the individual delimiters
         this.regexPrintClose = Pattern.compile("^\\s*" + Pattern.quote(whitespaceTrim) + "?"
-                + Pattern.quote(delimiterPrintClose) + POSSIBLE_NEW_LINE);
+                + Pattern.quote(delimiterPrintClose) + newlineRegexSuffix);
+        
         this.regexExecuteClose = Pattern.compile("^\\s*" + Pattern.quote(whitespaceTrim) + "?"
-                + Pattern.quote(delimiterExecuteClose) + POSSIBLE_NEW_LINE);
-        this.regexCommentClose = Pattern.compile(Pattern.quote(delimiterCommentClose) + POSSIBLE_NEW_LINE);
+                + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);
+        this.regexCommentClose = Pattern.compile(Pattern.quote(delimiterCommentClose) + newlineRegexSuffix);
 
         // combination regex used to find the next START delimiter of any kind
         this.regexStartDelimiters = Pattern.compile(Pattern.quote(delimiterPrintOpen) + "|"
@@ -73,10 +77,10 @@ public final class Syntax {
 
         // regex to find the verbatim tag
         this.regexVerbatimStart = Pattern.compile("^\\s*verbatim\\s*(" + Pattern.quote(whitespaceTrim) + ")?"
-                + Pattern.quote(delimiterExecuteClose) + POSSIBLE_NEW_LINE);
+                + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);
         this.regexVerbatimEnd = Pattern.compile(Pattern.quote(delimiterExecuteOpen) + "("
                 + Pattern.quote(whitespaceTrim) + ")?" + "\\s*endverbatim\\s*(" + Pattern.quote(whitespaceTrim) + ")?"
-                + Pattern.quote(delimiterExecuteClose) + POSSIBLE_NEW_LINE);
+                + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);
 
         // regex for the whitespace trim character
         this.regexLeadingWhitespaceTrim = Pattern.compile(Pattern.quote(whitespaceTrim) + "\\s+");
@@ -182,6 +186,8 @@ public final class Syntax {
         private String delimiterPrintClose = "}}";
 
         private String whitespaceTrim = "-";
+        
+        private boolean enableNewLineTrimming = true;
 
         /**
          * @return the commentOpenDelimiter
@@ -280,10 +286,19 @@ public final class Syntax {
         public void setWhitespaceTrim(String whitespaceTrim) {
             this.whitespaceTrim = whitespaceTrim;
         }
+        
+        public boolean isEnableNewLineTrimming() {
+            return enableNewLineTrimming;
+        }
+        
+        public Builder setEnableNewLineTrimming(boolean enableNewLineTrimming) {
+            this.enableNewLineTrimming = enableNewLineTrimming;
+            return this;
+        }
 
         public Syntax build() {
             return new Syntax(delimiterCommentOpen, delimiterCommentClose, delimiterExecuteOpen, delimiterExecuteClose,
-                    delimiterPrintOpen, delimiterPrintClose, whitespaceTrim);
+                    delimiterPrintOpen, delimiterPrintClose, whitespaceTrim, enableNewLineTrimming);
         }
     }
 


Test patch:
diff --git a/src/test/java/com/mitchellbosecke/pebble/NewlineTrimmingTest.java b/src/test/java/com/mitchellbosecke/pebble/NewlineTrimmingTest.java
new file mode 100644
index 00000000..de5e7d36
--- /dev/null
+++ b/src/test/java/com/mitchellbosecke/pebble/NewlineTrimmingTest.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * This file is part of Pebble.
+ * <p>
+ * Copyright (c) 2014 by Mitchell Bösecke
+ * <p>
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ ******************************************************************************/
+package com.mitchellbosecke.pebble;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.mitchellbosecke.pebble.error.PebbleException;
+import com.mitchellbosecke.pebble.loader.StringLoader;
+import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
+public class NewlineTrimmingTest extends AbstractTest {
+
+    @Test
+    public void testPrintDefault() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{{param1}}\n{{param2}}");
+
+        Writer writer = new StringWriter();
+
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("param1", "val1");
+        params.put("param2", "val2");
+
+        template.evaluate(writer, params);
+
+        assertEquals("val1val2", writer.toString());
+    }
+
+    @Test
+    public void testPrintForceToTrue() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(true)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{{param1}}\n{{param2}}");
+
+        Writer writer = new StringWriter();
+
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("param1", "val1");
+        params.put("param2", "val2");
+
+        template.evaluate(writer, params);
+
+        assertEquals("val1val2", writer.toString());
+    }
+
+    @Test
+    public void testPrintSetToFalse() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(false)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{{param1}}\n{{param2}}");
+
+        Writer writer = new StringWriter();
+
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("param1", "val1");
+        params.put("param2", "val2");
+
+        template.evaluate(writer, params);
+
+        assertEquals("val1\nval2", writer.toString());
+    }
+    
+    @Test
+    public void testPrintDefaultTwoNewlines() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{{param1}}\n\n{{param2}}");
+
+        Writer writer = new StringWriter();
+
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("param1", "val1");
+        params.put("param2", "val2");
+
+        template.evaluate(writer, params);
+
+        assertEquals("val1\nval2", writer.toString());
+    }
+    
+    @Test
+    public void testPrintSetToFalseTwoNewlines() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(false)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{{param1}}\n\n{{param2}}");
+
+        Writer writer = new StringWriter();
+
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("param1", "val1");
+        params.put("param2", "val2");
+
+        template.evaluate(writer, params);
+
+        assertEquals("val1\n\nval2", writer.toString());
+    }
+
+    @Test
+    public void testCommentDefault() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{# comment1 #}\n{# comment2 #}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    public void testCommentForceToTrue() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(true)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{# comment1 #}\n{# comment2 #}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    public void testCommentSetToFalse() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(false)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{# comment1 #}\n{# comment2 #}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("\n", writer.toString());
+    }
+
+    @Test
+    public void testExecuteDefault() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{% if true %}\n{% endif %}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    public void testExecuteForceToTrue() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(true)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{% if true %}\n{% endif %}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    public void testExecuteSetToFalse() throws PebbleException, IOException {
+
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())
+                                                        .enableNewLineTrimming(false)
+                                                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("{% if true %}\n{% endif %}");
+
+        Writer writer = new StringWriter();
+
+        template.evaluate(writer);
+
+        assertEquals("\n", writer.toString());
+    }
+    
+
+}