diff --git a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java index 1f298d70..1445034d 100644 --- a/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/DefaultMustacheVisitor.java @@ -70,6 +70,12 @@ public void partial(TemplateContext tc, final String variable, String indent) { list.add(new PartialCode(partialTC, df, variable)); } + @Override + public void dynamicPartial(TemplateContext tc, final String variable, String indent) { + TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); + list.add(new DynamicPartialCode(partialTC, df, variable)); + } + @Override public void value(TemplateContext tc, final String variable, boolean encoded) { list.add(new ValueCode(tc, df, variable, encoded)); diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java index f8f4a378..02ddae15 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheParser.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheParser.java @@ -199,8 +199,12 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger String indent = (onlywhitespace && startOfLine) ? out.toString() : ""; out = write(mv, out, file, currentLine.intValue(), startOfLine); startOfLine = startOfLine & onlywhitespace; - mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); + if (variable.trim().startsWith("*")) { + mv.dynamicPartial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable.replaceAll(" ", ""), indent); + } else { + mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent); + } // a new line following a partial is dropped if (specConformWhitespace && startOfLine) { br.mark(2); diff --git a/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java b/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java index 5866e569..56756869 100644 --- a/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java +++ b/compiler/src/main/java/com/github/mustachejava/MustacheVisitor.java @@ -14,6 +14,8 @@ public interface MustacheVisitor { void partial(TemplateContext templateContext, String variable, String indent); + void dynamicPartial(TemplateContext templateContext, String variable, String indent); + void value(TemplateContext templateContext, String variable, boolean encoded); void write(TemplateContext templateContext, String text); diff --git a/compiler/src/main/java/com/github/mustachejava/codes/DefaultCode.java b/compiler/src/main/java/com/github/mustachejava/codes/DefaultCode.java index dd71e51f..17aeff61 100644 --- a/compiler/src/main/java/com/github/mustachejava/codes/DefaultCode.java +++ b/compiler/src/main/java/com/github/mustachejava/codes/DefaultCode.java @@ -31,6 +31,7 @@ public class DefaultCode implements Code, Cloneable { protected final boolean returnThis; protected final Binding binding; protected final DefaultMustacheFactory df; + protected final boolean dynamic; @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"}) public Object clone() { @@ -79,7 +80,13 @@ public DefaultCode(TemplateContext tc, DefaultMustacheFactory df, Mustache musta this.type = type; this.name = name; this.tc = tc; - this.binding = oh == null ? null : oh.createBinding(name, tc, this); + if (name != null && name.startsWith("*")) { + this.binding = oh == null ? null : oh.createBinding(name.substring(1), tc, this); + this.dynamic = true; + } else { + this.binding = oh == null ? null : oh.createBinding(name, tc, this); + this.dynamic = false; + } this.returnThis = ".".equals(name); } @@ -142,6 +149,12 @@ public Object get(List scopes) { return length == 0 ? null : scopes.get(length - 1); } try { + if (dynamic) { + // We need to create a new binding each time, as the name is dynamic + String dynamicName = (String) binding.get(scopes); + // TODO: cache these bindings + return oh.createBinding(dynamicName, tc, this).get(scopes); + } return binding.get(scopes); } catch (MustacheException e) { e.setContext(tc); diff --git a/compiler/src/main/java/com/github/mustachejava/codes/DynamicPartialCode.java b/compiler/src/main/java/com/github/mustachejava/codes/DynamicPartialCode.java new file mode 100644 index 00000000..f2debc3a --- /dev/null +++ b/compiler/src/main/java/com/github/mustachejava/codes/DynamicPartialCode.java @@ -0,0 +1,32 @@ +package com.github.mustachejava.codes; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.TemplateContext; + +import java.io.Writer; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DynamicPartialCode extends DefaultCode { + public DynamicPartialCode(TemplateContext tc, DefaultMustacheFactory df, String name) { + super(tc, df, null, name, ">*"); + } + + private final ConcurrentMap partialCodeMap = new ConcurrentHashMap<>(); + + @Override + public Writer execute(Writer writer, List scopes) { + String partialName = (String) binding.get(scopes); + if (partialName == null) { + return appendText(writer); + } + PartialCode partialCode = partialCodeMap.computeIfAbsent(partialName, name -> { + PartialCode pc = new PartialCode(tc, df, partialName); + pc.append(appended); + pc.init(); + return pc; + }); + return partialCode.execute(writer, scopes); + } +} diff --git a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java index 0c8f44e9..d685e671 100644 --- a/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/FullSpecTest.java @@ -45,6 +45,11 @@ public void lambdas() { public void inheritance() throws IOException { } + @Override + @Test + @Ignore("not ready yet") + public void dynamicnames() { + } @Override protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) { diff --git a/compiler/src/test/java/com/github/mustachejava/SpecTest.java b/compiler/src/test/java/com/github/mustachejava/SpecTest.java index 11171213..dc0ff7d7 100644 --- a/compiler/src/test/java/com/github/mustachejava/SpecTest.java +++ b/compiler/src/test/java/com/github/mustachejava/SpecTest.java @@ -58,6 +58,11 @@ public void inheritance() throws IOException { run(getSpec("~inheritance.yml")); } + @Test + public void dynamicnames() throws IOException { + run(getSpec("~dynamic-names.yml")); + } + private void run(JsonNode spec) { int fail = 0; int success = 0;