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

Partials indentation #705

Merged
merged 2 commits into from
Aug 21, 2019
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
48 changes: 42 additions & 6 deletions mustache.js
Original file line number Diff line number Diff line change
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 All @@ -232,6 +248,8 @@
}
}

stripSpace();

// Make sure there are no open sections when we're done.
openSection = sections.pop();

Expand Down Expand Up @@ -418,7 +436,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 +610,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
112 changes: 112 additions & 0 deletions test/partial-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* eslint-disable func-names */
require('./helper');

describe('Partials spec', function () {
beforeEach(function () {
Mustache.clearCache();
});


it('The greater-than operator should expand to the named partial.', function () {
var template = '"{{>text}}"';
var data = {};
var partials = {'text':'from partial'};
var expected = '"from partial"';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('The empty string should be used when the named partial is not found.', function () {
var template = '"{{>text}}"';
var data = {};
var partials = {};
var expected = '""';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('The greater-than operator should operate within the current context.', function () {
var template = '"{{>partial}}"';
var data = {'text':'content'};
var partials = {'partial':'*{{text}}*'};
var expected = '"*content*"';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('The greater-than operator should properly recurse.', function () {
var template = '{{>node}}';
var data = {'content':'X','nodes':[{'content':'Y','nodes':[]}]};
var partials = {'node':'{{content}}<{{#nodes}}{{>node}}{{/nodes}}>'};
var expected = 'X<Y<>>';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('The greater-than operator should not alter surrounding whitespace.', function () {
var template = '| {{>partial}} |';
var data = {};
var partials = {'partial':'\t|\t'};
var expected = '| \t|\t |';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('"\r\n" should be considered a newline for standalone tags.', function () {
var template = '|\r\n{{>partial}}\r\n|';
var data = {};
var partials = {'partial':'>'};
var expected = '|\r\n>|';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('Standalone tags should not require a newline to precede them.', function () {
var template = ' {{>partial}}\n>';
var data = {};
var partials = {'partial':'>\n>'};
var expected = ' >\n >>';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('Superfluous in-tag whitespace should be ignored.', function () {
var template = '|{{> partial }}|';
var data = {'boolean':true};
var partials = {'partial':'[]'};
var expected = '|[]|';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
it('Each line of the partial should be indented before rendering.', function () {
var template = '\\\n {{>partial}}\n/\n';
var data = {
'content': '<\n->'
};
var partials = {
'partial': '|\n{{{content}}}\n|\n'
};
var expected = '\\\n |\n <\n->\n |\n/\n';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});

it('Standalone tags should not require a newline to follow them.', function () {
var template = '>\n {{>partial}}';
var data = {

};
var partials = {
'partial': '>\n>'
};
var expected = '>\n >\n >';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});

it('Whitespace should be left untouched.', function () {
var template = ' {{data}} {{> partial}}\n';
var data = {
'data': '|'
};
var partials = {
'partial': '>\n>'
};
var expected = ' | >\n>\n';
var renderResult = Mustache.render(template, data, partials);
assert.equal(renderResult, expected);
});
});