From acbddd07a2850fda4dc93cfa3866eb78d36f7d59 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 15 Feb 2013 16:38:36 -0800 Subject: [PATCH] Added basic lambda support. --- README.md | 41 +++++++++++++++++++ .../com/samskivert/mustache/Mustache.java | 20 ++++++++- .../com/samskivert/mustache/Template.java | 29 ++++++++++++- .../com/samskivert/mustache/MustacheTest.java | 28 +++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 248fe0b..6d0e20d 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,47 @@ If you wish to make use of partials (e.g. `{{>subtmpl}}`) you must provide a The above snippet will load `new File(templateDir, "subtmpl")` when compiling the template. +Lambdas +------- + +JMustache implements lambdas by passing you a `Template.Fragment` instance +which you can use to execute the fragment of the template that was passed to +the lambda. You can decorate the results of the fragment execution, as shown in +the standard Mustache documentation on lambdas: + + String tmpl = "{{#bold}}{{name}} is awesome.{{/bold}}"; + Mustache.compiler().compile(tmpl).execute(new Object() { + Mustache.Lambda bold = new Mustache.Lambda() { + public void execute (Template.Fragment frag, Writer out) throws IOException { + out.write(""); + frag.execute(out); + out.write(""); + } + }; + }); + // result: + Willy is awsome. + +You can also obtain the results of the fragment execution to do things like +internationalization or caching: + + Object ctx = new Object() { + Mustache.Lambda i18n = new Mustache.Lambda() { + public void execute (Template.Fragment frag, Writer out) throws IOException { + String key = frag.execute(); + String text = // look up key in i18n system + out.write(text); + } + }; + }; + // template might look something like: +

{{#i18n}}title{{/i18n}

+ {{#i18n}}welcome_msg{{/i18n}} + +Currently there is no support for "unexecuting" the template and obtaining the +original Mustache template text contained in the section. File a feature +request with a sane use case if you have one. + Default Values -------------- diff --git a/src/main/java/com/samskivert/mustache/Mustache.java b/src/main/java/com/samskivert/mustache/Mustache.java index e95e237..37e5cc6 100644 --- a/src/main/java/com/samskivert/mustache/Mustache.java +++ b/src/main/java/com/samskivert/mustache/Mustache.java @@ -150,7 +150,19 @@ public class Mustache { /** Returns a reader for the template with the supplied name. * @throws Exception if the template could not be loaded for any reason. */ - public Reader getTemplate (String name) throws Exception; + Reader getTemplate (String name) throws Exception; + } + + /** Used to handle lambdas. */ + public interface Lambda + { + /** Executes this lambda on the supplied template fragment. The lambda should write its + * results to {@code out}. + * + * @param tmpl the fragment of the template that was passed to the lambda. + * @param out the writer to which the lambda should write its output. + */ + void execute (Template.Fragment frag, Writer out) throws IOException; } /** Used to read variables from values. */ @@ -640,6 +652,12 @@ public class Mustache if ((Boolean)value) { executeSegs(tmpl, ctx, out); } + } else if (value instanceof Lambda) { + try { + ((Lambda)value).execute(tmpl.createFragment(_segs, ctx), out); + } catch (IOException ioe) { + throw new MustacheException(ioe); + } } else if (_compiler.emptyStringIsFalse && "".equals(value)) { // omit the section } else { diff --git a/src/main/java/com/samskivert/mustache/Template.java b/src/main/java/com/samskivert/mustache/Template.java index 445c42d..86123ff 100644 --- a/src/main/java/com/samskivert/mustache/Template.java +++ b/src/main/java/com/samskivert/mustache/Template.java @@ -36,6 +36,22 @@ import java.util.Map; */ public class Template { + /** + * Encapsulates a fragment of a template that is passed to a lambda. The fragment is bound to + * the variable context that was in effect at the time the lambda was called. + */ + public abstract class Fragment { + /** Executes this template fragment, writing its result to {@code out}. */ + public abstract void execute (Writer out); + + /** Executes this template fragment and returns is result as a string. */ + public String execute () { + StringWriter out = new StringWriter(); + execute(out); + return out.toString(); + } + } + /** * Executes this template with the given context, returning the results as a string. * @throws MustacheException if an error occurs while executing or writing the template. @@ -73,7 +89,7 @@ public class Template { _segs = segs; _compiler = compiler; - _fcache = _compiler.collector.createFetcherCache(); + _fcache = compiler.collector.createFetcherCache(); } protected void executeSegs (Context ctx, Writer out) throws MustacheException @@ -83,6 +99,17 @@ public class Template } } + protected Fragment createFragment (final Segment[] segs, final Context ctx) + { + return new Fragment() { + @Override public void execute (Writer out) { + for (Segment seg : segs) { + seg.execute(Template.this, ctx, out); + } + } + }; + } + /** * Called by executing segments to obtain the value of the specified variable in the supplied * context. diff --git a/src/test/java/com/samskivert/mustache/MustacheTest.java b/src/test/java/com/samskivert/mustache/MustacheTest.java index d032a97..ab5c1e5 100644 --- a/src/test/java/com/samskivert/mustache/MustacheTest.java +++ b/src/test/java/com/samskivert/mustache/MustacheTest.java @@ -8,8 +8,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.io.Writer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -484,6 +486,32 @@ public class MustacheTest }); } + @Test public void testLambda1 () { + test("Willy is awesome.", "{{#bold}}{{name}} is awesome.{{/bold}}", + context("name", "Willy", "bold", new Mustache.Lambda() { + public void execute (Template.Fragment frag, Writer out) throws IOException { + out.write(""); + frag.execute(out); + out.write(""); + } + })); + } + + @Test public void testLambda2 () { + test("Slug bug potato!", "{{#l}}1{{/l}} {{#l}}2{{/l}} {{#l}}{{three}}{{/l}}", + context("three", "3", "l", new Mustache.Lambda() { + public void execute (Template.Fragment frag, Writer out) throws IOException { + out.write(lookup(frag.execute())); + } + protected String lookup (String contents) { + if (contents.equals("1")) return "Slug"; + else if (contents.equals("2")) return "bug"; + else if (contents.equals("3")) return "potato!"; + else return "Who was that man?"; + } + })); + } + protected void test (Mustache.Compiler compiler, String expected, String template, Object ctx) { assertEquals(expected, compiler.compile(template).execute(ctx));