Moved falsey-ness detection into compiler.

Fixes #39.

This fixes a bug where falsey values were not properly triggering inverted
sections, and consolidates some logic.

Also did some renaming. Couldn't help myself.
This commit is contained in:
Michael Bayne
2013-07-03 16:47:21 -07:00
parent 6e3051c3ba
commit 9441d062a6
2 changed files with 42 additions and 26 deletions
@@ -162,6 +162,14 @@ public class Mustache
return (nullValue == null) ? null : nullValue.replace("{{name}}", name); return (nullValue == null) ? null : nullValue.replace("{{name}}", name);
} }
/** Returns true if the supplied value is "falsey". If {@link #emptyStringIsFalse} is true,
* then empty strings are considered falsey. If {@link #zeroIsFalse} is true, then zero
* values are considered falsey. */
public boolean isFalsey (Object value) {
return (emptyStringIsFalse && "".equals(value)) ||
(zeroIsFalse && (value instanceof Number) && ((Number)value).longValue() == 0);
}
protected Compiler (boolean escapeHTML, boolean standardsMode, String nullValue, protected Compiler (boolean escapeHTML, boolean standardsMode, String nullValue,
boolean missingIsNull, boolean emptyStringIsFalse, boolean zeroIsFalse, boolean missingIsNull, boolean emptyStringIsFalse, boolean zeroIsFalse,
TemplateLoader loader, Collector collector, Delims delims) { TemplateLoader loader, Collector collector, Delims delims) {
@@ -494,7 +502,7 @@ public class Mustache
protected static class Accumulator { protected static class Accumulator {
public Accumulator (Compiler compiler) { public Accumulator (Compiler compiler) {
_compiler = compiler; _comp = compiler;
} }
public boolean justOpenedOrClosedBlock () { public boolean justOpenedOrClosedBlock () {
@@ -518,7 +526,7 @@ public class Mustache
switch (tag.charAt(0)) { switch (tag.charAt(0)) {
case '#': case '#':
requireNoNewlines(tag, tagLine); requireNoNewlines(tag, tagLine);
return new Accumulator(_compiler) { return new Accumulator(_comp) {
@Override public boolean justOpenedOrClosedBlock () { @Override public boolean justOpenedOrClosedBlock () {
// if we just opened this section, we'll have no segments // if we just opened this section, we'll have no segments
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock(); return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
@@ -529,19 +537,18 @@ public class Mustache
} }
@Override protected Accumulator addCloseSectionSegment (String itag, int line) { @Override protected Accumulator addCloseSectionSegment (String itag, int line) {
requireSameName(tag1, itag, line); requireSameName(tag1, itag, line);
outer._segs.add( outer._segs.add(new SectionSegment(_comp, itag, super.finish(), tagLine));
new SectionSegment(itag, super.finish(), tagLine, _compiler));
return outer; return outer;
} }
}; };
case '>': case '>':
_segs.add(new IncludedTemplateSegment(tag1, _compiler)); _segs.add(new IncludedTemplateSegment(_comp, tag1));
return this; return this;
case '^': case '^':
requireNoNewlines(tag, tagLine); requireNoNewlines(tag, tagLine);
return new Accumulator(_compiler) { return new Accumulator(_comp) {
@Override public boolean justOpenedOrClosedBlock () { @Override public boolean justOpenedOrClosedBlock () {
// if we just opened this section, we'll have no segments // if we just opened this section, we'll have no segments
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock(); return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
@@ -552,7 +559,7 @@ public class Mustache
} }
@Override protected Accumulator addCloseSectionSegment (String itag, int line) { @Override protected Accumulator addCloseSectionSegment (String itag, int line) {
requireSameName(tag1, itag, line); requireSameName(tag1, itag, line);
outer._segs.add(new InvertedSectionSegment(itag, super.finish(), tagLine)); outer._segs.add(new InvertedSegment(_comp, itag, super.finish(), tagLine));
return outer; return outer;
} }
}; };
@@ -572,7 +579,7 @@ public class Mustache
default: default:
requireNoNewlines(tag, tagLine); requireNoNewlines(tag, tagLine);
_segs.add(new VariableSegment(tag, _compiler.escapeHTML, tagLine)); _segs.add(new VariableSegment(tag, _comp.escapeHTML, tagLine));
return this; return this;
} }
} }
@@ -601,7 +608,7 @@ public class Mustache
} }
} }
protected final Compiler _compiler; protected final Compiler _comp;
protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>(); protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>();
} }
@@ -617,16 +624,16 @@ public class Mustache
} }
protected static class IncludedTemplateSegment extends Template.Segment { protected static class IncludedTemplateSegment extends Template.Segment {
public IncludedTemplateSegment (String name, Compiler compiler) { public IncludedTemplateSegment (Compiler compiler, String name) {
_comp = compiler;
_name = name; _name = name;
_compiler = compiler;
} }
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) { @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
// we compile our template lazily to avoid infinie recursion if a template includes // we compile our template lazily to avoid infinie recursion if a template includes
// itself (see issue #13) // itself (see issue #13)
if (_template == null) { if (_template == null) {
try { try {
_template = _compiler.compile(_compiler.loader.getTemplate(_name)); _template = _comp.compile(_comp.loader.getTemplate(_name));
} catch (Exception e) { } catch (Exception e) {
if (e instanceof RuntimeException) { if (e instanceof RuntimeException) {
throw (RuntimeException)e; throw (RuntimeException)e;
@@ -639,8 +646,8 @@ public class Mustache
// would happen if we just called execute() with ctx.data // would happen if we just called execute() with ctx.data
_template.executeSegs(ctx, out); _template.executeSegs(ctx, out);
} }
protected final Compiler _comp;
protected final String _name; protected final String _name;
protected final Compiler _compiler;
protected Template _template; protected Template _template;
} }
@@ -688,13 +695,13 @@ public class Mustache
/** A segment that represents a section. */ /** A segment that represents a section. */
protected static class SectionSegment extends BlockSegment { protected static class SectionSegment extends BlockSegment {
public SectionSegment (String name, Template.Segment[] segs, int line, Compiler compiler) { public SectionSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
super(name, segs, line); super(name, segs, line);
_compiler = compiler; _comp = compiler;
} }
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) { @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
Iterator<?> iter = tmpl._compiler.collector.toIterator(value); Iterator<?> iter = _comp.collector.toIterator(value);
if (iter != null) { if (iter != null) {
int index = 0; int index = 0;
while (iter.hasNext()) { while (iter.hasNext()) {
@@ -712,26 +719,24 @@ public class Mustache
} catch (IOException ioe) { } catch (IOException ioe) {
throw new MustacheException(ioe); throw new MustacheException(ioe);
} }
} else if (_compiler.emptyStringIsFalse && "".equals(value)) { } else if (_comp.isFalsey(value)) {
// omit the section
} else if (_compiler.zeroIsFalse && (value instanceof Number) &&
((Number)value).longValue() == 0) {
// omit the section // omit the section
} else { } else {
executeSegs(tmpl, ctx.nest(value, 0, false, false), out); executeSegs(tmpl, ctx.nest(value, 0, false, false), out);
} }
} }
protected final Compiler _compiler; protected final Compiler _comp;
} }
/** A segment that represents an inverted section. */ /** A segment that represents an inverted section. */
protected static class InvertedSectionSegment extends BlockSegment { protected static class InvertedSegment extends BlockSegment {
public InvertedSectionSegment (String name, Template.Segment[] segs, int line) { public InvertedSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
super(name, segs, line); super(name, segs, line);
_comp = compiler;
} }
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) { @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
Iterator<?> iter = tmpl._compiler.collector.toIterator(value); Iterator<?> iter = _comp.collector.toIterator(value);
if (iter != null) { if (iter != null) {
if (!iter.hasNext()) { if (!iter.hasNext()) {
executeSegs(tmpl, ctx, out); executeSegs(tmpl, ctx, out);
@@ -740,8 +745,11 @@ public class Mustache
if (!(Boolean)value) { if (!(Boolean)value) {
executeSegs(tmpl, ctx, out); executeSegs(tmpl, ctx, out);
} }
} else if (_comp.isFalsey(value)) {
executeSegs(tmpl, ctx, out);
} // TODO: fail? } // TODO: fail?
} }
protected final Compiler _comp;
} }
/** Map of strings that must be replaced inside html attributes and their replacements. (They /** Map of strings that must be replaced inside html attributes and their replacements. (They
@@ -146,9 +146,17 @@ public class MustacheTest
} }
@Test public void testSectionWithFalseyEmptyString () { @Test public void testSectionWithFalseyEmptyString () {
test(Mustache.compiler().emptyStringIsFalse(true), "", "{{#foo}}test{{/foo}}", new Object() { Object ctx = new Object() {
String foo = ""; String foo = "";
}); String bar = "nonempty";
};
// test normal sections with falsey empty string
Mustache.Compiler compiler = Mustache.compiler().emptyStringIsFalse(true);
test(compiler, "", "{{#foo}}test{{/foo}}", ctx);
test(compiler, "test", "{{#bar}}test{{/bar}}", ctx);
// test inverted sections with falsey empty string
test(compiler, "test", "{{^foo}}test{{/foo}}", ctx);
test(compiler, "", "{{^bar}}test{{/bar}}", ctx);
} }
@Test public void testSectionWithNonFalseyZero () { @Test public void testSectionWithNonFalseyZero () {