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:
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user