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

Mustache specs - method interpolation compliance #305

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Expand Up @@ -43,7 +43,7 @@ There are several types of tags available in mustache.js.

### Variables

The most basic tag type is a simple variable. A `{{name}}` tag renders the value of the `name` key in the current context. If there is no such key, nothing is rendered.
The most basic tag type is a simple variable. A `{{name}}` tag renders the value of the `name` key in the context of its containing object. If there is no such key, nothing is rendered.

All variables are HTML-escaped by default. If you want to render unescaped HTML, use the triple mustache: `{{{name}}}`. You can also use `&` to unescape a variable.

Expand Down Expand Up @@ -170,7 +170,7 @@ Output:
* Porthos
* D'Artagnan

If the value of a section variable is a function, it will be called in the context of the current item in the list on each iteration.
If the value of a section variable is a function, it will be called in the context of the current item in the list on each iteration. The return value of the function will be rendered in the context of the current item.

View:

Expand All @@ -182,7 +182,9 @@ View:
{ "firstName": "Ringo", "lastName": "Starr" }
],
"name": function () {
return this.firstName + " " + this.lastName;
return function () {
return "{{firstName}} {{lastName}}";
};
}
}

Expand All @@ -201,7 +203,7 @@ Output:

#### Functions

If the value of a section key is a function, it is called with the section's literal block of text, un-rendered, as its first argument. The second argument is a special rendering function that uses the current view as its view argument. It is called in the context of the current view object.
If the value of a section key is a function, it is called with the section's literal block of text, un-rendered, as its first argument. The second argument is a special rendering function that uses the current view as its view argument. It is called in the context of the current view object. The return value of the function will be rendered in the context of the current item.

View:

Expand Down
40 changes: 26 additions & 14 deletions mustache.js
Expand Up @@ -141,35 +141,43 @@
};

Context.prototype.lookup = function (name) {
var value = this._cache[name];
var value = this._cache[name], view = this.view;

if (!value) {
if (name == '.') {
value = this.view;
value = view;
} else {
var context = this;
var names = name.split('.');

// Walk the context stack from top to bottom, finding the first
// context that contains the first part of the name
while (context) {
if (name.indexOf('.') > 0) {
value = context.view;
var names = name.split('.'), i = 0;
while (value && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
view = context.view;
value = view[names[0]];

if (value != null) break;

context = context.parent;
}

var i = 1;
// If there are more name parts, resolve them in the context
// from the former resolution.
while (value && i < names.length) {
// Call methods in the context of their object
if (typeof value === 'function') value = value.call(view);
view = value;
value = view[names[i++]];
}
}

this._cache[name] = value;
}

if (typeof value === 'function') value = value.call(this.view);
// Call methods in the context of their object and pass the lookup context
// as a parameter.
if (typeof value === 'function') value = value.call(view, this.view);

return value;
};
Expand Down Expand Up @@ -258,9 +266,9 @@
} else if (typeof value === 'function') {
var text = template == null ? null : template.slice(token[3], token[5]);
value = value.call(context.view, text, function (template) {
return writer.render(template, context);
return writer.render('' + template, context);
});
if (value != null) buffer += value;
if (value != null) buffer += writer.render('' + value, context);
} else if (value) {
buffer += renderTokens(token[4], writer, context, template);
}
Expand All @@ -282,10 +290,14 @@
break;
case '&':
value = context.lookup(tokenValue);
if (typeof value === 'function')
value = writer.render('' + value.call(context.view), context);
if (value != null) buffer += value;
break;
case 'name':
value = context.lookup(tokenValue);
if (typeof value === 'function')
value = writer.render('' + value.call(context.view), context);
if (value != null) buffer += exports.escape(value);
break;
case 'text':
Expand Down
4 changes: 2 additions & 2 deletions test/_files/complex.js
Expand Up @@ -7,8 +7,8 @@
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
],
link: function () {
return this["current"] !== true;
link: function (ctx) {
return ctx["current"] !== true;
},
list: function () {
return this.item.length !== 0;
Expand Down
2 changes: 1 addition & 1 deletion test/_files/higher_order_sections.js
Expand Up @@ -3,7 +3,7 @@
helper: "To tinker?",
bolder: function () {
return function (text, render) {
return text + ' => <b>' + render(text) + '</b> ' + this.helper;
return '<b>' + render(text) + '</b> ' + this.helper;
}
}
})
2 changes: 1 addition & 1 deletion test/_files/higher_order_sections.txt
@@ -1 +1 @@
Hi {{name}}. => <b>Hi Tater.</b> To tinker?
<b>Hi Tater.</b> To tinker?
16 changes: 16 additions & 0 deletions test/_files/list_example.js
@@ -0,0 +1,16 @@
({
"beatles": [
{ "firstName": "John", "lastName": "Lennon" },
{ "firstName": "Paul", "lastName": "McCartney" },
{ "firstName": "George", "lastName": "Harrison" },
{ "firstName": "Ringo", "lastName": "Starr" }
],
"name": function () {
return function () {
return "{{firstName}} {{lastName}}";
};
},
"name2": function (ctx) {
return ctx.firstName + " " + ctx.lastName + " " + this.beatles.length;
}
})
7 changes: 7 additions & 0 deletions test/_files/list_example.mustache
@@ -0,0 +1,7 @@
{{#beatles}}
* {{name}}
{{/beatles}}

{{#beatles}}
* {{name2}}
{{/beatles}}
9 changes: 9 additions & 0 deletions test/_files/list_example.txt
@@ -0,0 +1,9 @@
* John Lennon
* Paul McCartney
* George Harrison
* Ringo Starr

* John Lennon 4
* Paul McCartney 4
* George Harrison 4
* Ringo Starr 4
15 changes: 15 additions & 0 deletions test/_files/method_interpolation.js
@@ -0,0 +1,15 @@
({
a: function() { return {
b: function() { return {
c: function() { return 'a.b.c'; }
};}
};},
d: {
_p: 'd.e',
e: function() { return this._p; },
f: {
_p: 'd.f.g',
g: function() { return this._p; }
}
}
})
3 changes: 3 additions & 0 deletions test/_files/method_interpolation.mustache
@@ -0,0 +1,3 @@
a.b.c == {{a.b.c}}
d.e == {{d.e}}
d.f.g == {{d.f.g}}
3 changes: 3 additions & 0 deletions test/_files/method_interpolation.txt
@@ -0,0 +1,3 @@
a.b.c == a.b.c
d.e == d.e
d.f.g == d.f.g
10 changes: 4 additions & 6 deletions test/mustache-spec-test.js
Expand Up @@ -4,6 +4,10 @@ var fs = require('fs');
var path = require('path');
var specsDir = path.join(__dirname, 'spec/specs');

// need to define globals used in ~lambdas - Interpolation - Multiple Calls
// otherwise mocha complains
g=null, calls=null;

var skipTests = {
comments: [
'Standalone Without Newline'
Expand All @@ -23,12 +27,6 @@ var skipTests = {
'Standalone Without Newline'
],
'~lambdas': [
'Interpolation',
'Interpolation - Expansion',
'Interpolation - Alternate Delimiters',
'Interpolation - Multiple Calls',
'Escaping',
'Section - Expansion',
'Section - Alternate Delimiters'
]
};
Expand Down