Skip to content

Commit

Permalink
Indent Partials
Browse files Browse the repository at this point in the history
As referenced in this issue:
#562

Partials aren't indented as per the spec:
https://github.com/mustache/spec/blob/master/specs/partials.yml#L13-L15

This adds indentation tracking for partials and applies them to the
first instance of a partial on a line (to not indent inline partials
which would violate a different part of the spec).
  • Loading branch information
kevindew authored and phillipj committed Aug 21, 2019
1 parent 1a244cb commit cadf571
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 11 deletions.
46 changes: 40 additions & 6 deletions mustache.js
Expand Up @@ -49,7 +49,7 @@
* Safe way of detecting whether or not the given thing is a primitive and
* whether it has the given property
*/
function primitiveHasOwnProperty (primitive, propName) {
function primitiveHasOwnProperty (primitive, propName) {
return (
primitive != null
&& typeof primitive !== 'object'
Expand Down Expand Up @@ -114,6 +114,10 @@
* Tokens that are the root node of a subtree contain two more elements: 1) an
* array of tokens in the subtree and 2) the index in the original template at
* which the closing tag for that section begins.
*
* Tokens for partials also contain two more elements: 1) a string value of
* indendation prior to that tag and 2) the index of that tag on that line -
* eg a value of 2 indicates the partial is the third tag on this line.
*/
function parseTemplate (template, tags) {
if (!template)
Expand All @@ -124,6 +128,8 @@
var spaces = []; // Indices of whitespace tokens on the current line
var hasTag = false; // Is there a {{tag}} on the current line?
var nonSpace = false; // Is there a non-space char on the current line?
var indentation = ''; // Tracks indentation for tags that use it
var tagIndex = 0; // Stores a count of number of tags encountered on a line

// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
Expand Down Expand Up @@ -169,6 +175,8 @@

if (isWhitespace(chr)) {
spaces.push(tokens.length);
if (!nonSpace)
indentation += chr;
} else {
nonSpace = true;
}
Expand All @@ -177,8 +185,11 @@
start += 1;

// Check for whitespace on the current line.
if (chr === '\n')
if (chr === '\n') {
stripSpace();
indentation = '';
tagIndex = 0;
}
}
}

Expand Down Expand Up @@ -210,7 +221,12 @@
if (!scanner.scan(closingTagRe))
throw new Error('Unclosed tag at ' + scanner.pos);

token = [ type, value, start, scanner.pos ];
if (type == '>') {
token = [ type, value, start, scanner.pos, indentation, tagIndex ];
} else {
token = [ type, value, start, scanner.pos ];
}
tagIndex++;
tokens.push(token);

if (type === '#' || type === '^') {
Expand Down Expand Up @@ -418,7 +434,7 @@
while (intermediateValue != null && index < names.length) {
if (index === names.length - 1)
lookupHit = (
hasProperty(intermediateValue, names[index])
hasProperty(intermediateValue, names[index])
|| primitiveHasOwnProperty(intermediateValue, names[index])
);

Expand Down Expand Up @@ -592,12 +608,30 @@
return this.renderTokens(token[4], context, partials, originalTemplate);
};

Writer.prototype.indentPartial = function indentPartial (partial, indentation) {
var filteredIndentation = indentation.replace(/[^ \t]/g, '');
var partialByNl = partial.split('\n');
for (var i = 0; i < partialByNl.length; i++) {
if (partialByNl[i].length) {
partialByNl[i] = filteredIndentation + partialByNl[i];
}
}
return partialByNl.join('\n');
};

Writer.prototype.renderPartial = function renderPartial (token, context, partials, tags) {
if (!partials) return;

var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
if (value != null)
return this.renderTokens(this.parse(value, tags), context, partials, value);
if (value != null) {
var tagIndex = token[5];
var indentation = token[4];
var indentedValue = value;
if (tagIndex == 0 && indentation) {
indentedValue = this.indentPartial(value, indentation);
}
return this.renderTokens(this.parse(indentedValue, tags), context, partials, value);
}
};

Writer.prototype.unescapedValue = function unescapedValue (token, context) {
Expand Down
3 changes: 1 addition & 2 deletions test/mustache-spec-test.js
Expand Up @@ -16,8 +16,7 @@ var skipTests = {
],
partials: [
'Standalone Without Previous Line',
'Standalone Without Newline',
'Standalone Indentation'
'Standalone Without Newline'
],
sections: [
'Standalone Without Newline'
Expand Down
9 changes: 6 additions & 3 deletions test/parse-test.js
Expand Up @@ -40,9 +40,12 @@ var expectations = {
'a\n{{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [ [ '#', 'b', 9, 15, [], 16 ] ], 23 ], [ 'text', 'b', 30, 31 ] ],
'a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [], 17 ] ], 24 ], [ 'text', 'b', 31, 32 ] ],
'a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [], 17 ] ], 24 ], [ 'text', 'b', 32, 33 ] ],
'{{>abc}}' : [ [ '>', 'abc', 0, 8 ] ],
'{{> abc }}' : [ [ '>', 'abc', 0, 10 ] ],
'{{ > abc }}' : [ [ '>', 'abc', 0, 11 ] ],
'{{>abc}}' : [ [ '>', 'abc', 0, 8, '', 0 ] ],
'{{> abc }}' : [ [ '>', 'abc', 0, 10, '', 0 ] ],
'{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0 ] ],
' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0 ] ],
' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0 ], [ '>', 'abc', 13, 23, ' ', 1 ] ],
'{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0 ] ],
'{{=<% %>=}}' : [ [ '=', '<% %>', 0, 11 ] ],
'{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ],
'{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ],
Expand Down

0 comments on commit cadf571

Please sign in to comment.