Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix nested partial indentation and custom spec tests #153

Merged
merged 3 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 134 additions & 28 deletions src/main/java/com/samskivert/mustache/Mustache.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,25 +430,31 @@ protected static Template.Segment[] trim (Template.Segment[] segs, boolean top)
if (prevBlank && block.firstLeadsBlank()) {
if (pseg != null) segs[ii-1] = prev.trimTrailBlank();
block.trimFirstBlank();
block._standaloneStart = true;
}
if (nextBlank && block.lastTrailsBlank()) {
block.trimLastBlank();
if (nseg != null) segs[ii+1] = next.trimLeadBlank();
block._standaloneEnd = true;
}
}
// we have to indent partials if there is space before
// they are also standalone...
else if (seg instanceof IncludedTemplateSegment) {
IncludedTemplateSegment include = (IncludedTemplateSegment) seg;
if (prev != null && prevBlank && nextBlank) {
String indent = prev.indent();
include._standalone = true;
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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to remove this comment and add a new one discussing how the previous segment should not be trimmed.

/*
* We trim the end because partials
* follow standalone just like blocks
*/
if (nseg != null) {
if (next != null) {
segs[ii+1] = next.trimLeadBlank();
}
}
Expand All @@ -464,6 +470,59 @@ else if (seg instanceof FauxSegment) {
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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops this should be inverted.

}
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) {
text.insert(0, starts.start1);
if (starts.start2 != NO_CHAR) {
Expand Down Expand Up @@ -801,15 +860,16 @@ protected static void requireSameName (String name1, String name2, int line) {
/** A simple segment that reproduces a string. */
protected static class StringSegment extends Template.Segment {
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 trailBlank >= -1;
_text = text;
_leadBlank = leadBlank;
_trailBlank = trailBlank;
_first = first;
}

public boolean leadsBlank () { return _leadBlank != -1; }
Expand All @@ -818,11 +878,11 @@ public StringSegment (String text, int leadBlank, int trailBlank) {
public StringSegment trimLeadBlank () {
if (_leadBlank == -1) return this;
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 () {
return _trailBlank == -1 ? this : new StringSegment(
_text.substring(0, _trailBlank), _leadBlank, -1);
_text.substring(0, _trailBlank), _leadBlank, -1, _first);
}

/**
Expand All @@ -836,12 +896,16 @@ String indent() {
return _text.substring(_trailBlank);
}

StringSegment indent(String indent, boolean last) {
StringSegment indent(String indent, boolean first, boolean last) {
if (indent.equals("")) {
return this;
}
String reindent = reindent(_text, indent, last);
return new StringSegment(reindent , _leadBlank, _trailBlank);
String reindent = reindent(_text, indent, first, last);
return new StringSegment(reindent, _first);
}

@Override boolean isStandalone() {
return false;
}

@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
Expand All @@ -857,15 +921,18 @@ StringSegment indent(String indent, boolean last) {
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
_leadBlank + "/" + _trailBlank;
}

//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();
StringBuilder sb = new StringBuilder(length);
StringBuilder sb = new StringBuilder(indent.length() + length);
if (first) {
sb.append(indent);
}
for (int i = 0; i < length; i++) {
char c = input.charAt(i);
sb.append(c);
if (c == '\n' && ! ( last && i == length - 1)) {
if (c == '\n' && ( last || i != length - 1) ) {
sb.append(indent);
}
}
Expand All @@ -887,6 +954,7 @@ private static int blankPos (String text, boolean leading, boolean first) {

protected final String _text;
protected final int _leadBlank, _trailBlank;
protected final boolean _first;
}

/** A segment that loads and executes a sub-template. */
Expand Down Expand Up @@ -920,20 +988,30 @@ protected Template getTemplate () {
}
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.
// per the spec however much the partial reference is indendented (leading whitespace)
// is how much the partial content should be indented.
if (indent.equals("")) {
if (indent.equals("") || ! _standalone) {
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 String _name;
private final String _indent;
private Template _template;
protected boolean _standalone = false;
}

/** A helper class for named segments. */
Expand Down Expand Up @@ -970,9 +1048,13 @@ public VariableSegment (String name, int line, Formatter formatter, Escaper esca
visitor.visitVariable(_name);
}
@Override
VariableSegment indent(String indent, boolean last) {
VariableSegment indent(String indent, boolean first, boolean last) {
return this;
}
@Override
boolean isStandalone() {
return false;
}
@Override public String toString () {
return "Var(" + _name + ":" + _line + ")";
}
Expand Down Expand Up @@ -1004,15 +1086,31 @@ protected BlockSegment (String name, Template.Segment[] segs, int line) {
super(name, line);
_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) {
for (Template.Segment seg : _segs) {
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 boolean _standaloneStart = false;
protected boolean _standaloneEnd = false;
}

/** A segment that represents a section. */
Expand All @@ -1021,6 +1119,10 @@ public SectionSegment (Compiler compiler, String name, Template.Segment[] segs,
super(name, segs, line);
_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) {
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
Iterator<?> iter = _comp.collector.toIterator(value);
Expand Down Expand Up @@ -1059,16 +1161,15 @@ public SectionSegment (Compiler compiler, String name, Template.Segment[] segs,
}
}
}
@Override
protected SectionSegment indent(String indent, boolean last) {
@Override protected SectionSegment indent (String indent, boolean first, boolean last) {
if (indent.equals("")) {
return this;
}
Template.Segment[] segs = Template.indentSegs(_segs, indent);
Template.Segment[] segs = indentSegs(_segs, indent);
if (segs == _segs) {
return this;
}
return new SectionSegment(_comp, _name, segs, _line);
return new SectionSegment(this, segs);
}
@Override public String toString () {
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
Expand All @@ -1082,6 +1183,10 @@ public InvertedSegment (Compiler compiler, String name, Template.Segment[] segs,
super(name, segs, line);
_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) {
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
Iterator<?> iter = _comp.collector.toIterator(value);
Expand Down Expand Up @@ -1116,15 +1221,15 @@ public InvertedSegment (Compiler compiler, String name, Template.Segment[] segs,
}
}
@Override
protected InvertedSegment indent(String indent, boolean last) {
protected InvertedSegment indent (String indent, boolean first, boolean last) {
if (indent.equals("")) {
return this;
}
Template.Segment[] segs = Template.indentSegs(_segs, indent);
Template.Segment[] segs = indentSegs(_segs, indent);
if (segs == _segs) {
return this;
}
return new InvertedSegment(_comp, _name, segs, _line);
return new InvertedSegment(this, segs);
}
@Override public String toString () {
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
Expand All @@ -1136,7 +1241,8 @@ protected static class FauxSegment extends Template.Segment {
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
@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"; }
}

Expand Down
47 changes: 23 additions & 24 deletions src/main/java/com/samskivert/mustache/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,33 +176,12 @@ protected Template indent(String indent) {
if (indent.equals("")) {
return this;
}
Segment[] copySegs = indentSegs(_segs, indent);
Segment[] copySegs = Mustache.indentSegs(_segs, indent);
if (copySegs == _segs) {
return this;
}
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 {
for (Segment seg : _segs) {
Expand Down Expand Up @@ -422,8 +401,28 @@ protected static abstract class Segment {
abstract void decompile (Mustache.Delims delims, StringBuilder into);

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) {
try {
Expand Down
Loading