From 6afbf9616272c1154c45da861e5f6162a0b9ed04 Mon Sep 17 00:00:00 2001 From: Luke Blaney Date: Mon, 13 May 2024 23:10:00 +0100 Subject: [PATCH 1/5] Update to latest spec tests Skip new interpolation test and optional inheritance part of the spec --- test/mustache-spec-test.js | 31 +++++++++++++++++++++++++++++++ test/spec | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/test/mustache-spec-test.js b/test/mustache-spec-test.js index 0e5001899..d035048b5 100644 --- a/test/mustache-spec-test.js +++ b/test/mustache-spec-test.js @@ -14,6 +14,9 @@ var skipTests = { inverted: [ 'Standalone Without Newline' ], + interpolation: [ + 'Dotted Names - Context Precedence' + ], partials: [ 'Standalone Without Previous Line', 'Standalone Without Newline' @@ -21,6 +24,34 @@ var skipTests = { sections: [ 'Standalone Without Newline' ], + '~inheritance': [ + 'Default', + 'Variable', + 'Triple Mustache', + 'Sections', + 'Negative Sections', + 'Mustache Injection', + 'Inherit', + 'Overridden content', + 'Data does not override block', + 'Data does not override block default', + 'Overridden parent', + 'Two overridden parents', + 'Override parent with newlines', + 'Inherit indentation', + 'Only one override', + 'Parent template', + 'Recursion', + 'Multi-level inheritance', + 'Multi-level inheritance, no sub child', + 'Text inside parent', + 'Block scope', + 'Standalone parent', + 'Standalone block', + 'Block reindentation', + 'Intrinsic indentation', + 'Nested block reindentation' + ], '~lambdas': [ 'Interpolation', 'Interpolation - Expansion', diff --git a/test/spec b/test/spec index 72233f3ff..7138576e1 160000 --- a/test/spec +++ b/test/spec @@ -1 +1 @@ -Subproject commit 72233f3ffda9e33915fd3022d0a9ebbcce265acd +Subproject commit 7138576e12ff0f05511de77807c2fb3959bb22e3 From a7949acb1132f7181dd7e95d7e569e91b9ce5e27 Mon Sep 17 00:00:00 2001 From: Luke Blaney Date: Mon, 13 May 2024 23:31:43 +0100 Subject: [PATCH 2/5] add support for dynamic names --- mustache.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mustache.js b/mustache.js index ed0cd6d71..08858b65a 100644 --- a/mustache.js +++ b/mustache.js @@ -640,6 +640,10 @@ Writer.prototype.renderPartial = function renderPartial (token, context, partial if (!partials) return; var tags = this.getConfigTags(config); + // Partial names beginning with an asterix are treated as a dynamic name + if (token[1].trim().startsWith('*')) { + token[1] = context.lookup(token[1].trim().substring(1).trim()); + } var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) { var lineHasNonSpace = token[6]; From 71f579fafa72bd7f824a817422e16cfb9f2787b4 Mon Sep 17 00:00:00 2001 From: Luke Blaney Date: Mon, 13 May 2024 23:43:01 +0100 Subject: [PATCH 3/5] add dynamic name example to README --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index ea74fc830..5616bfc29 100644 --- a/README.md +++ b/README.md @@ -419,6 +419,29 @@ Mustache.render(template, view, { }); ``` +Partials can have dynamic names, which begin with `*`. In this case, a value is read from the context and the partial with that value is used. + +For example, this template and partial: + + base.mustache: +

Message of the Day

+ {{> *dayOfWeek}} + + thursday.mustache: + This must be Thursday. I never could get the hang of Thursdays. + +When loaded with the context: +```json +{ + "dayOfWeek": "thursday" +} +``` +will be expanded to: +```html +

Message of the Day

+ This must be Thursday. I never could get the hang of Thursdays. +``` + ### Custom Delimiters Custom delimiters can be used in place of `{{` and `}}` by setting the new values in JavaScript or in templates. From 8cae97f4b8848799852d783da61863dcb2808682 Mon Sep 17 00:00:00 2001 From: Luke Blaney Date: Tue, 14 May 2024 00:24:23 +0100 Subject: [PATCH 4/5] Make dynamic name change compatible with legacy node versions Node 0.10 and 0.12 don't implement the `startsWith` function on strings --- mustache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mustache.js b/mustache.js index 08858b65a..856646436 100644 --- a/mustache.js +++ b/mustache.js @@ -641,7 +641,7 @@ Writer.prototype.renderPartial = function renderPartial (token, context, partial var tags = this.getConfigTags(config); // Partial names beginning with an asterix are treated as a dynamic name - if (token[1].trim().startsWith('*')) { + if (token[1].trim().substring(0, 1) === '*') { token[1] = context.lookup(token[1].trim().substring(1).trim()); } var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; From 675ca3b1de4939709549d19b0e8b977779913c2a Mon Sep 17 00:00:00 2001 From: Luke Blaney Date: Sat, 1 Jun 2024 13:48:52 +0100 Subject: [PATCH 5/5] Handle dynamic names in parsing, so that they don't get cached non-dynamically --- mustache.js | 35 +++++++++++++++++++++++++++++------ test/parse-test.js | 4 ++++ test/partial-test.js | 14 ++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/mustache.js b/mustache.js index 856646436..42b5fd36e 100644 --- a/mustache.js +++ b/mustache.js @@ -79,6 +79,7 @@ var spaceRe = /\s+/; var equalsRe = /\s*=/; var curlyRe = /\s*\}/; var tagRe = /#|\^|\/|>|\{|&|=|!/; +var dynamicRe = /\*/; /** * Breaks up the given `template` string into a tree of tokens. If the `tags` @@ -202,6 +203,14 @@ function parseTemplate (template, tags) { scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = '&'; + } else if (type === '>') { + if (scanner.scan(dynamicRe) === '') { + value = scanner.scanUntil(closingTagRe); + } else { + scanner.scan(whiteRe); + type = '>*'; + value = scanner.scanUntil(closingTagRe); + } } else { value = scanner.scanUntil(closingTagRe); } @@ -210,7 +219,7 @@ function parseTemplate (template, tags) { if (!scanner.scan(closingTagRe)) throw new Error('Unclosed tag at ' + scanner.pos); - if (type == '>') { + if (type == '>' || type == '>*') { token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ]; } else { token = [ type, value, start, scanner.pos ]; @@ -571,6 +580,7 @@ Writer.prototype.renderTokens = function renderTokens (tokens, context, partials 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.renderDynamicPartial(token, context, partials, config); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context, config); else if (symbol === 'text') value = this.rawValue(token); @@ -636,14 +646,27 @@ Writer.prototype.indentPartial = function indentPartial (partial, indentation, l return partialByNl.join('\n'); }; -Writer.prototype.renderPartial = function renderPartial (token, context, partials, config) { +Writer.prototype.renderDynamicPartial = function renderPartial (token, context, partials, config) { if (!partials) return; var tags = this.getConfigTags(config); - - // Partial names beginning with an asterix are treated as a dynamic name - if (token[1].trim().substring(0, 1) === '*') { - token[1] = context.lookup(token[1].trim().substring(1).trim()); + var name = context.lookup(token[1].trim()); + var value = isFunction(partials) ? partials(name) : partials[name]; + if (value != null) { + var lineHasNonSpace = token[6]; + var tagIndex = token[5]; + var indentation = token[4]; + var indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + var tokens = this.parse(indentedValue, tags); + return this.renderTokens(tokens, context, partials, indentedValue, config); } +}; + +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) { var lineHasNonSpace = token[6]; diff --git a/test/parse-test.js b/test/parse-test.js index 1fb2cabe4..547520b7a 100644 --- a/test/parse-test.js +++ b/test/parse-test.js @@ -46,6 +46,10 @@ var expectations = { ' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ] ], ' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ], [ '>', 'abc', 13, 23, ' ', 1, false ] ], '{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0, false ] ], + '{{>*abc}}' : [ [ '>*', 'abc', 0, 9, '', 0, false ] ], + '{{> *abc}}' : [ [ '>*', 'abc', 0, 10, '', 0, false ] ], + '{{>* abc}}' : [ [ '>*', 'abc', 0, 10, '', 0, false ] ], + '{{ > * abc }}' : [ [ '>*', 'abc', 0, 13, '', 0, false ] ], '{{=<% %>=}}' : [ [ '=', '<% %>', 0, 11 ] ], '{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ], '{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ], diff --git a/test/partial-test.js b/test/partial-test.js index 54d62b581..55f8d0de0 100644 --- a/test/partial-test.js +++ b/test/partial-test.js @@ -172,4 +172,18 @@ describe('Partials spec', function () { var renderResult = Mustache.render(template, {}, partials, tags); assert.equal(renderResult, expected); }); + + describe('when rendering a dynamically named partial after already having rendered that partial with a different name value', function () { + it('returns different output for the latter render', function () { + var template = 'Place: {{>*place}}'; + var partials = { + first: '1st', + second: '2nd', + }; + var renderedFirst = Mustache.render(template, {place:'first'}, partials); + var renderedSecond = Mustache.render(template, {place:'second'}, partials); + + assert.notEqual(renderedFirst, renderedSecond); + }); + }); });