Skip to content

Commit

Permalink
Render function now recognizes a config object argument
Browse files Browse the repository at this point in the history
If an object is provided for what used to be the `tags` argument instead of an array, then the object is treated as a "config" object. A config object can have a `tags` attribute which behaves the same as the `tags` argument, and it can also optionally have an `escape` argument that is used for providing a custom escape function for that render call. The rationale for this addition is that providing an escape function here, when rendering a template, is a safer pattern for specifying an escape function than modifying the global mustache.escape attribute when a project may need to use mustache to render more than just one kind of content, e.g. both HTML and Latex templates in the same project.
  • Loading branch information
pineapplemachine authored and phillipj committed Oct 22, 2020
1 parent 67eb95c commit 9891d4f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 45 deletions.
75 changes: 55 additions & 20 deletions mustache.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,14 +536,25 @@
* also be a function that is used to load partial templates on the fly
* that takes a single argument: the name of the partial.
*
* If the optional `tags` argument is given here it must be an array with two
* If the optional `config` argument is given here, then it should be an
* object with a `tags` attribute or an `escape` attribute or both.
* If an array is passed, then it will be interpreted the same way as
* a `tags` attribute on a `config` object.
*
* The `tags` attribute of a `config` object must be an array with two
* string values: the opening and closing tags used in the template (e.g.
* [ "<%", "%>" ]). The default is to mustache.tags.
*
* The `escape` attribute of a `config` object must be a function which
* accepts a string as input and outputs a safely escaped string.
* If an `escape` function is not provided, then an HTML-safe string
* escaping function is used as the default.
*/
Writer.prototype.render = function render (template, view, partials, tags) {
Writer.prototype.render = function render (template, view, partials, config) {
var tags = this.getConfigTags(config);
var tokens = this.parse(template, tags);
var context = (view instanceof Context) ? view : new Context(view, undefined);
return this.renderTokens(tokens, context, partials, template, tags);
return this.renderTokens(tokens, context, partials, template, config);
};

/**
Expand All @@ -555,7 +566,7 @@
* If the template doesn't use higher-order sections, this argument may
* be omitted.
*/
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) {
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {
var buffer = '';

var token, symbol, value;
Expand All @@ -564,11 +575,11 @@
token = tokens[i];
symbol = token[0];

if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
else if (symbol === '>') value = this.renderPartial(token, context, partials, tags);
if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);
else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);
else if (symbol === '>') value = this.renderPartial(token, context, partials, config);
else if (symbol === '&') value = this.unescapedValue(token, context);
else if (symbol === 'name') value = this.escapedValue(token, context);
else if (symbol === 'name') value = this.escapedValue(token, context, config);
else if (symbol === 'text') value = this.rawValue(token);

if (value !== undefined)
Expand All @@ -578,7 +589,7 @@
return buffer;
};

Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) {
Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {
var self = this;
var buffer = '';
var value = context.lookup(token[1]);
Expand All @@ -593,10 +604,10 @@

if (isArray(value)) {
for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);
}
} else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {
buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);
} else if (isFunction(value)) {
if (typeof originalTemplate !== 'string')
throw new Error('Cannot use higher-order sections without the original template');
Expand All @@ -607,18 +618,18 @@
if (value != null)
buffer += value;
} else {
buffer += this.renderTokens(token[4], context, partials, originalTemplate);
buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);
}
return buffer;
};

Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) {
Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {
var value = context.lookup(token[1]);

// Use JavaScript's definition of falsy. Include empty arrays.
// See https://github.com/janl/mustache.js/issues/186
if (!value || (isArray(value) && value.length === 0))
return this.renderTokens(token[4], context, partials, originalTemplate);
return this.renderTokens(token[4], context, partials, originalTemplate, config);
};

Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {
Expand All @@ -632,8 +643,9 @@
return partialByNl.join('\n');
};

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

var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
if (value != null) {
Expand All @@ -644,7 +656,8 @@
if (tagIndex == 0 && indentation) {
indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);
}
return this.renderTokens(this.parse(indentedValue, tags), context, partials, indentedValue, tags);
var tokens = this.parse(indentedValue, tags);
return this.renderTokens(tokens, context, partials, indentedValue, config);
}
};

Expand All @@ -654,16 +667,38 @@
return value;
};

Writer.prototype.escapedValue = function escapedValue (token, context) {
Writer.prototype.escapedValue = function escapedValue (token, context, config) {
var escape = this.getConfigEscape(config) || mustache.escape;
var value = context.lookup(token[1]);
if (value != null)
return typeof value === 'number' ? String(value) : mustache.escape(value);
return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value);
};

Writer.prototype.rawValue = function rawValue (token) {
return token[1];
};

Writer.prototype.getConfigTags = function getConfigTags (config) {
if (Array.isArray(config)) {
return config;
}
else if (config && typeof config === 'object') {
return config.tags;
}
else {
return undefined;
}
};

Writer.prototype.getConfigEscape = function getConfigEscape (config) {
if (config && typeof config === 'object' && !Array.isArray(config)) {
return config.escape;
}
else {
return undefined;
}
};

var mustache = {
name: 'mustache.js',
version: '4.0.1',
Expand Down Expand Up @@ -716,14 +751,14 @@
* array with two string values: the opening and closing tags used in the
* template (e.g. [ "<%", "%>" ]). The default is to mustache.tags.
*/
mustache.render = function render (template, view, partials, tags) {
mustache.render = function render (template, view, partials, config) {
if (typeof template !== 'string') {
throw new TypeError('Invalid template! Template should be a "string" ' +
'but "' + typeStr(template) + '" was given as the first ' +
'argument for mustache#render(template, view, partials)');
}

return defaultWriter.render(template, view, partials, tags);
return defaultWriter.render(template, view, partials, config);
};

// Export the escaping function so that the user may override it.
Expand Down
Loading

0 comments on commit 9891d4f

Please sign in to comment.