diff --git a/src/main/java/com/samskivert/mustache/Mustache.java b/src/main/java/com/samskivert/mustache/Mustache.java
index e64d663..59294cf 100644
--- a/src/main/java/com/samskivert/mustache/Mustache.java
+++ b/src/main/java/com/samskivert/mustache/Mustache.java
@@ -10,7 +10,9 @@ import java.io.StringReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
@@ -379,6 +381,23 @@ public class Mustache {
*/
boolean visitInclude (String name);
+ /** Visits a parent partial tag. For backward compatibility by default
+ * false is returned.
+ * @param name the name of the parent partial template specified by the tag.
+ * @return true if the template should be resolved and visited, false to skip it.
+ */
+ default boolean visitParent (String name) {
+ return false;
+ }
+
+ /** Visits a block tag. For backward compatibility by default is skipped.
+ * @param name the name of the block.
+ * @return true if the contents of the block should be visited, false to skip.
+ */
+ default boolean visitBlock (String name) {
+ return false;
+ }
+
/** Visits a section tag.
* @param name the name of the section.
* @return true if the contents of the section should be visited, false to skip.
@@ -416,7 +435,6 @@ public class Mustache {
// trim() modifies segs! Its return is not a copy!
// now that we have all of our segments, we make a pass through them to trim whitespace
// from section tags which stand alone on their lines
- assert segs != null;
for (int ii = 0, ll = segs.length; ii < ll; ii++) {
Template.Segment seg = segs[ii];
Template.Segment pseg = (ii > 0 ) ? segs[ii-1] : null;
@@ -427,20 +445,30 @@ public class Mustache {
boolean prevBlank = ((pseg == null && top) || (prev != null && prev.trailsBlank()));
boolean nextBlank = ((nseg == null && top) || (next != null && next.leadsBlank()));
// potentially trim around the open and close tags of a block segment
- if (seg instanceof BlockSegment) {
- BlockSegment block = (BlockSegment)seg;
- if (prevBlank && block.firstLeadsBlank()) {
- if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
- block.trimFirstBlank();
- block._standaloneStart = true;
+ if (seg instanceof StandaloneSection) {
+ StandaloneSection sect = (StandaloneSection)seg;
+ String indent = "";
+ if (prevBlank && sect.firstLeadsBlank()) {
+ if (prev != null) {
+ // capture the indent before we trim
+ indent = prev.indent();
+ segs[ii-1] = prev.trimTrailBlank();
+ }
+ sect.trimFirstBlank();
+ sect.standaloneStart(true);
}
- if (nextBlank && block.lastTrailsBlank()) {
- block.trimLastBlank();
- if (nseg != null) segs[ii+1] = next.trimLeadBlank();
- block._standaloneEnd = true;
+ if (nextBlank && sect.lastTrailsBlank()) {
+ sect.trimLastBlank();
+ if (next != null) segs[ii+1] = next.trimLeadBlank();
+ sect.standaloneEnd(true);
+ }
+ if (sect instanceof ParentTemplateSegment && ! indent.equals("")) {
+ ParentTemplateSegment pt = (ParentTemplateSegment) sect;
+ segs[ii] = pt.indent(indent, pseg == null, nseg == null);
}
}
- // we have to indent partials if there is space before they are also standalone...
+
+ // we have to indent partials if there is space before and they are also standalone.
else if (seg instanceof IncludedTemplateSegment) {
IncludedTemplateSegment include = (IncludedTemplateSegment) seg;
if (prev != null && prevBlank && nextBlank) {
@@ -450,8 +478,11 @@ public class Mustache {
include = include.indent(indent, pseg == null,nseg == null);
segs[ii] = include;
}
- //segs[ii-1] = prev.trimTrailBlank();
- /* We trim the end because partials follow standalone just like blocks */
+ /*
+ * We trim the end because partials follow standalone just like blocks.
+ * HOWEVER we do NOT trim the previous StringSegment as it provides the partial indentation.
+ * See indentSegs.
+ */
if (next != null) {
segs[ii+1] = next.trimLeadBlank();
}
@@ -491,8 +522,8 @@ public class Mustache {
Template.Segment pseg = (i > 0) ? _segs[i-1] : null;
Template.Segment nseg = (i < length - 1) ? _segs[i+1] : null;
Template.Segment copy;
- if (seg instanceof BlockSegment) {
- BlockSegment bs = (BlockSegment) seg;
+ if (seg instanceof AbstractSectionSegment) {
+ AbstractSectionSegment bs = (AbstractSectionSegment) seg;
boolean first;
boolean last;
if (pseg == null) {
@@ -536,8 +567,8 @@ public class Mustache {
if (nseg == null) {
last = _last;
}
- else if (nseg instanceof BlockSegment) {
- BlockSegment bs = (BlockSegment) nseg;
+ else if (nseg instanceof AbstractSectionSegment) {
+ AbstractSectionSegment bs = (AbstractSectionSegment) nseg;
last = ! bs.isStandaloneStart();
}
else if (nseg.isStandalone()) {
@@ -554,8 +585,8 @@ public class Mustache {
* partial tag.
* [ WS ]{{> partial }}[\n]
*
- * That is partial tags do not have the trailing blank removed during the trim
- * process. This avoids needlessley creating StringSegment tags.
+ * That is partial tags do not have the trailing blank removed during the trim process.
+ * This avoids needlessley creating StringSegment tags.
*/
if (seg.isStandalone()) {
boolean last;
@@ -590,6 +621,34 @@ public class Mustache {
return _segs;
}
+ static Template.Segment[] replaceBlockSegs(Template.Segment[] _segs, Map blocks) {
+ if (blocks.isEmpty()) {
+ return _segs;
+ }
+ int length = _segs.length;
+ Template.Segment[] copySegs = new Template.Segment[length];
+ boolean changed = false;
+ for (int i = 0; i < _segs.length; i++) {
+ Template.Segment seg = _segs[i];
+ Template.Segment copy;
+ if (seg instanceof BlockReplaceable) {
+ BlockReplaceable br = (BlockReplaceable) seg;
+ copy = br.replaceBlocks(blocks);
+ }
+ else {
+ copy = seg;
+ }
+ if (copy != seg) {
+ changed = true;
+ }
+ copySegs[i] = copy;
+ }
+ if (changed) {
+ return copySegs;
+ }
+ return _segs;
+ }
+
protected static void restoreStartTag (StringBuilder text, Delims starts) {
text.insert(0, starts.start1);
if (starts.start2 != NO_CHAR) {
@@ -854,9 +913,34 @@ public class Mustache {
};
case '>':
- _segs.add(new IncludedTemplateSegment(_comp, tag1));
+ _segs.add(new IncludedTemplateSegment(_comp, tag1, tagLine));
return this;
-
+ case '<':
+ requireNoNewlines(tag, tagLine);
+ return new Accumulator(_comp, false) {
+ @Override public Template.Segment[] finish () {
+ throw new MustacheParseException(
+ "Parent missing close tag '" + tag1 + "'", tagLine);
+ }
+ @Override protected Accumulator addCloseSectionSegment (String itag, int line) {
+ requireSameName(tag1, itag, line);
+ outer._segs.add(new ParentTemplateSegment(_comp, itag, super.finish(), tagLine));
+ return outer;
+ }
+ };
+ case '$':
+ requireNoNewlines(tag, tagLine);
+ return new Accumulator(_comp, false) {
+ @Override public Template.Segment[] finish () {
+ throw new MustacheParseException(
+ "Block missing close tag '" + tag1 + "'", tagLine);
+ }
+ @Override protected Accumulator addCloseSectionSegment (String itag, int line) {
+ requireSameName(tag1, itag, line);
+ outer._segs.add(new BlockSegment(_comp, itag, super.finish(), tagLine));
+ return outer;
+ }
+ };
case '^':
requireNoNewlines(tag, tagLine);
return new Accumulator(_comp, false) {
@@ -1024,30 +1108,19 @@ public class Mustache {
protected final boolean _first;
}
- /** A segment that loads and executes a sub-template. */
- protected static class IncludedTemplateSegment extends Template.Segment {
- public IncludedTemplateSegment (Compiler compiler, String name) {
- this(compiler, name, "");
- }
- private IncludedTemplateSegment (Compiler compiler, String name, String indent) {
+ /** An abstract segment that is a template include. */
+ protected static abstract class AbstractPartialSegment extends NamedSegment {
+ protected AbstractPartialSegment (Compiler compiler, String name, int line, String indent) {
+ super(name, line);
_comp = compiler;
- _name = name;
_indent = indent;
}
- @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
+ @Override public final void execute (Template tmpl, Template.Context ctx, Writer out) {
// we must take care to preserve our context rather than creating a new one, which
// would happen if we just called execute() with ctx.data
getTemplate().executeSegs(ctx, out);
}
- @Override public void decompile (Delims delims, StringBuilder into) {
- delims.addTag('>', _name, into);
- }
- @Override public void visit (Visitor visitor) {
- if (visitor.visitInclude(_name)) {
- getTemplate().visit(visitor);
- }
- }
- protected Template getTemplate () {
+ protected final Template getTemplate () {
// we compile our template lazily to avoid infinie recursion if a template includes
// itself (see issue #13)
Template t = _template;
@@ -1057,7 +1130,7 @@ public class Mustache {
lock.lock();
try {
if ((t = _template) == null) {
- _template = t = _comp.loadTemplate(_name).indent(_indent);
+ _template = t = _loadTemplate();
}
} finally {
lock.unlock();
@@ -1065,31 +1138,158 @@ public class Mustache {
}
return t;
}
- protected IncludedTemplateSegment indent (String indent, boolean first, boolean last) {
+
+ protected Template _loadTemplate() {
+ return _comp.loadTemplate(_name).indent(_indent);
+ }
+
+ @Override public abstract boolean isStandalone();
+
+ protected final Compiler _comp;
+ protected final String _indent;
+ private final Lock lock = new ReentrantLock();
+ private volatile Template _template;
+ }
+
+ /** A segment that loads and executes a sub-template by spec called a partial. */
+ protected static class IncludedTemplateSegment extends AbstractPartialSegment {
+ public IncludedTemplateSegment (Compiler compiler, String name, int line) {
+ this(compiler, name, line,"");
+ }
+ private IncludedTemplateSegment (Compiler compiler, String name, int line, String indent) {
+ super(compiler, name, line, indent);
+ }
+ @Override public void decompile (Delims delims, StringBuilder into) {
+ delims.addTag('<', _name, into);
+ }
+ @Override public void visit (Visitor visitor) {
+ if (visitor.visitInclude(_name)) {
+ getTemplate().visit(visitor);
+ }
+ }
+ @Override protected IncludedTemplateSegment indent(String indent, boolean first, boolean last) {
// Indent this partial based on the spacing provided.
// per the spec however much the partial reference is indendented (leading whitespace)
// is how much the partial content should be indented.
if (indent.equals("") || ! _standalone) {
return this;
}
- IncludedTemplateSegment is = new IncludedTemplateSegment(_comp, _name, indent + this._indent );
+ IncludedTemplateSegment is = new IncludedTemplateSegment(_comp, _name, _line, indent + this._indent );
is._standalone = _standalone;
return is;
}
- @Override boolean isStandalone () { return _standalone; }
- @Override
- public String toString () {
+ @Override public String toString () {
return "Include(name=" + _name + ", indent=" + _indent + ", standalone=" + _standalone
+ ")";
}
+ @Override public boolean isStandalone() { return _standalone; }
+ protected boolean _standalone;
+ }
- protected final Compiler _comp;
- protected final String _name;
- private final String _indent;
- private final Lock lock = new ReentrantLock();
- private volatile Template _template;
- protected boolean _standalone = false;
+ /** A segment that loads and executes a parent template by spec called inheritance. */
+ protected static class ParentTemplateSegment extends AbstractPartialSegment implements StandaloneSection {
+ public ParentTemplateSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
+ this(compiler, name, segs, line, "");
+ }
+ private ParentTemplateSegment (Compiler compiler, String name, Template.Segment[] segs, int line, String indent) {
+ super(compiler, name, line, indent);
+ // Notice we consider the contents inside a parent to be at "top" = true.
+ // Furthermore to correctly trim we remove non blocks.
+ this._segs = trim(removeNonBlocks(segs), true);
+ this._blocks = new LinkedHashMap<>();
+ }
+ private ParentTemplateSegment (ParentTemplateSegment original, Template.Segment[] segs, String indent, Map blocks) {
+ super(original._comp, original._name, original._line, indent + original._indent);
+ this._segs = segs;
+ this._standaloneStart = original._standaloneStart;
+ this._standaloneEnd = original._standaloneEnd;
+ Map newBlocks = new LinkedHashMap<>();
+ newBlocks.putAll(original._blocks);
+ newBlocks.putAll(blocks);
+ this._blocks = newBlocks;
+ }
+ private static Template.Segment[] removeNonBlocks(Template.Segment[] segs) {
+ // the content inside a parent call
+ // e.g. {{ copy = new ArrayList<>();
+ for (Template.Segment seg : segs) {
+ if (seg instanceof BlockSegment) {
+ copy.add(seg);
+ }
+ }
+ return copy.toArray(new Template.Segment[] {});
+ }
+ @Override public void decompile (Delims delims, StringBuilder into) {
+ delims.addTag('<', _name, into);
+ }
+ @Override public void visit (Visitor visitor) {
+ if (visitor.visitParent(_name)) {
+ getTemplate().visit(visitor);
+ }
+ }
+ @Override protected ParentTemplateSegment indent(String indent, boolean first, boolean last) {
+ // Indent this partial based on the spacing provided.
+ // per the spec however much the partial reference is indendented (leading whitespace minus \n)
+ // is how much the partial content should be indented.
+ if (indent.equals("") || ! _standaloneStart) {
+ return this;
+ }
+ return new ParentTemplateSegment(this, this._segs, indent, Collections.emptyMap());
+ }
+ @Override protected Template _loadTemplate () {
+ Map blocks = new LinkedHashMap<>();
+ // While we might capture other segments we only care about blocks.
+ // The reason we are doing this now instead of at constructor time is
+ // that indentation and trim might have changed the segments (not sure on this).
+ for (Template.Segment seg : _segs) {
+ if (seg instanceof BlockSegment) {
+ BlockSegment bs = (BlockSegment) seg;
+ blocks.put(bs._name, bs);
+ }
+ }
+ blocks.putAll(_blocks);
+ return super._loadTemplate().replaceBlocks(blocks);
+ }
+ // Parents have an unusual condition
+ // where if they are empty the end tag still
+ // owns the following newline.
+ // Thus lastTrails and trimLast need custom behavior.
+ @Override public boolean lastTrailsBlank () {
+ Template.Segment[] _segs = _segs();
+ int lastIdx = _segs.length-1;
+
+ if (lastIdx < 0) {
+ return true;
+ }
+ if (!(_segs[lastIdx] instanceof StringSegment)) return false;
+ return ((StringSegment)_segs[lastIdx]).trailsBlank();
+ }
+ @Override public void trimLastBlank () {
+ Template.Segment[] _segs = _segs();
+ int idx = _segs.length-1;
+ if (idx < 0) return;
+ _segs[idx] = ((StringSegment)_segs[idx]).trimTrailBlank();
+ }
+
+ @Override public ParentTemplateSegment replaceBlocks(Map blocks) {
+ return new ParentTemplateSegment(this, _segs, "", blocks);
+ }
+ @Override public Template.Segment[] _segs() { return _segs; }
+ @Override public boolean isStandalone() { return _standaloneEnd; }
+ @Override public boolean isStandaloneStart() { return _standaloneStart; }
+ @Override public boolean isStandaloneEnd() { return _standaloneEnd; }
+ @Override public void standaloneStart(boolean standaloneStart) { this._standaloneStart = standaloneStart; }
+ @Override public void standaloneEnd(boolean standaloneEnd) { this._standaloneEnd = standaloneEnd; }
+ @Override public String toString() {
+ return "Parent(name=" + _name + ", indent=" + _indent + ", standaloneStart=" + _standaloneStart
+ + ")";
+ }
+ protected final Template.Segment[] _segs;
+ protected boolean _standaloneStart = false;
+ protected boolean _standaloneEnd = false;
+ protected final Map _blocks;
}
/** A helper class for named segments. */
@@ -1140,32 +1340,50 @@ public class Mustache {
protected final Escaper _escaper;
}
- /** A helper class for block segments. */
- protected static abstract class BlockSegment extends NamedSegment {
- public boolean firstLeadsBlank () {
+ protected interface StandaloneSection extends BlockReplaceable {
+ default boolean firstLeadsBlank () {
+ Template.Segment[] _segs = _segs();
if (_segs.length == 0 || !(_segs[0] instanceof StringSegment)) return false;
return ((StringSegment)_segs[0]).leadsBlank();
}
- public void trimFirstBlank () {
+ default void trimFirstBlank () {
+ Template.Segment[] _segs = _segs();
_segs[0] = ((StringSegment)_segs[0]).trimLeadBlank();
}
-
- public boolean lastTrailsBlank () {
+ default boolean lastTrailsBlank () {
+ Template.Segment[] _segs = _segs();
int lastIdx = _segs.length-1;
if (_segs.length == 0 || !(_segs[lastIdx] instanceof StringSegment)) return false;
return ((StringSegment)_segs[lastIdx]).trailsBlank();
}
- public void trimLastBlank () {
+ default void trimLastBlank () {
+ Template.Segment[] _segs = _segs();
int idx = _segs.length-1;
_segs[idx] = ((StringSegment)_segs[idx]).trimTrailBlank();
}
+ boolean isStandaloneEnd ();
+ boolean isStandaloneStart ();
+ void standaloneStart (boolean standaloneStart);
+ void standaloneEnd (boolean standaloneEnd);
- protected BlockSegment (String name, Template.Segment[] segs, int line) {
+ Template.Segment[] _segs();
+ }
+
+ protected interface BlockReplaceable {
+ public Template.Segment replaceBlocks(Map blocks);
+ }
+
+ /** A helper class for section-like segments. */
+ protected static abstract class AbstractSectionSegment extends NamedSegment implements StandaloneSection {
+
+ protected AbstractSectionSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
super(name, line);
+ _comp = compiler;
_segs = trim(segs, false);
}
- protected BlockSegment (BlockSegment original, Template.Segment[] segs) {
+ protected AbstractSectionSegment (AbstractSectionSegment original, Template.Segment[] segs) {
super(original._name, original._line);
+ _comp = original._comp;
// this call assumes the segments are already trimmed
_segs = segs;
}
@@ -1176,30 +1394,29 @@ public class Mustache {
}
}
- protected abstract BlockSegment indent (String indent, boolean first, boolean last);
+ protected abstract AbstractSectionSegment indent (String indent, boolean first, boolean last);
- @Override
- boolean isStandalone () {
- return _standaloneEnd;
- }
- boolean isStandaloneStart () {
- return _standaloneStart;
- }
+ @Override public boolean isStandalone() { return _standaloneEnd; }
+ @Override public boolean isStandaloneStart() { return _standaloneStart; }
+ @Override public boolean isStandaloneEnd() { return _standaloneEnd; }
+ @Override public void standaloneStart(boolean standaloneStart) { this._standaloneStart = standaloneStart; }
+ @Override public void standaloneEnd(boolean standaloneEnd) { this._standaloneEnd = standaloneEnd; }
+ @Override public Template.Segment[] _segs() { return _segs; }
+
+ protected final Compiler _comp;
protected final Template.Segment[] _segs;
protected boolean _standaloneStart = false;
protected boolean _standaloneEnd = false;
}
/** A segment that represents a section. */
- protected static class SectionSegment extends BlockSegment {
+ protected static class SectionSegment extends AbstractSectionSegment {
public SectionSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
- super(name, segs, line);
- _comp = compiler;
+ super(compiler, name, segs, line);
}
protected SectionSegment (SectionSegment original, Template.Segment[] segs) {
super(original, segs);
- _comp = original._comp;
}
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
@@ -1240,27 +1457,75 @@ public class Mustache {
}
}
@Override protected SectionSegment indent (String indent, boolean first, boolean last) {
- // If the end tag is standalone we do NOT add the indent on the end
- // (e.g. the newline following the end tag).
- // This is because the block always owns the newlines within the block but
- // it only owns the last newline following the end tag if and only if
- // the closing tag is standalone.
Template.Segment[] segs = indentSegs(_segs, indent, first, last);
if (segs == _segs) {
return this;
}
return new SectionSegment(this, segs);
}
+ @Override public SectionSegment replaceBlocks(Map blocks) {
+ Template.Segment[] segs = replaceBlockSegs(_segs, blocks);
+ if (segs == _segs) {
+ return this;
+ }
+ return new SectionSegment(this, segs);
+ }
@Override public String toString () {
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
}
- protected final Compiler _comp;
+ }
+
+ /** A parent partial parameter using $ as the sigil. */
+ protected static class BlockSegment extends AbstractSectionSegment {
+ public BlockSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
+ super(compiler, name, segs, line);
+ }
+ protected BlockSegment (BlockSegment original, Template.Segment[] segs) {
+ super(original, segs);
+ }
+ @Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
+ executeSegs(tmpl, ctx, out);
+ }
+ @Override public void decompile (Delims delims, StringBuilder into) {
+ delims.addTag('$', _name, into);
+ for (Template.Segment seg : _segs) seg.decompile(delims, into);
+ delims.addTag('/', _name, into);
+ }
+ @Override public void visit (Visitor visitor) {
+ if (visitor.visitBlock(_name)) {
+ for (Template.Segment seg : _segs) {
+ seg.visit(visitor);
+ }
+ }
+ }
+ @Override protected BlockSegment indent (String indent, boolean first, boolean last) {
+ // Current indenting block segments is not defined by spec but might eventually
+ Template.Segment[] segs = indentSegs(_segs, indent, first, last);
+ if (segs == _segs) {
+ return this;
+ }
+ return new BlockSegment(this, segs);
+ }
+ @Override public BlockSegment replaceBlocks (Map blocks) {
+ BlockSegment bs = blocks.get(_name);
+ if (bs == null) {
+ Template.Segment[] segs = replaceBlockSegs(_segs, blocks);
+ if (segs == _segs) {
+ return this;
+ }
+ return new BlockSegment(this, segs);
+ }
+ return new BlockSegment(this, bs._segs);
+ }
+ @Override public String toString () {
+ return "Block(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
+ }
}
/** A segment that represents an inverted section. */
- protected static class InvertedSegment extends BlockSegment {
+ protected static class InvertedSegment extends AbstractSectionSegment {
public InvertedSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
- super(name, segs, line);
+ super(compiler, name, segs, line);
_comp = compiler;
}
protected InvertedSegment (InvertedSegment original, Template.Segment[] segs) {
@@ -1300,14 +1565,20 @@ public class Mustache {
}
}
}
- @Override
- protected InvertedSegment indent (String indent, boolean first, boolean last) {
+ @Override protected InvertedSegment indent (String indent, boolean first, boolean last) {
Template.Segment[] segs = indentSegs(_segs, indent, first, last);
if (segs == _segs) {
return this;
}
return new InvertedSegment(this, segs);
}
+ @Override public InvertedSegment replaceBlocks (Map blocks) {
+ Template.Segment[] segs = replaceBlockSegs(_segs, blocks);
+ if (segs == _segs) {
+ return this;
+ }
+ return new InvertedSegment(this, segs);
+ }
@Override public String toString () {
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
}
diff --git a/src/main/java/com/samskivert/mustache/Template.java b/src/main/java/com/samskivert/mustache/Template.java
index b4ed014..393ed72 100644
--- a/src/main/java/com/samskivert/mustache/Template.java
+++ b/src/main/java/com/samskivert/mustache/Template.java
@@ -11,6 +11,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
+import com.samskivert.mustache.Mustache.BlockSegment;
+
/**
* Represents a compiled template. Templates are executed with a context to generate
* output. The context can be any tree of objects. Variables are resolved against the context.
@@ -165,7 +167,7 @@ public class Template {
_fcache = compiler.collector.createFetcherCache();
}
- protected Template indent(String indent) {
+ protected Template indent (String indent) {
// What we want to do here is rebuild this partial template but indented.
// If identing does not change anything we return the original template.
if (indent.equals("")) {
@@ -178,6 +180,17 @@ public class Template {
return new Template(copySegs, _compiler);
}
+ protected Template replaceBlocks (Map blocks) {
+ if (blocks.isEmpty()) {
+ return this;
+ }
+ Segment[] copySegs = Mustache.replaceBlockSegs(_segs, blocks);
+ if (copySegs == _segs) {
+ return this;
+ }
+ return new Template(copySegs, _compiler);
+ }
+
protected void executeSegs (Context ctx, Writer out) throws MustacheException {
for (Segment seg : _segs) {
seg.execute(this, ctx, out);
diff --git a/src/test/java/com/samskivert/mustache/PartialThreadSafeTest.java b/src/test/java/com/samskivert/mustache/PartialThreadSafeTest.java
index 577764c..2b25ee1 100644
--- a/src/test/java/com/samskivert/mustache/PartialThreadSafeTest.java
+++ b/src/test/java/com/samskivert/mustache/PartialThreadSafeTest.java
@@ -26,7 +26,6 @@ public class PartialThreadSafeTest {
@Test
public void testPartialThreadSafe() throws Exception {
- long t = System.currentTimeMillis();
AtomicInteger loadCount = new AtomicInteger();
TemplateLoader loader = new TemplateLoader() {
@Override
@@ -64,7 +63,5 @@ public class PartialThreadSafeTest {
}
assertTrue(q.isEmpty());
assertEquals(1, loadCount.get());
- System.out.println(loadCount);
- System.out.println(System.currentTimeMillis() - t);
}
}
diff --git a/src/test/java/com/samskivert/mustache/specs/CustomSpecTest.java b/src/test/java/com/samskivert/mustache/specs/CustomSpecTest.java
index b83052d..9e9bd7e 100644
--- a/src/test/java/com/samskivert/mustache/specs/CustomSpecTest.java
+++ b/src/test/java/com/samskivert/mustache/specs/CustomSpecTest.java
@@ -16,7 +16,9 @@ public class CustomSpecTest extends SpecTest {
@Parameters(name = "{1}")
public static Collection