diff --git a/README.md b/README.md index aec4072..649b4d8 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,48 @@ Note that if a variable _is_ defined in an inner context, it shadows the same name in the outer context. There is presently no way to access the variable from the outer context. +Invertable Lambdas +------------------ + +For some applications, it may be useful for lambdas to be executed for an +inverse section rather than having the section omitted altogether. This allows +for proper conditional substitution when statically translating templates into +other languages or contexts: + + String template = "{{#condition}}result if true{{/condition}}\n{{^condition}}result if false{{/condition}}"; + Mustache.compiler().compile(template).execute(new Object() { + Mustache.InvertableLambda condition = new Mustache.InvertableLambda() { + @Override + public void execute(Template.Fragment frag, Writer out) + throws IOException { + // this method is executed when the lambda is referenced in a normal section + out.write("if (condition) {console.log(\""); + out.write(toJavaScriptLiteral(frag.execute())); + out.write("\")}"); + } + + @Override + public void executeInverse(Template.Fragment frag, Writer out) + throws IOException { + // this method is executed when the lambda is referenced in an inverse section + out.write("if (!condition) {console.log(\""); + out.write(toJavaScriptLiteral(frag.execute())); + out.write("\")}"); + } + + private String toJavaScriptLiteral(String execute) { + // note: this is NOT a complete implementation of JavaScript string literal escaping + return execute.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\""); + } + }; + }); + // results: + // if (condition) {console.log("result if true")} + // if (!condition) {console.log("result if false")} + +Of course, you are not limited strictly to conditional substitution -- you can use an +InvertableLambda whenever you need a single function with two modes of operation. + Standards Mode -------------- diff --git a/src/main/java/com/samskivert/mustache/Mustache.java b/src/main/java/com/samskivert/mustache/Mustache.java index b69f946..8f7337b 100644 --- a/src/main/java/com/samskivert/mustache/Mustache.java +++ b/src/main/java/com/samskivert/mustache/Mustache.java @@ -224,6 +224,17 @@ public class Mustache void execute (Template.Fragment frag, Writer out) throws IOException; } + public interface InvertableLambda extends Lambda + { + /** Executes this lambda on the supplied template fragment, when the lambda is used in an + * inverse section. The lambda should write its results to {@code out}. + * + * @param frag the fragment of the template that was passed to the lambda. + * @param out the writer to which the lambda should write its output. + */ + void executeInverse (Template.Fragment frag, Writer out) throws IOException; + } + /** Reads variables from context objects. */ public interface VariableFetcher { @@ -773,6 +784,12 @@ public class Mustache if (!(Boolean)value) { executeSegs(tmpl, ctx, out); } + } else if (value instanceof InvertableLambda) { + try { + ((InvertableLambda)value).executeInverse(tmpl.createFragment(_segs, ctx), out); + } catch (IOException ioe) { + throw new MustacheException(ioe); + } } else if (_comp.isFalsey(value)) { executeSegs(tmpl, ctx, out); } // TODO: fail? diff --git a/src/test/java/com/samskivert/mustache/MustacheTest.java b/src/test/java/com/samskivert/mustache/MustacheTest.java index 2179c52..c52828b 100644 --- a/src/test/java/com/samskivert/mustache/MustacheTest.java +++ b/src/test/java/com/samskivert/mustache/MustacheTest.java @@ -629,6 +629,32 @@ public class MustacheTest })); } + @Test public void testInvertableLambda () { + test("positive = positive, negative = negative, simple lambdas do still work", + "{{#invertable}}positive{{/invertable}}, {{^invertable}}negative{{/invertable}}, simple lambdas do {{^simple}}NOT {{/simple}}still work", + context("invertable", new Mustache.InvertableLambda() { + @Override + public void execute(Template.Fragment frag, Writer out) + throws IOException { + out.write("positive = "); + frag.execute(out); + } + + @Override + public void executeInverse(Template.Fragment frag, Writer out) + throws IOException { + out.write("negative = "); + frag.execute(out); + } + }, "simple", new Mustache.Lambda() { + @Override + public void execute(Template.Fragment frag, Writer out) + throws IOException { + frag.execute(out); + } + })); + } + @Test public void testNonStandardDefaultDelims () { test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", new Object() { String foo = "bar";