Enabled more tests, fixed more bugs.

Mainly we also needed to trim around comment tags, but also there were a bunch
of tricky edge cases that were only partially addressed. This is some edgy
business!
This commit is contained in:
Michael Bayne
2014-12-16 14:47:31 -08:00
parent 0e2a7d13d5
commit 09fee0f037
3 changed files with 32 additions and 22 deletions
@@ -291,38 +291,39 @@ public class Mustache
*/ */
protected static Template compile (Reader source, Compiler compiler) { protected static Template compile (Reader source, Compiler compiler) {
Accumulator accum = new Parser(compiler).parse(source); Accumulator accum = new Parser(compiler).parse(source);
return new Template(trim(accum.finish()), compiler); return new Template(trim(accum.finish(), true), compiler);
} }
private Mustache () {} // no instantiateski private Mustache () {} // no instantiateski
protected static Template.Segment[] trim (Template.Segment[] segs) { protected static Template.Segment[] trim (Template.Segment[] segs, boolean top) {
// now that we have all of our segments, we make a pass through them to trim whitespace // now that we have all of our segments, we make a pass through them to trim whitespace
// from section tags which stand alone on their lines // from section tags which stand alone on their lines
for (int ii = 0, ll = segs.length; ii < ll; ii++) { for (int ii = 0, ll = segs.length; ii < ll; ii++) {
Template.Segment seg = segs[ii]; Template.Segment seg = segs[ii];
Template.Segment pseg = (ii > 0) ? segs[ii-1] : null; Template.Segment pseg = (ii > 0 ) ? segs[ii-1] : null;
Template.Segment nseg = (ii < ll-1) ? segs[ii+1] : null; Template.Segment nseg = (ii < ll-1) ? segs[ii+1] : null;
StringSegment prev = (pseg instanceof StringSegment) ? (StringSegment)pseg : null; StringSegment prev = (pseg instanceof StringSegment) ? (StringSegment)pseg : null;
StringSegment next = (nseg instanceof StringSegment) ? (StringSegment)nseg : null; StringSegment next = (nseg instanceof StringSegment) ? (StringSegment)nseg : null;
boolean prevTrailsBlank = (pseg == null || (prev != null && prev.trailsBlank())); // if we're at the top-level there are virtual "blank lines" before & after segs
boolean nextLeadsBlank = (nseg == null || (next != null && next.leadsBlank())); boolean prevBlank = ((pseg == null && top) || (prev != null && prev.trailsBlank()));
boolean nextBlank = ((nseg == null && top) || (next != null && next.leadsBlank()));
boolean trimPrev = false, trimNext = false; boolean trimPrev = false, trimNext = false;
// potentially trim the open and close tags of a block segment // potentially trim around the open and close tags of a block segment
if (seg instanceof BlockSegment) { if (seg instanceof BlockSegment) {
BlockSegment block = (BlockSegment)seg; BlockSegment block = (BlockSegment)seg;
if (prevTrailsBlank && block.firstLeadsBlank()) { if (prevBlank && block.firstLeadsBlank()) {
if (pseg != null) segs[ii-1] = prev.trimTrailBlank(); if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
block.trimFirstBlank(); block.trimFirstBlank();
} }
if (nextLeadsBlank && block.lastTrailsBlank()) { if (nextBlank && block.lastTrailsBlank()) {
block.trimLastBlank(); block.trimLastBlank();
if (nseg != null) segs[ii+1] = next.trimLeadBlank(); if (nseg != null) segs[ii+1] = next.trimLeadBlank();
} }
} }
// potentially trim around a delims segment // potentially trim around non-printing (comments/delims) segments
else if (seg instanceof DelimsSegment) { else if (seg instanceof FauxSegment) {
if (prevTrailsBlank && nextLeadsBlank) { if (prevBlank && nextBlank) {
if (pseg != null) segs[ii-1] = prev.trimTrailBlank(); if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
if (nseg != null) segs[ii+1] = next.trimLeadBlank(); if (nseg != null) segs[ii+1] = next.trimLeadBlank();
} }
@@ -362,7 +363,7 @@ public class Mustache
int tagStartColumn = -1; int tagStartColumn = -1;
public Parser (Compiler compiler) { public Parser (Compiler compiler) {
this.accum = new Accumulator(compiler); this.accum = new Accumulator(compiler, true);
this.delims = compiler.delims.copy(); this.delims = compiler.delims.copy();
} }
@@ -458,7 +459,7 @@ public class Mustache
if (text.charAt(0) == '=') { if (text.charAt(0) == '=') {
delims.updateDelims(text.substring(1, text.length()-1)); delims.updateDelims(text.substring(1, text.length()-1));
text.setLength(0); text.setLength(0);
accum.addDelimsSegment(); // for newline trimming accum.addFauxSegment(); // for newline trimming
} else { } else {
// if the delimiters are {{ and }}, and the tag starts with {{{ then // if the delimiters are {{ and }}, and the tag starts with {{{ then
// require that it end with }}} and disable escaping // require that it end with }}} and disable escaping
@@ -553,13 +554,14 @@ public class Mustache
} }
protected static class Accumulator { protected static class Accumulator {
public Accumulator (Compiler compiler) { public Accumulator (Compiler compiler, boolean topLevel) {
_comp = compiler; _comp = compiler;
_topLevel = topLevel;
} }
public void addTextSegment (StringBuilder text) { public void addTextSegment (StringBuilder text) {
if (text.length() > 0) { if (text.length() > 0) {
_segs.add(new StringSegment(text.toString(), _segs.isEmpty())); _segs.add(new StringSegment(text.toString(), _segs.isEmpty() && _topLevel));
text.setLength(0); text.setLength(0);
} }
} }
@@ -573,7 +575,7 @@ public class Mustache
switch (tag.charAt(0)) { switch (tag.charAt(0)) {
case '#': case '#':
requireNoNewlines(tag, tagLine); requireNoNewlines(tag, tagLine);
return new Accumulator(_comp) { return new Accumulator(_comp, false) {
@Override public Template.Segment[] finish () { @Override public Template.Segment[] finish () {
throw new MustacheParseException( throw new MustacheParseException(
"Section missing close tag '" + tag1 + "'", tagLine); "Section missing close tag '" + tag1 + "'", tagLine);
@@ -591,7 +593,7 @@ public class Mustache
case '^': case '^':
requireNoNewlines(tag, tagLine); requireNoNewlines(tag, tagLine);
return new Accumulator(_comp) { return new Accumulator(_comp, false) {
@Override public Template.Segment[] finish () { @Override public Template.Segment[] finish () {
throw new MustacheParseException( throw new MustacheParseException(
"Inverted section missing close tag '" + tag1 + "'", tagLine); "Inverted section missing close tag '" + tag1 + "'", tagLine);
@@ -609,6 +611,7 @@ public class Mustache
case '!': case '!':
// comment!, ignore // comment!, ignore
_segs.add(new FauxSegment()); // for whitespace trimming
return this; return this;
case '&': case '&':
@@ -623,8 +626,8 @@ public class Mustache
} }
} }
public void addDelimsSegment () { public void addFauxSegment () {
_segs.add(new DelimsSegment()); _segs.add(new FauxSegment());
} }
public Template.Segment[] finish () { public Template.Segment[] finish () {
@@ -652,6 +655,7 @@ public class Mustache
} }
protected final Compiler _comp; protected final Compiler _comp;
protected final boolean _topLevel;
protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>(); protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>();
} }
@@ -789,7 +793,7 @@ public class Mustache
protected BlockSegment (String name, Template.Segment[] segs, int line) { protected BlockSegment (String name, Template.Segment[] segs, int line) {
super(name, line); super(name, line);
_segs = trim(segs); _segs = trim(segs, false);
} }
protected void executeSegs (Template tmpl, Template.Context ctx, Writer out) { protected void executeSegs (Template tmpl, Template.Context ctx, Writer out) {
for (Template.Segment seg : _segs) { for (Template.Segment seg : _segs) {
@@ -871,8 +875,9 @@ public class Mustache
protected final Compiler _comp; protected final Compiler _comp;
} }
protected static class DelimsSegment extends Template.Segment { protected static class FauxSegment extends Template.Segment {
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
@Override public String toString () { return "Faux"; }
} }
/** Used when we have only a single character delimiter. */ /** Used when we have only a single character delimiter. */
@@ -439,6 +439,11 @@ public class MustacheTest
test("Begin.\nEnd.\n", "Begin.\n{{=@ @=}}\nEnd.\n", context()); test("Begin.\nEnd.\n", "Begin.\n{{=@ @=}}\nEnd.\n", context());
} }
@Test public void testNoTrimNewlineFromNestedTagAt0 () {
test(" | \n | \n", " | {{^boolean}}{{! comment }}\n {{/boolean}} | \n",
context("boolean", false));
}
@Test public void testTrimBlank () { @Test public void testTrimBlank () {
Mustache.StringSegment str = new Mustache.StringSegment(" \r\n ", false); Mustache.StringSegment str = new Mustache.StringSegment(" \r\n ", false);
check("Text( )-1/0", str.trimLeadBlank().toString()); check("Text( )-1/0", str.trimLeadBlank().toString());
@@ -47,7 +47,7 @@ public class SpecRunner extends BlockJUnit4ClassRunner
private List<FrameworkMethod> computeTests () throws InitializationError { private List<FrameworkMethod> computeTests () throws InitializationError {
String[] groups = new String[] { String[] groups = new String[] {
// "comments", "comments",
"delimiters", "delimiters",
"interpolation", "interpolation",
"inverted", "inverted",