From 88b053b2fe30cdd509c267d4584ebff90a1b46c4 Mon Sep 17 00:00:00 2001 From: spullara Date: Mon, 1 Feb 2021 14:07:06 -0800 Subject: [PATCH] ISSUE #257: initial stab at dynamic partials and how they can work today by extending via the current extension points --- .../github/mustachejava/DynamicPartials.java | 129 ++++++++++++++++++ .../src/test/resources/dynamicbase.mustache | 3 + .../src/test/resources/dynamicbase2.mustache | 1 + .../src/test/resources/dynamicimage.mustache | 1 + .../src/test/resources/dynamictext.mustache | 1 + 5 files changed, 135 insertions(+) create mode 100644 compiler/src/test/java/com/github/mustachejava/DynamicPartials.java create mode 100644 compiler/src/test/resources/dynamicbase.mustache create mode 100644 compiler/src/test/resources/dynamicbase2.mustache create mode 100644 compiler/src/test/resources/dynamicimage.mustache create mode 100644 compiler/src/test/resources/dynamictext.mustache diff --git a/compiler/src/test/java/com/github/mustachejava/DynamicPartials.java b/compiler/src/test/java/com/github/mustachejava/DynamicPartials.java new file mode 100644 index 000000000..57445cabf --- /dev/null +++ b/compiler/src/test/java/com/github/mustachejava/DynamicPartials.java @@ -0,0 +1,129 @@ +package com.github.mustachejava; + +import com.github.mustachejava.codes.PartialCode; +import com.github.mustachejava.util.Wrapper; +import org.junit.Test; + +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class DynamicPartials { + + private static final Object view = new Object() { + final Object[] items = new Object[]{ + new Object() { + final String partial = "dynamictext"; + final String content = "Some text"; + }, + new Object() { + final String partial = "dynamicimage"; + final String url = "https://www.example.org"; + } + }; + }; + + @Test + public void testDynamicPartial() { + DefaultMustacheFactory mf = new DefaultMustacheFactory() { + @Override + public MustacheVisitor createMustacheVisitor() { + return new DefaultMustacheVisitor(this) { + + private final Map partials = new HashMap<>(); + + @Override + public void value(TemplateContext tc, String variable, boolean encoded) { + if (variable.startsWith("@")) { + // This is actually a dynamic partial + String file = tc.file(); + int dotindex = file.lastIndexOf("."); + String extension = dotindex == -1 ? "" : file.substring(dotindex); + Mustache dynamicpartial = compile(new StringReader("{{>.}}\n"), "dynamicpartial" + extension); + iterable(tc, variable.substring(1), dynamicpartial); + return; + } + super.value(tc, variable, encoded); + } + + @Override + public void partial(TemplateContext tc, String variable) { + // Implements {{>.}} which dynamically gets the partial name from the partial field + if (variable.equals(".")) { + dynamicPartial(tc, variable); + } else { + super.partial(tc, variable); + } + } + + private void dynamicPartial(TemplateContext tc, String variable) { + TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); + list.add(new PartialCode(partialTC, df, variable) { + @Override + public void setCodes(Code[] newcodes) { + } + + @Override + public Writer execute(Writer writer, List scopes) { + // Need to figure out which partial to compile and execute + Wrapper partialWrapper = getObjectHandler().find("partial", scopes); + Object call = partialWrapper.call(scopes); + if (call == null) { + throw new MustacheException("No partial field found in scope", tc); + } + Mustache partial; + synchronized (this) { + String name = call.toString(); + partial = partials.get(name); + if (partial == null) { + partial = compilePartial(df.resolvePartialPath(dir, name, extension)); + partials.put(name, partial); + } + } + Writer execute = partial.execute(writer, scopes); + return appendText(execute); + } + + @Override + public synchronized void init() { + synchronized (this) { + filterText(); + } + } + + @Override + protected String partialName() { + throw new MustacheException("Unknown at compile time"); + } + + @Override + public Code[] getCodes() { + // We don't know the codes at compile time. We will not support recursion for dynamic partials. + return new Code[]{}; + } + }); + } + }; + } + }; + { + Mustache mustache = mf.compile("dynamicbase.mustache"); + StringWriter sw = new StringWriter(); + mustache.execute(sw, view); + assertEquals("

Some text

\n" + + "

\n", sw.toString()); + } + { + Mustache mustache = mf.compile("dynamicbase2.mustache"); + StringWriter sw = new StringWriter(); + mustache.execute(sw, view); + assertEquals("

Some text

\n" + + "

\n", sw.toString()); + } + } +} diff --git a/compiler/src/test/resources/dynamicbase.mustache b/compiler/src/test/resources/dynamicbase.mustache new file mode 100644 index 000000000..30ffa5f77 --- /dev/null +++ b/compiler/src/test/resources/dynamicbase.mustache @@ -0,0 +1,3 @@ +{{#items}} +{{>.}} +{{/items}} \ No newline at end of file diff --git a/compiler/src/test/resources/dynamicbase2.mustache b/compiler/src/test/resources/dynamicbase2.mustache new file mode 100644 index 000000000..33132ad96 --- /dev/null +++ b/compiler/src/test/resources/dynamicbase2.mustache @@ -0,0 +1 @@ +{{@items}} \ No newline at end of file diff --git a/compiler/src/test/resources/dynamicimage.mustache b/compiler/src/test/resources/dynamicimage.mustache new file mode 100644 index 000000000..dee102f3f --- /dev/null +++ b/compiler/src/test/resources/dynamicimage.mustache @@ -0,0 +1 @@ +

\ No newline at end of file diff --git a/compiler/src/test/resources/dynamictext.mustache b/compiler/src/test/resources/dynamictext.mustache new file mode 100644 index 000000000..a50d81aa7 --- /dev/null +++ b/compiler/src/test/resources/dynamictext.mustache @@ -0,0 +1 @@ +

{{content}}

\ No newline at end of file