Added Compiler.nullValue() for situations where one wants to provide a value
for use when variables resolve to null, but still wishes for exceptions to be thrown when values are not resolvable (i.e. are typos). Pre-existing behavior is preserved if one uses defaultValue() or does not change the default configuration. Nota bene: there are complexities, so be sure to read the documentation.
This commit is contained in:
@@ -105,6 +105,7 @@ Sections behave as you would expect:
|
|||||||
|
|
||||||
* `Boolean` values enable or disable the section.
|
* `Boolean` values enable or disable the section.
|
||||||
* Array, `Iterator`, or `Iterable` values repeatedly execute the section with each element used as the context for each iteration. Empty collections result in zero instances of the section being included in the template.
|
* Array, `Iterator`, or `Iterable` values repeatedly execute the section with each element used as the context for each iteration. Empty collections result in zero instances of the section being included in the template.
|
||||||
|
* An unresolvable or null value is treated as false (by default, see //Default Values// for more details).
|
||||||
* Any other object results in a single execution of the section with that object as a context.
|
* Any other object results in a single execution of the section with that object as a context.
|
||||||
|
|
||||||
See the code in
|
See the code in
|
||||||
@@ -134,9 +135,9 @@ the template.
|
|||||||
Default Values
|
Default Values
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
By default, an exception will be thrown any time a variable cannot be found, or
|
By default, an exception will be thrown any time a variable cannot be resolved,
|
||||||
resolves to null. A value to use in such circumstances can be provided when
|
or resolves to null. You can change this behavior in two ways. If you want to
|
||||||
creating your compiler:
|
provide a value for use in all such circumstances, use `defaultValue()`:
|
||||||
|
|
||||||
String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?";
|
String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?";
|
||||||
Mustache.compiler().defaultValue("what").compile(tmpl).execute(new Object() {
|
Mustache.compiler().defaultValue("what").compile(tmpl).execute(new Object() {
|
||||||
@@ -147,6 +148,33 @@ creating your compiler:
|
|||||||
// result:
|
// result:
|
||||||
Say what what?
|
Say what what?
|
||||||
|
|
||||||
|
If you only wish to provide a default value for variables that resolve to null,
|
||||||
|
and wish to preserve exceptions in cases where variables cannot be resolved,
|
||||||
|
use `nullValue()`:
|
||||||
|
|
||||||
|
String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?";
|
||||||
|
Mustache.compiler().nullValue("what").compile(tmpl).execute(new Object() {
|
||||||
|
String exists = "Say";
|
||||||
|
String nullValued = null;
|
||||||
|
// String doesNotExist
|
||||||
|
});
|
||||||
|
// throws MustacheException when executing the template because
|
||||||
|
// doesNotExist cannot be resolved
|
||||||
|
|
||||||
|
Note that any variable resolved against a `Map` context will be resolvable, but
|
||||||
|
will be treated as having the value null if the map contains no mapping for the
|
||||||
|
variable. Only variables resolved against Java object fields or methods risk
|
||||||
|
being unresolvable.
|
||||||
|
|
||||||
|
Note that section behavior deviates from the above specification (for
|
||||||
|
historical reasons and because it's kind of useful). By default, a section that
|
||||||
|
is not resolvable or resolves to null will be omitted (and conversely, an
|
||||||
|
inverse section that is not resolvable or resolves to null will be included).
|
||||||
|
If you use `defaultValue()`, this behavior is preserved. If you use
|
||||||
|
`nullValue()`, sections that refer to an unresolvable variable will now throw
|
||||||
|
an exception (sections that refer to a resolvable, but null-valued variable,
|
||||||
|
will behave as before).
|
||||||
|
|
||||||
Extensions
|
Extensions
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,15 @@ public class Mustache
|
|||||||
/** Whether or not standards mode is enabled. */
|
/** Whether or not standards mode is enabled. */
|
||||||
public final boolean standardsMode;
|
public final boolean standardsMode;
|
||||||
|
|
||||||
/** A value to use when a variable cannot be resolved, or resolves to null. If the default
|
/** A value to use when a variable resolves to null. If this value is null (which is the
|
||||||
* value is null (which is the default default value), an exception will be thrown. */
|
* default null value), an exception will be thrown. If {@link #missingIsNull} is also
|
||||||
public final String defaultValue;
|
* true, this value will be used when a variable cannot be resolved. */
|
||||||
|
public final String nullValue;
|
||||||
|
|
||||||
|
/** If this value is true, missing variables will be treated like variables that return
|
||||||
|
* null. {@link #nullValue} will be used in their place, assuming {@link #nullValue} is
|
||||||
|
* configured to a non-null value. */
|
||||||
|
public final boolean missingIsNull;
|
||||||
|
|
||||||
/** The template loader in use during this compilation. */
|
/** The template loader in use during this compilation. */
|
||||||
public final TemplateLoader loader;
|
public final TemplateLoader loader;
|
||||||
@@ -57,32 +63,59 @@ public class Mustache
|
|||||||
|
|
||||||
/** Returns a compiler that either does or does not escape HTML by default. */
|
/** Returns a compiler that either does or does not escape HTML by default. */
|
||||||
public Compiler escapeHTML (boolean escapeHTML) {
|
public Compiler escapeHTML (boolean escapeHTML) {
|
||||||
return new Compiler(escapeHTML, this.standardsMode, this.defaultValue, this.loader);
|
return new Compiler(escapeHTML, this.standardsMode,
|
||||||
|
this.nullValue, this.missingIsNull, this.loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that either does or does not use standards mode. Standards mode
|
/** Returns a compiler that either does or does not use standards mode. Standards mode
|
||||||
* disables the non-standard JMustache extensions like looking up missing names in a parent
|
* disables the non-standard JMustache extensions like looking up missing names in a parent
|
||||||
* context. */
|
* context. */
|
||||||
public Compiler standardsMode (boolean standardsMode) {
|
public Compiler standardsMode (boolean standardsMode) {
|
||||||
return new Compiler(this.escapeHTML, standardsMode, this.defaultValue, this.loader);
|
return new Compiler(this.escapeHTML, standardsMode,
|
||||||
|
this.nullValue, this.missingIsNull, this.loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that will use the given value for any variable that is missing, or
|
/** Returns a compiler that will use the given value for any variable that is missing, or
|
||||||
* otherwise resolves to null. */
|
* otherwise resolves to null. This is like {@link #nullValue} except that it returns the
|
||||||
|
* supplied default for missing keys and existing keys that return null values. Note that
|
||||||
|
* regardless of whether a null or default value is configured, if the resolution of part
|
||||||
|
* of a compound key results in a missing or null value, an exception will be raised. */
|
||||||
public Compiler defaultValue (String defaultValue) {
|
public Compiler defaultValue (String defaultValue) {
|
||||||
return new Compiler(this.escapeHTML, this.standardsMode, defaultValue, this.loader);
|
return new Compiler(this.escapeHTML, this.standardsMode,
|
||||||
|
defaultValue, true, this.loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a compiler that will use the given value for any variable that resolves to
|
||||||
|
* null, but will still raise an exception for variables for which an accessor cannot be
|
||||||
|
* found. This is like {@link #defaultValue} except that it differentiates between missing
|
||||||
|
* accessors, and accessors that exist but return null.
|
||||||
|
* <ul>
|
||||||
|
* <li>In the case of a Java object being used as a context, if no field or method can be
|
||||||
|
* found for a variable, an exception will be raised.</li>
|
||||||
|
* <li>In the case of a {@link Map} being used as a context, all possible accessors are
|
||||||
|
* assumed to exist (but potentially return null), and no exception will ever be
|
||||||
|
* raised.</li>
|
||||||
|
* </ul>
|
||||||
|
* Note that regardless of whether a null or default value is configured, if the resolution
|
||||||
|
* of part of a compound key results in a missing or null value, an exception will be
|
||||||
|
* raised. */
|
||||||
|
public Compiler nullValue (String nullValue) {
|
||||||
|
return new Compiler(this.escapeHTML, this.standardsMode,
|
||||||
|
nullValue, false, this.loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler configured to use the supplied template loader to handle partials. */
|
/** Returns a compiler configured to use the supplied template loader to handle partials. */
|
||||||
public Compiler withLoader (TemplateLoader loader) {
|
public Compiler withLoader (TemplateLoader loader) {
|
||||||
return new Compiler(this.escapeHTML, this.standardsMode, this.defaultValue, loader);
|
return new Compiler(this.escapeHTML, this.standardsMode,
|
||||||
|
this.nullValue, this.missingIsNull, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Compiler (boolean escapeHTML, boolean standardsMode, String defaultValue,
|
protected Compiler (boolean escapeHTML, boolean standardsMode,
|
||||||
TemplateLoader loader) {
|
String nullValue, boolean missingIsNull, TemplateLoader loader) {
|
||||||
this.escapeHTML = escapeHTML;
|
this.escapeHTML = escapeHTML;
|
||||||
this.standardsMode = standardsMode;
|
this.standardsMode = standardsMode;
|
||||||
this.defaultValue = defaultValue;
|
this.nullValue = nullValue;
|
||||||
|
this.missingIsNull = missingIsNull;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +133,7 @@ public class Mustache
|
|||||||
*/
|
*/
|
||||||
public static Compiler compiler ()
|
public static Compiler compiler ()
|
||||||
{
|
{
|
||||||
return new Compiler(true, false, null, FAILING_LOADER);
|
return new Compiler(true, false, null, true, FAILING_LOADER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -513,10 +546,7 @@ public class Mustache
|
|||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
}
|
}
|
||||||
@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.getValue(ctx, _name, _line);
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
if (value == null) {
|
|
||||||
return; // TODO: configurable behavior on missing values
|
|
||||||
}
|
|
||||||
if (value instanceof Iterable<?>) {
|
if (value instanceof Iterable<?>) {
|
||||||
value = ((Iterable<?>)value).iterator();
|
value = ((Iterable<?>)value).iterator();
|
||||||
}
|
}
|
||||||
@@ -551,10 +581,8 @@ public class Mustache
|
|||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
}
|
}
|
||||||
@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.getValue(ctx, _name, _line);
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
if (value == null) {
|
if (value instanceof Iterable<?>) {
|
||||||
executeSegs(tmpl, ctx, out); // TODO: configurable behavior on missing values
|
|
||||||
} else if (value instanceof Iterable<?>) {
|
|
||||||
Iterable<?> iable = (Iterable<?>)value;
|
Iterable<?> iable = (Iterable<?>)value;
|
||||||
if (!iable.iterator().hasNext()) {
|
if (!iable.iterator().hasNext()) {
|
||||||
executeSegs(tmpl, ctx, out);
|
executeSegs(tmpl, ctx, out);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.io.StringWriter;
|
|||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -76,10 +77,12 @@ public class Template
|
|||||||
*
|
*
|
||||||
* @param ctx the context in which to look up the variable.
|
* @param ctx the context in which to look up the variable.
|
||||||
* @param name the name of the variable to be resolved, which must be an interned string.
|
* @param name the name of the variable to be resolved, which must be an interned string.
|
||||||
|
* @param missingIsNull whether to fail if a variable cannot be resolved, or to return null in
|
||||||
|
* that case.
|
||||||
*
|
*
|
||||||
* @return the value associated with the supplied name or null if no value could be resolved.
|
* @return the value associated with the supplied name or null if no value could be resolved.
|
||||||
*/
|
*/
|
||||||
protected Object getValue (Context ctx, String name, int line)
|
protected Object getValue (Context ctx, String name, int line, boolean missingIsNull)
|
||||||
{
|
{
|
||||||
if (!_compiler.standardsMode) {
|
if (!_compiler.standardsMode) {
|
||||||
// if we're dealing with a compound key, resolve each component and use the result to
|
// if we're dealing with a compound key, resolve each component and use the result to
|
||||||
@@ -89,10 +92,13 @@ public class Template
|
|||||||
// we want to allow the first component of a compound key to be located in a parent
|
// we want to allow the first component of a compound key to be located in a parent
|
||||||
// context, but once we're selecting sub-components, they must only be resolved in the
|
// context, but once we're selecting sub-components, they must only be resolved in the
|
||||||
// object that represents that component
|
// object that represents that component
|
||||||
Object data = getValue(ctx, comps[0].intern(), line);
|
Object data = getValue(ctx, comps[0].intern(), line, missingIsNull);
|
||||||
for (int ii = 1; ii < comps.length; ii++) {
|
for (int ii = 1; ii < comps.length; ii++) {
|
||||||
// generate more helpful error message
|
if (data == NO_FETCHER_FOUND) {
|
||||||
if (data == null) {
|
throw new NullPointerException(
|
||||||
|
"Missing context for compound variable '" + name + "' on line " + line +
|
||||||
|
". '" + comps[ii - 1] + "' was not found.");
|
||||||
|
} else if (data == null) {
|
||||||
throw new NullPointerException(
|
throw new NullPointerException(
|
||||||
"Null context for compound variable '" + name + "' on line " + line +
|
"Null context for compound variable '" + name + "' on line " + line +
|
||||||
". '" + comps[ii - 1] + "' resolved to null.");
|
". '" + comps[ii - 1] + "' resolved to null.");
|
||||||
@@ -101,7 +107,7 @@ public class Template
|
|||||||
// contexts; that would be weird and confusing
|
// contexts; that would be weird and confusing
|
||||||
data = getValueIn(data, comps[ii].intern(), line);
|
data = getValueIn(data, comps[ii].intern(), line);
|
||||||
}
|
}
|
||||||
return data;
|
return checkForMissing(name, line, missingIsNull, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,18 +122,40 @@ public class Template
|
|||||||
|
|
||||||
// if we're in standards mode, we don't search our parent contexts
|
// if we're in standards mode, we don't search our parent contexts
|
||||||
if (_compiler.standardsMode) {
|
if (_compiler.standardsMode) {
|
||||||
return getValueIn(ctx.data, name, line);
|
return checkForMissing(name, line, missingIsNull, getValueIn(ctx.data, name, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean variableMissing = true;
|
||||||
while (ctx != null) {
|
while (ctx != null) {
|
||||||
Object value = getValueIn(ctx.data, name, line);
|
Object value = getValueIn(ctx.data, name, line);
|
||||||
if (value != null) {
|
if (value == NO_FETCHER_FOUND) {
|
||||||
|
// preserve variableMissing
|
||||||
|
} else if (value == null) {
|
||||||
|
// we found a fetcher, and it returned null; so we keep searching our parents, but
|
||||||
|
// we won't freak out about a missing variable if we have a nullValue configured
|
||||||
|
variableMissing = false;
|
||||||
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
ctx = ctx.parent;
|
ctx = ctx.parent;
|
||||||
}
|
}
|
||||||
// we've popped off the top of our stack of contexts, so return null
|
// we've popped off the top of our stack of contexts, if we never actually found a fetcher
|
||||||
return null;
|
// for our variable, we need to let checkForMissing() know
|
||||||
|
return checkForMissing(name, line, missingIsNull, variableMissing ? NO_FETCHER_FOUND : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the specified variable, noting that it is intended to be used as the
|
||||||
|
* contents for a segment. Presently this does not do anything special, but eventually this
|
||||||
|
* will be the means by which we enact configured behavior for sections that reference null or
|
||||||
|
* missing variables. Right now, all such variables result in a length 0 section.
|
||||||
|
*/
|
||||||
|
protected Object getSectionValue (Context ctx, String name, int line)
|
||||||
|
{
|
||||||
|
// TODO: configurable behavior on missing values
|
||||||
|
Object value = getValue(ctx, name, line, _compiler.missingIsNull);
|
||||||
|
// TODO: configurable behavior on null values
|
||||||
|
return (value == null) ? Collections.emptyList() : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,8 +164,11 @@ public class Template
|
|||||||
*/
|
*/
|
||||||
protected Object getValueOrDefault (Context ctx, String name, int line)
|
protected Object getValueOrDefault (Context ctx, String name, int line)
|
||||||
{
|
{
|
||||||
Object value = getValue(ctx, name, line);
|
Object value = getValue(ctx, name, line, _compiler.missingIsNull);
|
||||||
return (value == null) ? _compiler.defaultValue : value;
|
// getValue will raise MustacheException if a variable cannot be resolved and missingIsNull
|
||||||
|
// is not configured; so we're safe to assume that any null that makes it up to this point
|
||||||
|
// can be converted to nullValue
|
||||||
|
return (value == null) ? _compiler.nullValue : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object getValueIn (Object data, String name, int line)
|
protected Object getValueIn (Object data, String name, int line)
|
||||||
@@ -163,7 +194,7 @@ public class Template
|
|||||||
// if we were unable to create a fetcher, just return null and our caller can either try
|
// if we were unable to create a fetcher, just return null and our caller can either try
|
||||||
// the parent context, or do le freak out
|
// the parent context, or do le freak out
|
||||||
if (fetcher == null) {
|
if (fetcher == null) {
|
||||||
return null;
|
return NO_FETCHER_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -176,6 +207,20 @@ public class Template
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Object checkForMissing (String name, int line, boolean missingIsNull, Object value)
|
||||||
|
{
|
||||||
|
if (value == NO_FETCHER_FOUND) {
|
||||||
|
if (missingIsNull) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new MustacheException(
|
||||||
|
"No method or field with name '" + name + "' on line " + line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected final Segment[] _segs;
|
protected final Segment[] _segs;
|
||||||
protected final Mustache.Compiler _compiler;
|
protected final Mustache.Compiler _compiler;
|
||||||
protected final Map<Key, VariableFetcher> _fcache =
|
protected final Map<Key, VariableFetcher> _fcache =
|
||||||
@@ -339,6 +384,8 @@ public class Template
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected static final Object NO_FETCHER_FOUND = new Object();
|
||||||
|
|
||||||
protected static final String DOT_NAME = ".".intern();
|
protected static final String DOT_NAME = ".".intern();
|
||||||
protected static final String THIS_NAME = "this".intern();
|
protected static final String THIS_NAME = "this".intern();
|
||||||
protected static final String FIRST_NAME = "-first".intern();
|
protected static final String FIRST_NAME = "-first".intern();
|
||||||
|
|||||||
@@ -113,6 +113,43 @@ public class MustacheTest
|
|||||||
"d", new Object[] { context("e", "3"), context("e", "4") })));
|
"d", new Object[] { context("e", "3"), context("e", "4") })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void testNullSection () {
|
||||||
|
test("", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
Object foo = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testNullSectionWithDefaultValue () {
|
||||||
|
test(Mustache.compiler().defaultValue(""), "", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
Object foo = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testNullSectionWithNullValue () {
|
||||||
|
test(Mustache.compiler().nullValue(""), "", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
Object foo = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testMissingSection () {
|
||||||
|
test("", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
// no foo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testMissingSectionWithDefaultValue () {
|
||||||
|
test(Mustache.compiler().defaultValue(""), "", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
// no foo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=MustacheException.class)
|
||||||
|
public void testMissingSectionWithNullValue () {
|
||||||
|
test(Mustache.compiler().nullValue(""), "", "{{#foo}}{{bar}}{{/foo}}", new Object() {
|
||||||
|
// no foo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void testComment () {
|
@Test public void testComment () {
|
||||||
test("foobar", "foo{{! nothing to see here}}bar", new Object());
|
test("foobar", "foo{{! nothing to see here}}bar", new Object());
|
||||||
}
|
}
|
||||||
@@ -215,6 +252,28 @@ public class MustacheTest
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=NullPointerException.class)
|
||||||
|
public void testNullComponentInCompoundVariable () {
|
||||||
|
// make sure that even if we have a null value configured, we freak out
|
||||||
|
test(Mustache.compiler().nullValue("foo"), "unused", "{{foo.bar.baz}}", new Object() {
|
||||||
|
Object foo () {
|
||||||
|
return new Object() {
|
||||||
|
Object bar = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=NullPointerException.class)
|
||||||
|
public void testMissingComponentInCompoundVariable () {
|
||||||
|
// make sure that even if we have a default value configured, we freak out
|
||||||
|
test(Mustache.compiler().defaultValue("foo"), "unused", "{{foo.bar.baz}}", new Object() {
|
||||||
|
Object foo () {
|
||||||
|
return new Object(); // no bar
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void testNewlineSkipping () {
|
@Test public void testNewlineSkipping () {
|
||||||
String tmpl = "list:\n" +
|
String tmpl = "list:\n" +
|
||||||
"{{#items}}\n" +
|
"{{#items}}\n" +
|
||||||
@@ -322,6 +381,23 @@ public class MustacheTest
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=MustacheException.class)
|
||||||
|
public void testMissingValueWithNullDefault () {
|
||||||
|
test(Mustache.compiler().nullValue(""),
|
||||||
|
"bar", "{{missing}}{{notmissing}}", new Object() {
|
||||||
|
String notmissing = "bar";
|
||||||
|
// no field or method for 'missing'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testNullValueGetsNullDefault () {
|
||||||
|
test(Mustache.compiler().nullValue("foo"),
|
||||||
|
"foobar", "{{nullvar}}{{nonnullvar}}", new Object() {
|
||||||
|
String nonnullvar = "bar";
|
||||||
|
String nullvar = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected void test (Mustache.Compiler compiler, String expected, String template, Object ctx)
|
protected void test (Mustache.Compiler compiler, String expected, String template, Object ctx)
|
||||||
{
|
{
|
||||||
assertEquals(expected, compiler.compile(template).execute(ctx));
|
assertEquals(expected, compiler.compile(template).execute(ctx));
|
||||||
|
|||||||
Reference in New Issue
Block a user