From fc1230b007c86fccd281602da4b5c0c14824d7ca Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 11 Sep 2018 18:34:19 +0200 Subject: [PATCH 01/14] fixed whitespace handling for partials --- .../mustachejava/DefaultMustacheVisitor.java | 4 +- .../DeferringMustacheFactory.java | 4 +- .../github/mustachejava/MustacheParser.java | 13 ++++- .../github/mustachejava/MustacheVisitor.java | 2 +- .../github/mustachejava/codes/ExtendCode.java | 2 +- .../mustachejava/codes/PartialCode.java | 16 ++++-- .../github/mustachejava/codes/ValueCode.java | 17 +++++- .../mustachejava/util/IndentWriter.java | 57 +++++++++++++++++++ .../github/mustachejava/InterpreterTest.java | 10 ++-- 9 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 compiler/src/main/java/com/github/mustachejava/util/IndentWriter.java diff --git a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java index 69b71cd37..67cc5f5ca 100644 --- a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java @@ -64,9 +64,9 @@ public void checkName(TemplateContext templateContext, String variable, Mustache } @Override - public void partial(TemplateContext tc, final String variable) { + public void partial(TemplateContext tc, final String variable, String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable)); + list.add(new PartialCode(partialTC, df, variable, indent)); } @Override diff --git a/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java index 945717d9b..7b1893d8e 100644 --- a/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java +++ b/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java @@ -70,10 +70,10 @@ public MustacheVisitor createMustacheVisitor() { final AtomicLong id = new AtomicLong(0); return new DefaultMustacheVisitor(this) { @Override - public void partial(TemplateContext tc, final String variable) { + public void partial(TemplateContext tc, final String variable, final String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); final Long divid = id.incrementAndGet(); - list.add(new PartialCode(partialTC, df, variable) { + list.add(new PartialCode(partialTC, df, variable, indent) { Wrapper deferredWrapper; @Override diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 928132385..885ea3824 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -181,9 +181,20 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger return mv.mustache(new TemplateContext(sm, em, file, 0, startOfLine)); } case '>': { + String indent = (onlywhitespace && startOfLine) ? out.toString() : ""; out = write(mv, out, file, currentLine.intValue(), startOfLine); startOfLine = startOfLine & onlywhitespace; - mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable); + mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); + + // a new line following a partial is dropped + if (startOfLine) { + br.mark(2); + int ca = br.read(); + if (ca == '\r') { + ca = br.read(); + } + if (ca != '\n') br.reset(); + } break; } case '{': { diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java index a8083b7d4..5866e5698 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java @@ -12,7 +12,7 @@ public interface MustacheVisitor { void notIterable(TemplateContext templateContext, String variable, Mustache mustache); - void partial(TemplateContext templateContext, String variable); + void partial(TemplateContext templateContext, String variable, String indent); void value(TemplateContext templateContext, String variable, boolean encoded); diff --git a/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java b/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java index 224a73a02..f899d8406 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java @@ -23,7 +23,7 @@ public class ExtendCode extends PartialCode { private final DefaultMustacheFactory mf; public ExtendCode(TemplateContext tc, DefaultMustacheFactory mf, Mustache codes, String name) throws MustacheException { - super(tc, mf, codes, "<", name); + super(tc, mf, codes, "<", name, ""); this.mf = mf; } diff --git a/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java b/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java index a10861f69..40ff3a07d 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java @@ -1,6 +1,7 @@ package com.github.mustachejava.codes; import com.github.mustachejava.*; +import com.github.mustachejava.util.IndentWriter; import java.io.IOException; import java.io.Writer; @@ -13,8 +14,13 @@ public class PartialCode extends DefaultCode { protected int recrusionLimit; protected boolean isRecursive; - protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mustache, String type, String variable) { + private final String indent; + + protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mustache, String type, String variable, String indent) { super(tc, df, mustache, variable, type); + + this.indent = indent; + // Use the name of the parent to get the name of the partial String file = tc.file(); int dotindex = file.lastIndexOf("."); @@ -24,8 +30,8 @@ protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mu recrusionLimit = df.getRecursionLimit(); } - public PartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable) { - this(tc, cf, null, ">", variable); + public PartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable, String indent) { + this(tc, cf, null, ">", variable, indent); } @Override @@ -67,12 +73,12 @@ public Writer execute(Writer writer, final List scopes) { } writer = depthLimitedWriter; } - Writer execute = partial.execute(writer, scopes); + partial.execute(new IndentWriter(writer, indent), scopes); if (isRecursive) { assert depthLimitedWriter != null; depthLimitedWriter.decr(); } - return appendText(execute); + return appendText(writer); } @Override diff --git a/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java b/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java index fabbf3a90..8d73ca53c 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java @@ -4,6 +4,7 @@ import com.github.mustachejava.FragmentKey; import com.github.mustachejava.MustacheException; import com.github.mustachejava.TemplateContext; +import com.github.mustachejava.util.IndentWriter; import com.github.mustachejava.util.LatchedWriter; import com.github.mustachejava.util.Node; @@ -75,6 +76,18 @@ public Writer execute(Writer writer, final List scopes) { } } + private Writer getInnerWriter(Writer w) throws IOException { + if (w instanceof IndentWriter) { + IndentWriter iw = (IndentWriter) w; + iw.flushIndent(); + w = iw.inner; + while (w instanceof IndentWriter) { + w = ((IndentWriter) w).inner; + } + } + return w; + } + protected Writer handleCallable(Writer writer, final Callable callable, final List scopes) throws Exception { if (les == null) { Object call = callable.call(); @@ -125,9 +138,9 @@ protected void execute(Writer writer, String value) throws IOException { // Treat null values as the empty string if (value != null) { if (encoded) { - df.encode(value, writer); + df.encode(value, getInnerWriter(writer)); } else { - writer.write(value); + getInnerWriter(writer).write(value); } } } diff --git a/compiler/src/main/java/com/github/mustachejava/util/IndentWriter.java b/compiler/src/main/java/com/github/mustachejava/util/IndentWriter.java new file mode 100644 index 000000000..f44bb1b64 --- /dev/null +++ b/compiler/src/main/java/com/github/mustachejava/util/IndentWriter.java @@ -0,0 +1,57 @@ +package com.github.mustachejava.util; + +import java.io.IOException; +import java.io.Writer; + +public class IndentWriter extends Writer { + + public final Writer inner; + private final String indent; + private boolean prependIndent = false; + + public IndentWriter(Writer inner, String indent) { + this.inner = inner; + this.indent = indent; + } + + @Override + public void write(char[] chars, int off, int len) throws IOException { + int newOff = off; + for (int i = newOff; i < len; ++i) { + if (chars[i] == '\n') { + // write character up to newline + writeLine(chars, newOff, i + 1 - newOff); + this.prependIndent = true; + + newOff = i + 1; + } + } + writeLine(chars, newOff, len - (newOff - off)); + } + + public void flushIndent() throws IOException { + if (this.prependIndent) { + inner.append(indent); + this.prependIndent = false; + } + } + + private void writeLine(char[] chars, int off, int len) throws IOException { + if (len <= 0) { + return; + } + + this.flushIndent(); + inner.write(chars, off, len); + } + + @Override + public void flush() throws IOException { + inner.flush(); + } + + @Override + public void close() throws IOException { + inner.close(); + } +} diff --git a/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java b/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java index 4edfef430..c040a081a 100644 --- a/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java +++ b/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java @@ -684,11 +684,11 @@ public void testDynamicPartial() throws MustacheException, IOException { public MustacheVisitor createMustacheVisitor() { return new DefaultMustacheVisitor(this) { @Override - public void partial(TemplateContext tc, String variable) { + public void partial(TemplateContext tc, String variable, String indent) { if (variable.startsWith("+")) { // This is a dynamic partial rather than a static one TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable.substring(1).trim()) { + list.add(new PartialCode(partialTC, df, variable.substring(1).trim(), indent) { @Override public synchronized void init() { filterText(); @@ -713,7 +713,7 @@ public Writer execute(Writer writer, List scopes) { } }); } else { - super.partial(tc, variable); + super.partial(tc, variable, indent); } } }; @@ -1222,9 +1222,9 @@ public void testOverrideExtension() throws IOException { public MustacheVisitor createMustacheVisitor() { return new DefaultMustacheVisitor(this) { @Override - public void partial(TemplateContext tc, String variable) { + public void partial(TemplateContext tc, String variable, String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable) { + list.add(new PartialCode(partialTC, df, variable, indent) { @Override protected String partialName() { return name; From 1f2ee1a7bd5e66f43c7e70e53c674487293b0fd2 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 15:21:56 +0200 Subject: [PATCH 02/14] refactored whitespace fixes into separate factory --- .../mustachejava/DefaultMustacheVisitor.java | 2 +- .../DeferringMustacheFactory.java | 2 +- .../mustachejava/SpecMustacheFactory.java | 12 ++++ .../mustachejava/SpecMustacheVisitor.java | 62 +++++++++++++++++++ .../github/mustachejava/codes/ExtendCode.java | 2 +- .../mustachejava/codes/PartialCode.java | 19 +++--- .../github/mustachejava/codes/ValueCode.java | 17 +---- .../github/mustachejava/InterpreterTest.java | 4 +- 8 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java create mode 100644 compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java diff --git a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java index 67cc5f5ca..786c4fa9e 100644 --- a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java @@ -66,7 +66,7 @@ public void checkName(TemplateContext templateContext, String variable, Mustache @Override public void partial(TemplateContext tc, final String variable, String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable, indent)); + list.add(new PartialCode(partialTC, df, variable)); } @Override diff --git a/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java index 7b1893d8e..0aec6379a 100644 --- a/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java +++ b/compiler/src/main/java/com/github/mustachejava/DeferringMustacheFactory.java @@ -73,7 +73,7 @@ public MustacheVisitor createMustacheVisitor() { public void partial(TemplateContext tc, final String variable, final String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); final Long divid = id.incrementAndGet(); - list.add(new PartialCode(partialTC, df, variable, indent) { + list.add(new PartialCode(partialTC, df, variable) { Wrapper deferredWrapper; @Override diff --git a/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java new file mode 100644 index 000000000..06e5e84a5 --- /dev/null +++ b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java @@ -0,0 +1,12 @@ +package com.github.mustachejava; + +/** + * This factory is similar to DefaultMustacheFactory but handles whitespace according to the mustache specification. + * Therefore the rendering is less performant than with the DefaultMustacheFactory. + */ +public class SpecMustacheFactory extends DefaultMustacheFactory { + @Override + public MustacheVisitor createMustacheVisitor() { + return new SpecMustacheVisitor(this); + } +} diff --git a/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java new file mode 100644 index 000000000..8736d2cee --- /dev/null +++ b/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java @@ -0,0 +1,62 @@ +package com.github.mustachejava; + +import com.github.mustachejava.codes.PartialCode; +import com.github.mustachejava.codes.ValueCode; +import com.github.mustachejava.util.IndentWriter; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +public class SpecMustacheVisitor extends DefaultMustacheVisitor { + public SpecMustacheVisitor(DefaultMustacheFactory df) { + super(df); + } + + @Override + public void partial(TemplateContext tc, final String variable, String indent) { + TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); + list.add(new SpecPartialCode(partialTC, df, variable, indent)); + } + + @Override + public void value(TemplateContext tc, final String variable, boolean encoded) { + list.add(new SpecValueCode(tc, df, variable, encoded)); + } + + static class SpecPartialCode extends PartialCode { + private final String indent; + + public SpecPartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable, String indent) { + super(tc, cf, variable); + this.indent = indent; + } + + @Override + protected Writer executePartial(Writer writer, final List scopes) { + partial.execute(new IndentWriter(writer, indent), scopes); + return writer; + } + } + + static class SpecValueCode extends ValueCode { + + public SpecValueCode(TemplateContext tc, DefaultMustacheFactory df, String variable, boolean encoded) { + super(tc, df, variable, encoded); + } + + @Override + protected void execute(Writer writer, final String value) throws IOException { + if (writer instanceof IndentWriter) { + IndentWriter iw = (IndentWriter) writer; + iw.flushIndent(); + writer = iw.inner; + while (writer instanceof IndentWriter) { + writer = ((IndentWriter) writer).inner; + } + } + + super.execute(writer, value); + } + } +} diff --git a/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java b/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java index f899d8406..224a73a02 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/ExtendCode.java @@ -23,7 +23,7 @@ public class ExtendCode extends PartialCode { private final DefaultMustacheFactory mf; public ExtendCode(TemplateContext tc, DefaultMustacheFactory mf, Mustache codes, String name) throws MustacheException { - super(tc, mf, codes, "<", name, ""); + super(tc, mf, codes, "<", name); this.mf = mf; } diff --git a/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java b/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java index 40ff3a07d..b0dac3f9b 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/PartialCode.java @@ -1,7 +1,6 @@ package com.github.mustachejava.codes; import com.github.mustachejava.*; -import com.github.mustachejava.util.IndentWriter; import java.io.IOException; import java.io.Writer; @@ -14,13 +13,9 @@ public class PartialCode extends DefaultCode { protected int recrusionLimit; protected boolean isRecursive; - private final String indent; - - protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mustache, String type, String variable, String indent) { + protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mustache, String type, String variable) { super(tc, df, mustache, variable, type); - this.indent = indent; - // Use the name of the parent to get the name of the partial String file = tc.file(); int dotindex = file.lastIndexOf("."); @@ -30,8 +25,8 @@ protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mu recrusionLimit = df.getRecursionLimit(); } - public PartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable, String indent) { - this(tc, cf, null, ">", variable, indent); + public PartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable) { + this(tc, cf, null, ">", variable); } @Override @@ -73,12 +68,16 @@ public Writer execute(Writer writer, final List scopes) { } writer = depthLimitedWriter; } - partial.execute(new IndentWriter(writer, indent), scopes); + Writer execute = executePartial(writer, scopes); if (isRecursive) { assert depthLimitedWriter != null; depthLimitedWriter.decr(); } - return appendText(writer); + return appendText(execute); + } + + protected Writer executePartial(Writer writer, final List scopes) { + return partial.execute(writer, scopes); } @Override diff --git a/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java b/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java index 8d73ca53c..fabbf3a90 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/ValueCode.java @@ -4,7 +4,6 @@ import com.github.mustachejava.FragmentKey; import com.github.mustachejava.MustacheException; import com.github.mustachejava.TemplateContext; -import com.github.mustachejava.util.IndentWriter; import com.github.mustachejava.util.LatchedWriter; import com.github.mustachejava.util.Node; @@ -76,18 +75,6 @@ public Writer execute(Writer writer, final List scopes) { } } - private Writer getInnerWriter(Writer w) throws IOException { - if (w instanceof IndentWriter) { - IndentWriter iw = (IndentWriter) w; - iw.flushIndent(); - w = iw.inner; - while (w instanceof IndentWriter) { - w = ((IndentWriter) w).inner; - } - } - return w; - } - protected Writer handleCallable(Writer writer, final Callable callable, final List scopes) throws Exception { if (les == null) { Object call = callable.call(); @@ -138,9 +125,9 @@ protected void execute(Writer writer, String value) throws IOException { // Treat null values as the empty string if (value != null) { if (encoded) { - df.encode(value, getInnerWriter(writer)); + df.encode(value, writer); } else { - getInnerWriter(writer).write(value); + writer.write(value); } } } diff --git a/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java b/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java index c040a081a..264144471 100644 --- a/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java +++ b/compiler/src/test/java/com/github/mustachejava/InterpreterTest.java @@ -688,7 +688,7 @@ public void partial(TemplateContext tc, String variable, String indent) { if (variable.startsWith("+")) { // This is a dynamic partial rather than a static one TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable.substring(1).trim(), indent) { + list.add(new PartialCode(partialTC, df, variable.substring(1).trim()) { @Override public synchronized void init() { filterText(); @@ -1224,7 +1224,7 @@ public MustacheVisitor createMustacheVisitor() { @Override public void partial(TemplateContext tc, String variable, String indent) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); - list.add(new PartialCode(partialTC, df, variable, indent) { + list.add(new PartialCode(partialTC, df, variable) { @Override protected String partialName() { return name; From e7ce2da8fc3ebcd978bf8c595bf77557c3fddf91 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 15:38:47 +0200 Subject: [PATCH 03/14] added spec tests for full spec conformity --- .../mustachejava/SpecMustacheFactory.java | 30 +++++++++++++++++++ .../com/github/mustachejava/FullSpecTest.java | 24 +++++++++++++++ .../com/github/mustachejava/SpecTest.java | 6 +++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 compiler/src/test/java/com/github/mustachejava/FullSpecTest.java diff --git a/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java index 06e5e84a5..2278b91d5 100644 --- a/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java +++ b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java @@ -1,5 +1,9 @@ package com.github.mustachejava; +import com.github.mustachejava.resolver.DefaultResolver; + +import java.io.File; + /** * This factory is similar to DefaultMustacheFactory but handles whitespace according to the mustache specification. * Therefore the rendering is less performant than with the DefaultMustacheFactory. @@ -9,4 +13,30 @@ public class SpecMustacheFactory extends DefaultMustacheFactory { public MustacheVisitor createMustacheVisitor() { return new SpecMustacheVisitor(this); } + + public SpecMustacheFactory() { + super(); + } + + public SpecMustacheFactory(MustacheResolver mustacheResolver) { + super(mustacheResolver); + } + + /** + * Use the classpath to resolve mustache templates. + * + * @param classpathResourceRoot the location in the resources where templates are stored + */ + public SpecMustacheFactory(String classpathResourceRoot) { + super(classpathResourceRoot); + } + + /** + * Use the file system to resolve mustache templates. + * + * @param fileRoot the root of the file system where templates are stored + */ + public SpecMustacheFactory(File fileRoot) { + super(fileRoot); + } } diff --git a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java new file mode 100644 index 000000000..393736130 --- /dev/null +++ b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java @@ -0,0 +1,24 @@ +package com.github.mustachejava; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.Reader; +import java.io.StringReader; + +public class FullSpecTest extends SpecTest { + @Override + protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) { + return new SpecMustacheFactory("/spec/specs") { + @Override + public Reader getReader(String resourceName) { + JsonNode partial = test.get("partials").get(resourceName); + return new StringReader(partial == null ? "" : partial.asText()); + } + }; + } + + @Override + protected String transformOutput(String output) { + return output; + } +} diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index 438a85dc3..7b602c67f 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -147,7 +147,7 @@ Function lambda() { StringWriter writer = new StringWriter(); compile.execute(writer, new Object[]{new ObjectMapper().readValue(data.toString(), Map.class), functionMap.get(file)}); String expected = test.get("expected").asText(); - if (writer.toString().replaceAll("\\s+", "").equals(expected.replaceAll("\\s+", ""))) { + if (transformOutput(writer.toString()).equals(transformOutput(expected))) { System.out.print(": success"); if (writer.toString().equals(expected)) { System.out.println("!"); @@ -174,6 +174,10 @@ Function lambda() { assertFalse(fail > 0); } + protected String transformOutput(String output) { + return output.replaceAll("\\s+", ""); + } + protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) { return new DefaultMustacheFactory("/spec/specs") { @Override From 09a455bd46bc0b6387d0b1144a08478beedf2b11 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 15:47:06 +0200 Subject: [PATCH 04/14] making whitespace parsing backwards compatible --- .../mustachejava/DefaultMustacheFactory.java | 6 ++- .../github/mustachejava/MustacheParser.java | 10 +++- .../mustachejava/SpecMustacheFactory.java | 53 ++++++++++--------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheFactory.java index 9140c0536..20a9fd789 100644 --- a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheFactory.java +++ b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheFactory.java @@ -35,7 +35,7 @@ public class DefaultMustacheFactory implements MustacheFactory { /** * This parser should work with any MustacheFactory */ - protected final MustacheParser mc = new MustacheParser(this); + protected final MustacheParser mc = createParser(); /** * New templates that are generated at runtime are cached here. The template key @@ -259,6 +259,10 @@ public Mustache compilePartial(String s) { } } + protected MustacheParser createParser() { + return new MustacheParser(this); + } + protected Function getMustacheCacheFunction() { return mc::compile; } diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 885ea3824..ed59dbd0e 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -16,10 +16,16 @@ public class MustacheParser { public static final String DEFAULT_SM = "{{"; public static final String DEFAULT_EM = "}}"; + private final boolean specConformWhitespace; private MustacheFactory mf; - protected MustacheParser(MustacheFactory mf) { + protected MustacheParser(MustacheFactory mf, boolean specConformWhitespace) { this.mf = mf; + this.specConformWhitespace = specConformWhitespace; + } + + protected MustacheParser(MustacheFactory mf) { + this(mf, false); } public Mustache compile(String file) { @@ -187,7 +193,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); // a new line following a partial is dropped - if (startOfLine) { + if (specConformWhitespace && startOfLine) { br.mark(2); int ca = br.read(); if (ca == '\r') { diff --git a/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java index 2278b91d5..d4d9065cd 100644 --- a/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java +++ b/compiler/src/main/java/com/github/mustachejava/SpecMustacheFactory.java @@ -9,34 +9,39 @@ * Therefore the rendering is less performant than with the DefaultMustacheFactory. */ public class SpecMustacheFactory extends DefaultMustacheFactory { - @Override - public MustacheVisitor createMustacheVisitor() { - return new SpecMustacheVisitor(this); - } + @Override + public MustacheVisitor createMustacheVisitor() { + return new SpecMustacheVisitor(this); + } - public SpecMustacheFactory() { - super(); - } + public SpecMustacheFactory() { + super(); + } - public SpecMustacheFactory(MustacheResolver mustacheResolver) { - super(mustacheResolver); - } + public SpecMustacheFactory(MustacheResolver mustacheResolver) { + super(mustacheResolver); + } - /** - * Use the classpath to resolve mustache templates. - * - * @param classpathResourceRoot the location in the resources where templates are stored - */ - public SpecMustacheFactory(String classpathResourceRoot) { - super(classpathResourceRoot); - } + /** + * Use the classpath to resolve mustache templates. + * + * @param classpathResourceRoot the location in the resources where templates are stored + */ + public SpecMustacheFactory(String classpathResourceRoot) { + super(classpathResourceRoot); + } - /** - * Use the file system to resolve mustache templates. - * - * @param fileRoot the root of the file system where templates are stored - */ - public SpecMustacheFactory(File fileRoot) { + /** + * Use the file system to resolve mustache templates. + * + * @param fileRoot the root of the file system where templates are stored + */ + public SpecMustacheFactory(File fileRoot) { super(fileRoot); } + + @Override + protected MustacheParser createParser() { + return new MustacheParser(this, true); + } } From d611bed628edb30405f4a898b3ed7c93c06aabfb Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 18:12:36 +0200 Subject: [PATCH 05/14] set failing tests to ignore for now --- .../com/github/mustachejava/FullSpecTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java index 393736130..46f0e225e 100644 --- a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java @@ -1,11 +1,43 @@ package com.github.mustachejava; import com.fasterxml.jackson.databind.JsonNode; +import org.junit.Ignore; +import org.junit.Test; import java.io.Reader; import java.io.StringReader; public class FullSpecTest extends SpecTest { + @Override + @Test + @Ignore("not ready yet") + public void interpolations() { + } + + @Override + @Test + @Ignore("not ready yet") + public void sections() { + } + + @Override + @Test + @Ignore("not ready yet") + public void delimiters() { + } + + @Override + @Test + @Ignore("not ready yet") + public void inverted() { + } + + @Override + @Test + @Ignore("not ready yet") + public void lambdas() { + } + @Override protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) { return new SpecMustacheFactory("/spec/specs") { From 2454efabaa9abdecaec5a143f722615f0e42ff66 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 18:13:44 +0200 Subject: [PATCH 06/14] Revert "set failing tests to ignore for now" This reverts commit d611bed628edb30405f4a898b3ed7c93c06aabfb. --- .../com/github/mustachejava/FullSpecTest.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java index 46f0e225e..393736130 100644 --- a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java @@ -1,43 +1,11 @@ package com.github.mustachejava; import com.fasterxml.jackson.databind.JsonNode; -import org.junit.Ignore; -import org.junit.Test; import java.io.Reader; import java.io.StringReader; public class FullSpecTest extends SpecTest { - @Override - @Test - @Ignore("not ready yet") - public void interpolations() { - } - - @Override - @Test - @Ignore("not ready yet") - public void sections() { - } - - @Override - @Test - @Ignore("not ready yet") - public void delimiters() { - } - - @Override - @Test - @Ignore("not ready yet") - public void inverted() { - } - - @Override - @Test - @Ignore("not ready yet") - public void lambdas() { - } - @Override protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) { return new SpecMustacheFactory("/spec/specs") { From 52e0b5461ae5a467b5fe67cbcc95ccaa6800e836 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 16:00:09 +0200 Subject: [PATCH 07/14] fixed lambdas --- .../github/mustachejava/MustacheParser.java | 31 ++++++++++++++----- .../com/github/mustachejava/SpecTest.java | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index ed59dbd0e..2e34b0589 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -193,14 +193,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); // a new line following a partial is dropped - if (specConformWhitespace && startOfLine) { - br.mark(2); - int ca = br.read(); - if (ca == '\r') { - ca = br.read(); - } - if (ca != '\n') br.reset(); - } + chompNewline(startOfLine, br); break; } case '{': { @@ -256,6 +249,9 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger } sm = split[0]; em = split[1]; + + chompNewline(startOfLine, br); + break; default: { if (c == -1) { @@ -314,4 +310,23 @@ private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file, return new StringBuilder(); } + /** + * Some statements such as partials are treated as "standalone". + * This means that if they are the only content on this line (except whitespace) then the following newline is + * chopped. + * For backwards compatibility we only do this if the parser is explicitly configured so. + * @param startOfLine If the statement that was just read was at the start of line + * @param br The reader + * @throws IOException + */ + private void chompNewline(boolean startOfLine, Reader br) throws IOException { + if (specConformWhitespace && startOfLine) { + br.mark(2); + int ca = br.read(); + if (ca == '\r') { + ca = br.read(); + } + if (ca != '\n') br.reset(); + } + } } diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index 7b602c67f..764f4b9e4 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -157,7 +157,7 @@ Function lambda() { } } else { System.out.println(": failed!"); - System.out.println(expected + " != " + writer.toString()); + System.out.println(expected.replace("\n", "\\n").replace("\r", "\\r") + " != " + writer.toString().replace("\n", "\\n").replace("\r", "\\r")); System.out.println(test); failed = true; } From dc14721adba01b19dd12f50b92373a5750efddfe Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 16:11:32 +0200 Subject: [PATCH 08/14] fixed delimiters --- .../github/mustachejava/MustacheParser.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 2e34b0589..f8fae4bf5 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -240,7 +240,13 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger break; case '=': // Change delimiters - out = write(mv, out, file, currentLine.intValue(), startOfLine); + if (chompNewline(startOfLine & onlywhitespace, br)) { + // indented standalone tags drop the preceding whitespace + out = new StringBuilder(); + } else { + out = write(mv, out, file, currentLine.intValue(), startOfLine); + } + String trimmed = command.substring(1).trim(); String[] split = trimmed.split("\\s+"); if (split.length != 2) { @@ -249,9 +255,6 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger } sm = split[0]; em = split[1]; - - chompNewline(startOfLine, br); - break; default: { if (c == -1) { @@ -315,18 +318,28 @@ private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file, * This means that if they are the only content on this line (except whitespace) then the following newline is * chopped. * For backwards compatibility we only do this if the parser is explicitly configured so. - * @param startOfLine If the statement that was just read was at the start of line + * @param firstStmt If the statement that was just read was at the start of line with only whitespace preceding it * @param br The reader + * @return true if a following new line was chomped or the buffer was finished * @throws IOException */ - private void chompNewline(boolean startOfLine, Reader br) throws IOException { - if (specConformWhitespace && startOfLine) { + private boolean chompNewline(boolean firstStmt, Reader br) throws IOException { + boolean chomped = false; + + if (specConformWhitespace && firstStmt) { br.mark(2); int ca = br.read(); if (ca == '\r') { ca = br.read(); } - if (ca != '\n') br.reset(); + + if (ca == '\n' || ca == -1) { + chomped = true; + } else { + br.reset(); + } } + + return chomped; } } From ea9f9c506f04afecb72bbf94beb929bd173f0dd8 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 16:14:43 +0200 Subject: [PATCH 09/14] renaming --- .../com/github/mustachejava/MustacheParser.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index f8fae4bf5..9a62a5f09 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -193,7 +193,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); // a new line following a partial is dropped - chompNewline(startOfLine, br); + trimNewline(startOfLine, br); break; } case '{': { @@ -240,7 +240,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger break; case '=': // Change delimiters - if (chompNewline(startOfLine & onlywhitespace, br)) { + if (trimNewline(startOfLine & onlywhitespace, br)) { // indented standalone tags drop the preceding whitespace out = new StringBuilder(); } else { @@ -320,11 +320,11 @@ private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file, * For backwards compatibility we only do this if the parser is explicitly configured so. * @param firstStmt If the statement that was just read was at the start of line with only whitespace preceding it * @param br The reader - * @return true if a following new line was chomped or the buffer was finished + * @return true if trimming was allowed and a following new line was removed or the buffer was finished; * @throws IOException */ - private boolean chompNewline(boolean firstStmt, Reader br) throws IOException { - boolean chomped = false; + private boolean trimNewline(boolean firstStmt, Reader br) throws IOException { + boolean trimmed = false; if (specConformWhitespace && firstStmt) { br.mark(2); @@ -334,12 +334,12 @@ private boolean chompNewline(boolean firstStmt, Reader br) throws IOException { } if (ca == '\n' || ca == -1) { - chomped = true; + trimmed = true; } else { br.reset(); } } - return chomped; + return trimmed; } } From e6c2b56ea930510c463591a3f9e393f3881c2091 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 16:42:38 +0200 Subject: [PATCH 10/14] fixed whitespace in sections --- .../com/github/mustachejava/MustacheParser.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 9a62a5f09..0530495f2 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -80,7 +80,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger // Increment the line if (c == '\n') { currentLine.incrementAndGet(); - if (!iterable || (iterable && !onlywhitespace)) { + if (specConformWhitespace || !iterable || (iterable && !onlywhitespace)) { if (sawCR) out.append("\r"); out.append("\n"); } @@ -146,13 +146,16 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger case '$': { boolean oldStartOfLine = startOfLine; startOfLine = startOfLine & onlywhitespace; - int line = currentLine.get(); - final Mustache mustache = compile(br, variable, currentLine, file, sm, em, startOfLine); - int lines = currentLine.get() - line; - if (!onlywhitespace || lines == 0) { + + if (!trimNewline(startOfLine, br)) { write(mv, out, file, currentLine.intValue(), oldStartOfLine); } out = new StringBuilder(); + + int line = currentLine.get(); + final Mustache mustache = compile(br, variable, currentLine, file, sm, em, startOfLine); + int lines = currentLine.get() - line; + switch (ch) { case '#': mv.iterable(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache); @@ -175,7 +178,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger } case '/': { // Tag end - if (!startOfLine || !onlywhitespace) { + if (!trimNewline(onlywhitespace & startOfLine, br)) { write(mv, out, file, currentLine.intValue(), startOfLine); } if (!variable.equals(tag)) { From 2d2acac3f7bcfa7070122c4e14e3fe83ae291467 Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 18:05:57 +0200 Subject: [PATCH 11/14] fixed parser; all whitespace tests work now --- .../github/mustachejava/MustacheParser.java | 69 ++++++++++++++----- .../com/github/mustachejava/SpecTest.java | 2 +- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 0530495f2..8bf5058d3 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -16,7 +16,12 @@ public class MustacheParser { public static final String DEFAULT_SM = "{{"; public static final String DEFAULT_EM = "}}"; + + /** + * For legacy reasons we keep the non-spec conform parsing of whitespace unless this flag is true. + */ private final boolean specConformWhitespace; + private MustacheFactory mf; protected MustacheParser(MustacheFactory mf, boolean specConformWhitespace) { @@ -147,15 +152,18 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger boolean oldStartOfLine = startOfLine; startOfLine = startOfLine & onlywhitespace; - if (!trimNewline(startOfLine, br)) { - write(mv, out, file, currentLine.intValue(), oldStartOfLine); - } - out = new StringBuilder(); + boolean nextStartOfLine = specConformWhitespace && trimNewline(startOfLine, br); int line = currentLine.get(); final Mustache mustache = compile(br, variable, currentLine, file, sm, em, startOfLine); int lines = currentLine.get() - line; + if ((specConformWhitespace && !nextStartOfLine) || + (!specConformWhitespace && (!onlywhitespace || lines == 0))) { + write(mv, out, file, currentLine.intValue(), oldStartOfLine); + } + out = new StringBuilder(); + switch (ch) { case '#': mv.iterable(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache); @@ -173,14 +181,21 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger mv.checkName(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache); break; } + + startOfLine = nextStartOfLine; iterable = lines != 0; break; } case '/': { // Tag end - if (!trimNewline(onlywhitespace & startOfLine, br)) { + if (specConformWhitespace) { + if (!trimNewline(onlywhitespace & startOfLine, br)) { + write(mv, out, file, currentLine.intValue(), startOfLine); + } + } else if (!startOfLine || !onlywhitespace) { write(mv, out, file, currentLine.intValue(), startOfLine); } + if (!variable.equals(tag)) { TemplateContext tc = new TemplateContext(sm, em, file, currentLine.get(), startOfLine); throw new MustacheException( @@ -196,7 +211,7 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); // a new line following a partial is dropped - trimNewline(startOfLine, br); + startOfLine = specConformWhitespace && trimNewline(startOfLine, br); break; } case '{': { @@ -213,12 +228,16 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger } } mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), name, false); + + startOfLine = false; break; } case '&': { // Not escaped out = write(mv, out, file, currentLine.intValue(), startOfLine); mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), variable, false); + + startOfLine = false; break; } case '%': @@ -235,21 +254,17 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger args = variable.substring(index + 1); } mv.pragma(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), pragma, args); + + startOfLine = false; break; case '!': // Comment mv.comment(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable); out = write(mv, out, file, currentLine.intValue(), startOfLine); + startOfLine = false; break; case '=': // Change delimiters - if (trimNewline(startOfLine & onlywhitespace, br)) { - // indented standalone tags drop the preceding whitespace - out = new StringBuilder(); - } else { - out = write(mv, out, file, currentLine.intValue(), startOfLine); - } - String trimmed = command.substring(1).trim(); String[] split = trimmed.split("\\s+"); if (split.length != 2) { @@ -258,6 +273,23 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger } sm = split[0]; em = split[1]; + + + if (specConformWhitespace) { + boolean sol = trimNewline(startOfLine & onlywhitespace, br); + + if (!sol) { + write(mv, out, file, currentLine.intValue(), startOfLine); + } + + startOfLine = sol; + } else { + write(mv, out, file, currentLine.intValue(), startOfLine); + startOfLine = false; + } + out = new StringBuilder(); + + break; default: { if (c == -1) { @@ -267,11 +299,16 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger // Reference out = write(mv, out, file, currentLine.intValue(), startOfLine); mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), command.trim(), true); + startOfLine = false; break; } } - // Additional text is no longer at the start of the line - startOfLine = false; + if (!specConformWhitespace) { + // Additional text is no longer at the start of the line + // in spec-conform whitespace parsing we sometimes chop a whole line so we let the individual commands + // decide wether we are at the start of a line + startOfLine = false; + } continue; } else { // Only one @@ -329,7 +366,7 @@ private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file, private boolean trimNewline(boolean firstStmt, Reader br) throws IOException { boolean trimmed = false; - if (specConformWhitespace && firstStmt) { + if (firstStmt) { br.mark(2); int ca = br.read(); if (ca == '\r') { diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index 764f4b9e4..ee0f2baf4 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -157,7 +157,7 @@ Function lambda() { } } else { System.out.println(": failed!"); - System.out.println(expected.replace("\n", "\\n").replace("\r", "\\r") + " != " + writer.toString().replace("\n", "\\n").replace("\r", "\\r")); + System.out.println(expected + " !=" + writer.toString()); System.out.println(test); failed = true; } From 3a5579d6645e6a787d469d6ee3e6ee0578cf554f Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Tue, 18 Sep 2018 18:16:54 +0200 Subject: [PATCH 12/14] unfix --- compiler/src/test/java/com/github/mustachejava/SpecTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index ee0f2baf4..7b602c67f 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -157,7 +157,7 @@ Function lambda() { } } else { System.out.println(": failed!"); - System.out.println(expected + " !=" + writer.toString()); + System.out.println(expected + " != " + writer.toString()); System.out.println(test); failed = true; } From e89ccc30cb854158f2f79b4816f52724450b437a Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Fri, 26 Oct 2018 15:57:13 +0200 Subject: [PATCH 13/14] fixed specs for comments --- .../com/github/mustachejava/MustacheParser.java | 17 +++++++++++++++-- .../java/com/github/mustachejava/SpecTest.java | 5 +++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index 8bf5058d3..59f82ee00 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -260,8 +260,21 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger case '!': // Comment mv.comment(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable); - out = write(mv, out, file, currentLine.intValue(), startOfLine); - startOfLine = false; + + if (specConformWhitespace) { + boolean sol = trimNewline(startOfLine & onlywhitespace, br); + + if (!sol) { + write(mv, out, file, currentLine.intValue(), startOfLine); + } + + startOfLine = sol; + } else { + write(mv, out, file, currentLine.intValue(), startOfLine); + startOfLine = false; + } + out = new StringBuilder(); + break; case '=': // Change delimiters diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index 7b602c67f..7c795d29c 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -22,6 +22,11 @@ */ public class SpecTest { + @Test + public void comments() throws IOException { + run(getSpec("comments.yml")); + } + @Test public void interpolations() throws IOException { run(getSpec("interpolation.yml")); From aeadf01ce3bd511315d482faab0bdfaf93eb5bda Mon Sep 17 00:00:00 2001 From: David Poetzsch-Heffter Date: Fri, 26 Oct 2018 17:00:28 +0200 Subject: [PATCH 14/14] fixed whitespace for nested partials with variables --- .../main/java/com/github/mustachejava/SpecMustacheVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java index 8736d2cee..5d59d2281 100644 --- a/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/SpecMustacheVisitor.java @@ -52,6 +52,7 @@ protected void execute(Writer writer, final String value) throws IOException { iw.flushIndent(); writer = iw.inner; while (writer instanceof IndentWriter) { + ((IndentWriter) writer).flushIndent(); writer = ((IndentWriter) writer).inner; } }