Merge pull request #153 from jstachio/custom_spec_tests
Fix nested partial indentation and custom spec tests
This commit is contained in:
@@ -430,25 +430,31 @@ public class Mustache {
|
|||||||
if (prevBlank && block.firstLeadsBlank()) {
|
if (prevBlank && block.firstLeadsBlank()) {
|
||||||
if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
|
if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
|
||||||
block.trimFirstBlank();
|
block.trimFirstBlank();
|
||||||
|
block._standaloneStart = true;
|
||||||
}
|
}
|
||||||
if (nextBlank && block.lastTrailsBlank()) {
|
if (nextBlank && block.lastTrailsBlank()) {
|
||||||
block.trimLastBlank();
|
block.trimLastBlank();
|
||||||
if (nseg != null) segs[ii+1] = next.trimLeadBlank();
|
if (nseg != null) segs[ii+1] = next.trimLeadBlank();
|
||||||
|
block._standaloneEnd = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we have to indent partials if there is space before
|
// we have to indent partials if there is space before
|
||||||
// they are also standalone...
|
// they are also standalone...
|
||||||
else if (seg instanceof IncludedTemplateSegment) {
|
else if (seg instanceof IncludedTemplateSegment) {
|
||||||
|
IncludedTemplateSegment include = (IncludedTemplateSegment) seg;
|
||||||
if (prev != null && prevBlank && nextBlank) {
|
if (prev != null && prevBlank && nextBlank) {
|
||||||
String indent = prev.indent();
|
String indent = prev.indent();
|
||||||
|
include._standalone = true;
|
||||||
if (! indent.equals("")) {
|
if (! indent.equals("")) {
|
||||||
segs[ii] = seg.indent(indent, nseg == null);
|
include = include.indent(indent, pseg == null,nseg == null);
|
||||||
|
segs[ii] = include;
|
||||||
}
|
}
|
||||||
|
//segs[ii-1] = prev.trimTrailBlank();
|
||||||
/*
|
/*
|
||||||
* We trim the end because partials
|
* We trim the end because partials
|
||||||
* follow standalone just like blocks
|
* follow standalone just like blocks
|
||||||
*/
|
*/
|
||||||
if (nseg != null) {
|
if (next != null) {
|
||||||
segs[ii+1] = next.trimLeadBlank();
|
segs[ii+1] = next.trimLeadBlank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,6 +470,59 @@ public class Mustache {
|
|||||||
return segs;
|
return segs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Template.Segment[] indentSegs(Template.Segment[] _segs, String indent) {
|
||||||
|
// unlike trim this method clones the segments if they have changed
|
||||||
|
// so the return value must be handled
|
||||||
|
// a simple identity check on the return can be used to determine
|
||||||
|
// if there is change
|
||||||
|
if (indent.equals("")) {
|
||||||
|
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 pseg = (i > 0) ? _segs[i-1] : null;
|
||||||
|
Template.Segment nseg = (i < length - 1) ? _segs[i+1] : null;
|
||||||
|
/*
|
||||||
|
* If we are the first segment then we rely on the indentation
|
||||||
|
* already present before the partial tag.
|
||||||
|
* [ WS ]{{> partial }}
|
||||||
|
*
|
||||||
|
* That is partial tags do not have the trailing blank
|
||||||
|
* removed.
|
||||||
|
*
|
||||||
|
* This avoids needlessley creating StringSegment tags.
|
||||||
|
*/
|
||||||
|
boolean first = pseg == null ? false : seg.isStandalone() || pseg.isStandalone();
|
||||||
|
boolean last;
|
||||||
|
if (nseg instanceof BlockSegment){
|
||||||
|
BlockSegment bs = (BlockSegment) nseg;
|
||||||
|
last = bs.isStandaloneStart();
|
||||||
|
}
|
||||||
|
else if (nseg == null || nseg.isStandalone()) {
|
||||||
|
last = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
last = true;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Recursively indent
|
||||||
|
*/
|
||||||
|
Template.Segment copy = seg.indent(indent, first, last);
|
||||||
|
if (copy != seg) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
copySegs[i] = copy;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
return copySegs;
|
||||||
|
}
|
||||||
|
return _segs;
|
||||||
|
}
|
||||||
|
|
||||||
protected static void restoreStartTag (StringBuilder text, Delims starts) {
|
protected static void restoreStartTag (StringBuilder text, Delims starts) {
|
||||||
text.insert(0, starts.start1);
|
text.insert(0, starts.start1);
|
||||||
if (starts.start2 != NO_CHAR) {
|
if (starts.start2 != NO_CHAR) {
|
||||||
@@ -801,15 +860,16 @@ public class Mustache {
|
|||||||
/** A simple segment that reproduces a string. */
|
/** A simple segment that reproduces a string. */
|
||||||
protected static class StringSegment extends Template.Segment {
|
protected static class StringSegment extends Template.Segment {
|
||||||
public StringSegment (String text, boolean first) {
|
public StringSegment (String text, boolean first) {
|
||||||
this(text, blankPos(text, true, first), blankPos(text, false, first));
|
this(text, blankPos(text, true, first), blankPos(text, false, first), first);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringSegment (String text, int leadBlank, int trailBlank) {
|
public StringSegment (String text, int leadBlank, int trailBlank, boolean first) {
|
||||||
assert leadBlank >= -1;
|
assert leadBlank >= -1;
|
||||||
assert trailBlank >= -1;
|
assert trailBlank >= -1;
|
||||||
_text = text;
|
_text = text;
|
||||||
_leadBlank = leadBlank;
|
_leadBlank = leadBlank;
|
||||||
_trailBlank = trailBlank;
|
_trailBlank = trailBlank;
|
||||||
|
_first = first;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean leadsBlank () { return _leadBlank != -1; }
|
public boolean leadsBlank () { return _leadBlank != -1; }
|
||||||
@@ -818,11 +878,11 @@ public class Mustache {
|
|||||||
public StringSegment trimLeadBlank () {
|
public StringSegment trimLeadBlank () {
|
||||||
if (_leadBlank == -1) return this;
|
if (_leadBlank == -1) return this;
|
||||||
int lpos = _leadBlank+1, newTrail = _trailBlank == -1 ? -1 : _trailBlank-lpos;
|
int lpos = _leadBlank+1, newTrail = _trailBlank == -1 ? -1 : _trailBlank-lpos;
|
||||||
return new StringSegment(_text.substring(lpos), -1, newTrail);
|
return new StringSegment(_text.substring(lpos), -1, newTrail, _first);
|
||||||
}
|
}
|
||||||
public StringSegment trimTrailBlank () {
|
public StringSegment trimTrailBlank () {
|
||||||
return _trailBlank == -1 ? this : new StringSegment(
|
return _trailBlank == -1 ? this : new StringSegment(
|
||||||
_text.substring(0, _trailBlank), _leadBlank, -1);
|
_text.substring(0, _trailBlank), _leadBlank, -1, _first);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -836,12 +896,16 @@ public class Mustache {
|
|||||||
return _text.substring(_trailBlank);
|
return _text.substring(_trailBlank);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSegment indent(String indent, boolean last) {
|
StringSegment indent(String indent, boolean first, boolean last) {
|
||||||
if (indent.equals("")) {
|
if (indent.equals("")) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
String reindent = reindent(_text, indent, last);
|
String reindent = reindent(_text, indent, first, last);
|
||||||
return new StringSegment(reindent , _leadBlank, _trailBlank);
|
return new StringSegment(reindent, _first);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override boolean isStandalone() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
@@ -857,15 +921,18 @@ public class Mustache {
|
|||||||
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
||||||
_leadBlank + "/" + _trailBlank;
|
_leadBlank + "/" + _trailBlank;
|
||||||
}
|
}
|
||||||
|
|
||||||
//we indent after every new line for partial indententation
|
//we indent after every new line for partial indententation
|
||||||
private static String reindent(String input, String indent, boolean last) {
|
private static String reindent(String input, String indent, boolean first, boolean last) {
|
||||||
int length = input.length();
|
int length = input.length();
|
||||||
StringBuilder sb = new StringBuilder(length);
|
StringBuilder sb = new StringBuilder(indent.length() + length);
|
||||||
|
if (first) {
|
||||||
|
sb.append(indent);
|
||||||
|
}
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
char c = input.charAt(i);
|
char c = input.charAt(i);
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
if (c == '\n' && ! ( last && i == length - 1)) {
|
if (c == '\n' && ( last || i != length - 1) ) {
|
||||||
sb.append(indent);
|
sb.append(indent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -887,6 +954,7 @@ public class Mustache {
|
|||||||
|
|
||||||
protected final String _text;
|
protected final String _text;
|
||||||
protected final int _leadBlank, _trailBlank;
|
protected final int _leadBlank, _trailBlank;
|
||||||
|
protected final boolean _first;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A segment that loads and executes a sub-template. */
|
/** A segment that loads and executes a sub-template. */
|
||||||
@@ -920,20 +988,30 @@ public class Mustache {
|
|||||||
}
|
}
|
||||||
return _template;
|
return _template;
|
||||||
}
|
}
|
||||||
protected IncludedTemplateSegment indent(String indent, boolean last) {
|
protected IncludedTemplateSegment indent(String indent, boolean first, boolean last) {
|
||||||
// Indent this partial based on the spacing provided.
|
// Indent this partial based on the spacing provided.
|
||||||
// per the spec however much the partial reference is indendented (leading whitespace)
|
// per the spec however much the partial reference is indendented (leading whitespace)
|
||||||
// is how much the partial content should be indented.
|
// is how much the partial content should be indented.
|
||||||
if (indent.equals("")) {
|
if (indent.equals("") || ! _standalone) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new IncludedTemplateSegment(_comp, _name, indent + this._indent );
|
IncludedTemplateSegment is = new IncludedTemplateSegment(_comp, _name, indent + this._indent );
|
||||||
|
is._standalone = _standalone;
|
||||||
|
return is;
|
||||||
}
|
}
|
||||||
|
@Override boolean isStandalone() { return _standalone; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Include(name=" + _name + ", indent=" + _indent + ", standalone=" + _standalone
|
||||||
|
+ ")";
|
||||||
|
}
|
||||||
|
|
||||||
protected final Compiler _comp;
|
protected final Compiler _comp;
|
||||||
protected final String _name;
|
protected final String _name;
|
||||||
private final String _indent;
|
private final String _indent;
|
||||||
private Template _template;
|
private Template _template;
|
||||||
|
protected boolean _standalone = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A helper class for named segments. */
|
/** A helper class for named segments. */
|
||||||
@@ -970,9 +1048,13 @@ public class Mustache {
|
|||||||
visitor.visitVariable(_name);
|
visitor.visitVariable(_name);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
VariableSegment indent(String indent, boolean last) {
|
VariableSegment indent(String indent, boolean first, boolean last) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
boolean isStandalone() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Var(" + _name + ":" + _line + ")";
|
return "Var(" + _name + ":" + _line + ")";
|
||||||
}
|
}
|
||||||
@@ -1004,15 +1086,31 @@ public class Mustache {
|
|||||||
super(name, line);
|
super(name, line);
|
||||||
_segs = trim(segs, false);
|
_segs = trim(segs, false);
|
||||||
}
|
}
|
||||||
|
protected BlockSegment (BlockSegment original, Template.Segment[] segs) {
|
||||||
|
super(original._name, original._line);
|
||||||
|
// this call assumes the segments are already trimmed
|
||||||
|
_segs = segs;
|
||||||
|
}
|
||||||
|
|
||||||
protected void executeSegs (Template tmpl, Template.Context ctx, Writer out) {
|
protected void executeSegs (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
for (Template.Segment seg : _segs) {
|
for (Template.Segment seg : _segs) {
|
||||||
seg.execute(tmpl, ctx, out);
|
seg.execute(tmpl, ctx, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract BlockSegment indent(String indent, boolean last);
|
|
||||||
|
|
||||||
|
protected abstract BlockSegment indent (String indent, boolean first, boolean last);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isStandalone() {
|
||||||
|
return _standaloneEnd;
|
||||||
|
}
|
||||||
|
boolean isStandaloneStart() {
|
||||||
|
return _standaloneStart;
|
||||||
|
}
|
||||||
|
|
||||||
protected final Template.Segment[] _segs;
|
protected final Template.Segment[] _segs;
|
||||||
|
protected boolean _standaloneStart = false;
|
||||||
|
protected boolean _standaloneEnd = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A segment that represents a section. */
|
/** A segment that represents a section. */
|
||||||
@@ -1021,6 +1119,10 @@ public class Mustache {
|
|||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
_comp = compiler;
|
_comp = compiler;
|
||||||
}
|
}
|
||||||
|
protected SectionSegment(SectionSegment original, Template.Segment[] segs) {
|
||||||
|
super(original, segs);
|
||||||
|
_comp = original._comp;
|
||||||
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
Iterator<?> iter = _comp.collector.toIterator(value);
|
Iterator<?> iter = _comp.collector.toIterator(value);
|
||||||
@@ -1059,16 +1161,15 @@ public class Mustache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override protected SectionSegment indent (String indent, boolean first, boolean last) {
|
||||||
protected SectionSegment indent(String indent, boolean last) {
|
|
||||||
if (indent.equals("")) {
|
if (indent.equals("")) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Template.Segment[] segs = Template.indentSegs(_segs, indent);
|
Template.Segment[] segs = indentSegs(_segs, indent);
|
||||||
if (segs == _segs) {
|
if (segs == _segs) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new SectionSegment(_comp, _name, segs, _line);
|
return new SectionSegment(this, segs);
|
||||||
}
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
@@ -1082,6 +1183,10 @@ public class Mustache {
|
|||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
_comp = compiler;
|
_comp = compiler;
|
||||||
}
|
}
|
||||||
|
protected InvertedSegment (InvertedSegment original, Template.Segment[] segs) {
|
||||||
|
super(original, segs);
|
||||||
|
_comp = original._comp;
|
||||||
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
Iterator<?> iter = _comp.collector.toIterator(value);
|
Iterator<?> iter = _comp.collector.toIterator(value);
|
||||||
@@ -1116,15 +1221,15 @@ public class Mustache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected InvertedSegment indent(String indent, boolean last) {
|
protected InvertedSegment indent (String indent, boolean first, boolean last) {
|
||||||
if (indent.equals("")) {
|
if (indent.equals("")) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Template.Segment[] segs = Template.indentSegs(_segs, indent);
|
Template.Segment[] segs = indentSegs(_segs, indent);
|
||||||
if (segs == _segs) {
|
if (segs == _segs) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new InvertedSegment(_comp, _name, segs, _line);
|
return new InvertedSegment(this, segs);
|
||||||
}
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
@@ -1136,7 +1241,8 @@ public class Mustache {
|
|||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
||||||
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
|
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
|
||||||
@Override public void visit (Visitor visit) {}
|
@Override public void visit (Visitor visit) {}
|
||||||
@Override FauxSegment indent(String indent, boolean last) { return this; }
|
@Override FauxSegment indent (String indent, boolean first, boolean last) { return this; }
|
||||||
|
@Override boolean isStandalone() { return true; }
|
||||||
@Override public String toString () { return "Faux"; }
|
@Override public String toString () { return "Faux"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,33 +176,12 @@ public class Template {
|
|||||||
if (indent.equals("")) {
|
if (indent.equals("")) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Segment[] copySegs = indentSegs(_segs, indent);
|
Segment[] copySegs = Mustache.indentSegs(_segs, indent);
|
||||||
if (copySegs == _segs) {
|
if (copySegs == _segs) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new Template(copySegs, _compiler);
|
return new Template(copySegs, _compiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Segment[] indentSegs(Segment[] _segs, String indent) {
|
|
||||||
if (indent.equals("")) {
|
|
||||||
return _segs;
|
|
||||||
}
|
|
||||||
int length = _segs.length;
|
|
||||||
Segment[] copySegs = new Segment[length];
|
|
||||||
boolean changed = false;
|
|
||||||
for (int i = 0; i < _segs.length; i++) {
|
|
||||||
Segment seg = _segs[i];
|
|
||||||
Segment copy = seg.indent(indent, i == (length - 1));
|
|
||||||
if (copy != seg) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
copySegs[i] = copy;
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
return copySegs;
|
|
||||||
}
|
|
||||||
return _segs;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void executeSegs (Context ctx, Writer out) throws MustacheException {
|
protected void executeSegs (Context ctx, Writer out) throws MustacheException {
|
||||||
for (Segment seg : _segs) {
|
for (Segment seg : _segs) {
|
||||||
@@ -422,8 +401,28 @@ public class Template {
|
|||||||
abstract void decompile (Mustache.Delims delims, StringBuilder into);
|
abstract void decompile (Mustache.Delims delims, StringBuilder into);
|
||||||
|
|
||||||
abstract void visit (Mustache.Visitor visitor);
|
abstract void visit (Mustache.Visitor visitor);
|
||||||
|
/**
|
||||||
abstract Segment indent(String indent, boolean last);
|
* Recursively indent by the parameter indent.
|
||||||
|
* @param indent should be space characters that are not \n
|
||||||
|
* @param first append indent to the first line (regardless if it has a \n or not)
|
||||||
|
* @param last append indent on the last \n that has no text after it
|
||||||
|
* @return newly crated segment or the same segment if nothing changed.
|
||||||
|
*/
|
||||||
|
abstract Segment indent(String indent, boolean first, boolean last);
|
||||||
|
/**
|
||||||
|
* Whether or not the segment is standalone.
|
||||||
|
* For blocks this is based on the closing tag.
|
||||||
|
* Once Trim is called standalone tags are determined so
|
||||||
|
* that proper (re)indentation will work without reparsing
|
||||||
|
* the template.
|
||||||
|
*
|
||||||
|
* String and variable tags are never standalone.
|
||||||
|
*
|
||||||
|
* The definition of standalone is defined by the mustache spec.
|
||||||
|
*
|
||||||
|
* @return true if the tag is standalone
|
||||||
|
*/
|
||||||
|
abstract boolean isStandalone();
|
||||||
|
|
||||||
protected static void write (Writer out, CharSequence data) {
|
protected static void write (Writer out, CharSequence data) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,16 +4,34 @@
|
|||||||
|
|
||||||
package com.samskivert.mustache;
|
package com.samskivert.mustache;
|
||||||
|
|
||||||
|
import static java.util.Map.entry;
|
||||||
|
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.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.assertEquals;
|
import org.junit.rules.TestRule;
|
||||||
import static org.junit.Assert.assertTrue;
|
import org.junit.rules.TestWatcher;
|
||||||
import static org.junit.Assert.fail;
|
import org.junit.runner.Description;
|
||||||
|
|
||||||
|
import com.samskivert.mustache.specs.SpecTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vestige from when JMustache supported both GWT and JVM.
|
* Vestige from when JMustache supported both GWT and JVM.
|
||||||
@@ -210,17 +228,25 @@ public abstract class SharedTests
|
|||||||
fail();
|
fail();
|
||||||
} catch (UnsupportedOperationException uoe) {} // expected
|
} catch (UnsupportedOperationException uoe) {} // expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Map<String,String> partials = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
protected final Mustache.TemplateLoader partials(Map.Entry<String, String> ... entries) {
|
||||||
|
Map<String,String> templates = new LinkedHashMap<>();
|
||||||
|
for (Entry<String, String> e : entries) {
|
||||||
|
templates.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
partials = templates;
|
||||||
|
return name -> new StringReader(templates.get(name));
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void testPartial () {
|
@Test public void testPartial () {
|
||||||
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
test(Mustache.compiler().withLoader(
|
||||||
public Reader getTemplate (String name) {
|
partials(entry("foo", "inside:{{bar}}"),
|
||||||
if (name.equals("foo")) {
|
entry("baz", "nonfoo")))
|
||||||
return new StringReader("inside:{{bar}}");
|
, "foo inside:foo nonfoo foo", "{{bar}} {{>foo}} {{>baz}} {{bar}}", context("bar", "foo"));
|
||||||
} else {
|
|
||||||
return new StringReader("nonfoo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), "foo inside:foo nonfoo foo", "{{bar}} {{>foo}} {{>baz}} {{bar}}", context("bar", "foo"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testPartialIndent () {
|
@Test public void testPartialIndent () {
|
||||||
@@ -231,15 +257,15 @@ public abstract class SharedTests
|
|||||||
}), "\\\n |\n <\n->\n |\n/\n", "\\\n {{>partial}}\n/\n", context("content", "<\n->"));
|
}), "\\\n |\n <\n->\n |\n/\n", "\\\n {{>partial}}\n/\n", context("content", "<\n->"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @Test */ public void testPartialBlankLines () {
|
@Test public void testPartialBlankLines () {
|
||||||
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
||||||
public Reader getTemplate (String name) {
|
public Reader getTemplate (String name) {
|
||||||
return new StringReader("|\na\n\nb\n|\n");
|
return new StringReader("|\na\n\nb\n|\n");
|
||||||
}
|
}
|
||||||
}), "\\\n\t|\n\ta\n\n\tb\n\t|\n/\n", "\\\n\t{{>partial}}\n/\n", context());
|
}), "\\\n\t|\n\ta\n\t\n\tb\n\t|\n/\n", "\\\n\t{{>partial}}\n/\n", context());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @Test */ public void testNestedPartialBlankLines () {
|
@Test public void testNestedPartialBlankLines () {
|
||||||
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
||||||
public Reader getTemplate (String name) {
|
public Reader getTemplate (String name) {
|
||||||
if (name.equals("partial")) {
|
if (name.equals("partial")) {
|
||||||
@@ -248,10 +274,10 @@ public abstract class SharedTests
|
|||||||
return new StringReader("2\na\n\nb\n2\n");
|
return new StringReader("2\na\n\nb\n2\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), "\\\n\t1\n\t\t2\n\t\ta\n\n\t\tb\n\t\t2\n\t1\n/\n", "\\\n\t{{>partial}}\n/\n", context());
|
}), "\\\n\t1\n\t\t2\n\t\ta\n\t\t\n\t\tb\n\t\t2\n\t1\n/\n", "\\\n\t{{>partial}}\n/\n", context());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @Test */ public void testNestedPartialBlankLinesCRLF () {
|
@Test public void testNestedPartialBlankLinesCRLF () {
|
||||||
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
||||||
public Reader getTemplate (String name) {
|
public Reader getTemplate (String name) {
|
||||||
if (name.equals("partial")) {
|
if (name.equals("partial")) {
|
||||||
@@ -260,19 +286,14 @@ public abstract class SharedTests
|
|||||||
return new StringReader("2\r\na\r\n\r\nb\r\n2\r\n");
|
return new StringReader("2\r\na\r\n\r\nb\r\n2\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), "\\\r\n\t1\r\n\t\t2\r\n\t\ta\r\n\r\n\t\tb\r\n\t\t2\r\n\t1\r\n/\r\n", "\\\r\n\t{{>partial}}\r\n/\r\n", context());
|
}), "\\\r\n\t1\r\n\t\t2\r\n\t\ta\r\n\t\t\r\n\t\tb\r\n\t\t2\r\n\t1\r\n/\r\n", "\\\r\n\t{{>partial}}\r\n/\r\n", context());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @Test */ public void testNestedPartialIndent () {
|
@Test public void testNestedPartialIndent () {
|
||||||
test(Mustache.compiler().withLoader(new Mustache.TemplateLoader() {
|
Mustache.TemplateLoader loader = partials(entry("partial", "1\n {{>nest}}\n1\n"), entry("nest", "2\n{{{content}}}\n2\n"));
|
||||||
public Reader getTemplate (String name) {
|
test(Mustache.compiler().withLoader(loader),
|
||||||
if (name.equals("partial")) {
|
"|\n 1\n 2\n <\n->\n 2\n 1\n|\n",
|
||||||
return new StringReader("1\n {{>nest}}\n1\n");
|
"|\n {{>partial}}\n|\n", context("content", "<\n->"));
|
||||||
} else {
|
|
||||||
return new StringReader("2\n{{{content}}}\n2\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), "|\n 1\n 2\n <\n->\n 2\n 1\n|\n", "|\n {{>partial}}\n|\n", context("content", "<\n->"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testPartialIndentWithVariableAtTheStart () {
|
@Test public void testPartialIndentWithVariableAtTheStart () {
|
||||||
@@ -826,12 +847,40 @@ public abstract class SharedTests
|
|||||||
test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", context("foo", "bar"));
|
test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", context("foo", "bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void test (Mustache.Compiler compiler, String expected, String template, Object ctx) {
|
protected String name;
|
||||||
check(expected, compiler.compile(template).execute(ctx));
|
|
||||||
|
@Rule public TestRule watcher = new TestWatcher() {
|
||||||
|
protected void starting(Description description) {
|
||||||
|
name = description.getDisplayName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void test(Mustache.Compiler compiler, String expected, String template, Object ctx) {
|
||||||
|
String actual = compiler.compile(template).execute(ctx);
|
||||||
|
if (! Objects.equals(expected, actual)) {
|
||||||
|
System.out.println("");
|
||||||
|
System.out.println("----------------------------------------");
|
||||||
|
System.out.println("");
|
||||||
|
System.out.println("Failed: " + name);
|
||||||
|
System.out.println("Template : \"" + SpecTest.showWhitespace(template) + "\"");
|
||||||
|
if (! partials.isEmpty()) {
|
||||||
|
System.out.println("Partials : ");
|
||||||
|
for (Entry<String, String> e : partials.entrySet()) {
|
||||||
|
System.out.println("\t" + e.getKey() + ": \"" + SpecTest.showWhitespace(e.getValue()) + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("Expected : \"" + SpecTest.showWhitespace(expected) + "\"");
|
||||||
|
System.out.println("Result : \"" + SpecTest.showWhitespace(actual) + "\"");
|
||||||
|
System.out.println("----------------------------------------");
|
||||||
|
System.out.println("");
|
||||||
|
}
|
||||||
|
check(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void check (String expected, String output) {
|
protected void check (String expected, String output) {
|
||||||
assertEquals(uncrlf(expected), uncrlf(output));
|
//assertEquals(uncrlf(expected), uncrlf(output));
|
||||||
|
assertEquals(SpecTest.showWhitespace(expected), SpecTest.showWhitespace(output));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void test (String expected, String template, Object ctx) {
|
protected void test (String expected, String template, Object ctx) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.samskivert.mustache.specs;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class CustomSpecTest extends SpecTest {
|
||||||
|
|
||||||
|
public CustomSpecTest(Spec spec, String name) {
|
||||||
|
super(spec, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters(name = "{1}")
|
||||||
|
public static Collection<Object[]> data () {
|
||||||
|
String[] groups = new String[] {
|
||||||
|
"partials"
|
||||||
|
};
|
||||||
|
return SpecTest.data("/custom/specs/", groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.samskivert.mustache.specs;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class OfficialSpecTest extends SpecTest {
|
||||||
|
|
||||||
|
public OfficialSpecTest(Spec spec, String name) {
|
||||||
|
super(spec, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters(name = "{1}")
|
||||||
|
public static Collection<Object[]> data () {
|
||||||
|
String[] groups = new String[] {
|
||||||
|
"comments",
|
||||||
|
"delimiters",
|
||||||
|
"interpolation",
|
||||||
|
"inverted",
|
||||||
|
"sections",
|
||||||
|
"partials"
|
||||||
|
};
|
||||||
|
return SpecTest.data("/specs/specs/", groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
package com.samskivert.mustache.specs;
|
package com.samskivert.mustache.specs;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Yoryos Valotasios
|
* @author Yoryos Valotasios
|
||||||
@@ -26,7 +27,7 @@ public class Spec
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription () {
|
public String getDescription () {
|
||||||
return (String) map.get("descr");
|
return (String) map.get("desc");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTemplate () {
|
public String getTemplate () {
|
||||||
@@ -44,4 +45,20 @@ public class Spec
|
|||||||
public String getPartial (String name) {
|
public String getPartial (String name) {
|
||||||
return partials == null ? null : partials.get(name);
|
return partials == null ? null : partials.get(name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
Consumer<String> value = s -> sb.append("\"").append(s).append("\"").append("\n");
|
||||||
|
Consumer<String> label = s -> sb.append("").append(s).append(": ");
|
||||||
|
label.accept("name");
|
||||||
|
value.accept(getName());
|
||||||
|
label.accept("desc");
|
||||||
|
value.accept(getDescription());
|
||||||
|
label.accept("template");
|
||||||
|
value.accept(getTemplate());
|
||||||
|
label.accept("expected");
|
||||||
|
value.accept(getExpectedOutput());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,10 @@ import com.samskivert.mustache.Mustache;
|
|||||||
public class SpecAwareTemplateLoader implements Mustache.TemplateLoader
|
public class SpecAwareTemplateLoader implements Mustache.TemplateLoader
|
||||||
{
|
{
|
||||||
private static final String EMPTY_STRING = "";
|
private static final String EMPTY_STRING = "";
|
||||||
private Spec spec;
|
private final Spec spec;
|
||||||
|
|
||||||
public void setSpec (Spec spec) {
|
public SpecAwareTemplateLoader(Spec spec) {
|
||||||
|
super();
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,14 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
import org.yaml.snakeyaml.error.YAMLException;
|
import org.yaml.snakeyaml.error.YAMLException;
|
||||||
|
|
||||||
import com.samskivert.mustache.Mustache;
|
import com.samskivert.mustache.Mustache;
|
||||||
import com.samskivert.mustache.Template;
|
import com.samskivert.mustache.Template;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
public abstract class SpecTest {
|
||||||
public class SpecTest {
|
|
||||||
|
|
||||||
private static final Yaml yaml = new Yaml();
|
private static final Yaml yaml = new Yaml();
|
||||||
|
|
||||||
@@ -32,19 +27,11 @@ public class SpecTest {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mustache.Compiler compiler;
|
|
||||||
private static SpecAwareTemplateLoader loader;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp () {
|
|
||||||
loader = new SpecAwareTemplateLoader();
|
|
||||||
compiler = Mustache.compiler().defaultValue("").withLoader(loader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test () throws Exception {
|
public void test () throws Exception {
|
||||||
//System.out.println("Testing: " + name);
|
//System.out.println("Testing: " + name);
|
||||||
loader.setSpec(spec);
|
SpecAwareTemplateLoader loader = new SpecAwareTemplateLoader(spec);
|
||||||
|
Mustache.Compiler compiler = Mustache.compiler().defaultValue("").withLoader(loader);
|
||||||
String tmpl = spec.getTemplate();
|
String tmpl = spec.getTemplate();
|
||||||
String desc = String.format("Template: '''%s'''\nData: '%s'\n",
|
String desc = String.format("Template: '''%s'''\nData: '%s'\n",
|
||||||
uncrlf(tmpl), uncrlf(spec.getData().toString()));
|
uncrlf(tmpl), uncrlf(spec.getData().toString()));
|
||||||
@@ -75,7 +62,7 @@ public class SpecTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String showWhitespace (String s) {
|
public static String showWhitespace (String s) {
|
||||||
s = s.replace("\r\n", "\u240D");
|
s = s.replace("\r\n", "\u240D");
|
||||||
s = s.replace('\t', '\u21E5');
|
s = s.replace('\t', '\u21E5');
|
||||||
s = s.replace("\n", "\u21B5\n");
|
s = s.replace("\n", "\u21B5\n");
|
||||||
@@ -88,20 +75,12 @@ public class SpecTest {
|
|||||||
return (text == null) ? null : text.replace("\r", "\\r").replace("\n", "\\n");
|
return (text == null) ? null : text.replace("\r", "\\r").replace("\n", "\\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parameters(name = "{1}")
|
|
||||||
public static Collection<Object[]> data () {
|
public static Collection<Object[]> data (String specPath, String[] groups) {
|
||||||
String[] groups = new String[] {
|
|
||||||
"comments",
|
|
||||||
"delimiters",
|
|
||||||
"interpolation",
|
|
||||||
"inverted",
|
|
||||||
"sections",
|
|
||||||
"partials"
|
|
||||||
};
|
|
||||||
List<Object[]> tuples = new ArrayList<>();
|
List<Object[]> tuples = new ArrayList<>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (String g : groups) {
|
for (String g : groups) {
|
||||||
Iterable<Spec> specs = getTestsForGroup(g);
|
Iterable<Spec> specs = getTestsForGroup(specPath, g);
|
||||||
for (Spec s : specs) {
|
for (Spec s : specs) {
|
||||||
Object[] tuple = new Object[] {s, g + "-" + s.getName() + "-" + i++};
|
Object[] tuple = new Object[] {s, g + "-" + s.getName() + "-" + i++};
|
||||||
tuples.add(tuple);
|
tuples.add(tuple);
|
||||||
@@ -110,8 +89,13 @@ public class SpecTest {
|
|||||||
return tuples;
|
return tuples;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<Spec> getTestsForGroup (String name) {
|
private static Iterable<Spec> getTestsForGroup (String specPath, String name) {
|
||||||
String ymlPath = "/specs/specs/" + name + ".yml";
|
//String ymlPath = "/specs/specs/" + name + ".yml";
|
||||||
|
String ymlPath = specPath + name + ".yml";
|
||||||
|
return getTestsFromYaml(name, ymlPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Iterable<Spec> getTestsFromYaml(String name, String ymlPath) {
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> map = (Map<String, Object>) yaml.load(SpecTest.class.getResourceAsStream(ymlPath));
|
Map<String, Object> map = (Map<String, Object>) yaml.load(SpecTest.class.getResourceAsStream(ymlPath));
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
overview: |
|
||||||
|
Partial tags are used to expand an external template into the current
|
||||||
|
template.
|
||||||
|
|
||||||
|
The tag's content MUST be a non-whitespace character sequence NOT containing
|
||||||
|
the current closing delimiter.
|
||||||
|
|
||||||
|
This tag's content names the partial to inject. Set Delimiter tags MUST NOT
|
||||||
|
affect the parsing of a partial. The partial MUST be rendered against the
|
||||||
|
context stack local to the tag. If the named partial cannot be found, the
|
||||||
|
empty string SHOULD be used instead, as in interpolations.
|
||||||
|
|
||||||
|
Partial tags SHOULD be treated as standalone when appropriate. If this tag
|
||||||
|
is used standalone, any whitespace preceding the tag should treated as
|
||||||
|
indentation, and prepended to each line of the partial before rendering.
|
||||||
|
tests:
|
||||||
|
|
||||||
|
- name: Nested Partial Indent
|
||||||
|
desc: "Nested partials should including the parent partials indent"
|
||||||
|
data: { content: "<\n->" }
|
||||||
|
template: "|\n {{>partial}}\n|\n"
|
||||||
|
partials:
|
||||||
|
partial: "1\n {{>nest}}\n1\n"
|
||||||
|
nest: "2\n{{{content}}}\n2\n"
|
||||||
|
expected: "|\n 1\n 2\n <\n->\n 2\n 1\n|\n"
|
||||||
|
|
||||||
|
- name: Standalone Indentation
|
||||||
|
desc: Each line of the partial should be indented before rendering.
|
||||||
|
data: { content: "<\n->" }
|
||||||
|
template: |
|
||||||
|
\
|
||||||
|
{{>partial}}
|
||||||
|
/
|
||||||
|
partials:
|
||||||
|
partial: |
|
||||||
|
|
|
||||||
|
{{{content}}}
|
||||||
|
|
|
||||||
|
expected: |
|
||||||
|
\
|
||||||
|
|
|
||||||
|
<
|
||||||
|
->
|
||||||
|
|
|
||||||
|
/
|
||||||
Reference in New Issue
Block a user