diff --git a/CHANGES b/CHANGES
index 953420c..cc0f1f8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+= HEAD
+
+ * Fixed a bug with higher-order sections that prevented them from being
+ passed the raw text of the section from the original template.
+
= 0.6.0 / 31 Aug 2012
* Use JavaScript's definition of falsy when determining whether to render an
diff --git a/mustache.js b/mustache.js
index 579a959..16e85ba 100644
--- a/mustache.js
+++ b/mustache.js
@@ -219,24 +219,6 @@ var Mustache;
this._partialCache = {};
};
- Renderer.prototype.compile = function (tokens, tags) {
- if (typeof tokens === "string") {
- tokens = parse(tokens, tags);
- }
-
- var fn = compileTokens(tokens),
- self = this;
-
- return function (view) {
- return fn(Context.make(view), self);
- };
- };
-
- Renderer.prototype.compilePartial = function (name, tokens, tags) {
- this._partialCache[name] = this.compile(tokens, tags);
- return this._partialCache[name];
- };
-
Renderer.prototype.render = function (template, view) {
var fn = this._cache[template];
@@ -248,7 +230,22 @@ var Mustache;
return fn(view);
};
- Renderer.prototype._section = function (name, context, callback) {
+ Renderer.prototype.compile = function (template, tags) {
+ var tokens = parse(template, tags);
+ var render = compileTokens(tokens);
+
+ var self = this;
+ return function (view) {
+ return render(Context.make(view), self, template);
+ };
+ };
+
+ Renderer.prototype.compilePartial = function (name, template, tags) {
+ this._partialCache[name] = this.compile(template, tags);
+ return this._partialCache[name];
+ };
+
+ Renderer.prototype._section = function (name, context, text, callback) {
var value = context.lookup(name);
switch (typeof value) {
@@ -265,15 +262,12 @@ var Mustache;
return value ? callback(context.push(value), this) : "";
case "function":
- // TODO: The text should be passed to the callback plain, not rendered.
- var sectionText = callback(context, this),
- self = this;
-
+ var self = this;
var scopedRender = function (template) {
return self.render(template, context);
};
- return value.call(context.view, sectionText, scopedRender) || "";
+ return value.call(context.view, text, scopedRender) || "";
default:
if (value) {
return callback(context, this);
@@ -321,6 +315,24 @@ var Mustache;
return string;
};
+ /**
+ * Calculates the bounds of the section represented by the given `token` in
+ * the original template by drilling down into nested sections to find the
+ * last token that is part of that section. Returns an array of [start, end].
+ */
+ function sectionBounds(token) {
+ var start = token.end;
+ var end = start;
+
+ var tokens;
+ while ((tokens = token.tokens) && tokens.length) {
+ token = tokens[tokens.length - 1];
+ end = token.end;
+ }
+
+ return [start, end];
+ }
+
/**
* Low-level function that compiles the given `tokens` into a
* function that accepts two arguments: a Context and a
@@ -329,23 +341,28 @@ var Mustache;
*/
function compileTokens(tokens, returnBody) {
var body = ['""'];
- var token, method, escape;
+ var token, escape, bounds, text;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
switch (token.type) {
case "#":
+ bounds = sectionBounds(token);
+ text = "t.slice(" + bounds[0] + ", " + bounds[1] + ")";
+ body.push("r._section(" + quote(token.value) + ", c, " + text + ", function (c, r) {\n" +
+ " " + compileTokens(token.tokens, true) + "\n" +
+ "})");
+ break;
case "^":
- method = (token.type === "#") ? "_section" : "_inverted";
- body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
+ body.push("r._inverted(" + quote(token.value) + ", c, function (c, r) {\n" +
" " + compileTokens(token.tokens, true) + "\n" +
"})");
break;
case "{":
case "&":
case "name":
- escape = token.type === "name" ? "true" : "false";
+ escape = String(token.type === "name");
body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
break;
case ">":
@@ -368,7 +385,7 @@ var Mustache;
}
// For great evil!
- return new Function("c, r", body);
+ return new Function("c, r, t", body);
}
function escapeTags(tags) {
@@ -487,9 +504,10 @@ var Mustache;
nonSpace = false;
};
- var type, value, chr;
+ var start, type, value, chr;
while (!scanner.eos()) {
+ start = scanner.pos;
value = scanner.scanUntil(tagRes[0]);
if (value) {
@@ -502,7 +520,7 @@ var Mustache;
nonSpace = true;
}
- tokens.push({type: "text", value: chr});
+ tokens.push({type: "text", value: chr, start: start, end: scanner.pos});
if (chr === "\n") {
stripSpace(); // Check for whitespace on the current line.
@@ -510,6 +528,8 @@ var Mustache;
}
}
+ start = scanner.pos;
+
// Match the opening tag.
if (!scanner.scan(tagRes[0])) {
break;
@@ -540,7 +560,7 @@ var Mustache;
throw new Error("Unclosed tag at " + scanner.pos);
}
- tokens.push({type: type, value: value});
+ tokens.push({type: type, value: value, start: start, end: scanner.pos});
if (type === "name" || type === "{" || type === "&") {
nonSpace = true;
diff --git a/test/_files/higher_order_sections.js b/test/_files/higher_order_sections.js
index 7b52c0d..bacb0a4 100644
--- a/test/_files/higher_order_sections.js
+++ b/test/_files/higher_order_sections.js
@@ -3,7 +3,7 @@
helper: "To tinker?",
bolder: function () {
return function (text, render) {
- return "" + render(text) + ' ' + this.helper;
+ return text + ' => ' + render(text) + ' ' + this.helper;
}
}
})
diff --git a/test/_files/higher_order_sections.txt b/test/_files/higher_order_sections.txt
index 9db786a..be50ad7 100644
--- a/test/_files/higher_order_sections.txt
+++ b/test/_files/higher_order_sections.txt
@@ -1 +1 @@
-Hi Tater. To tinker?
+Hi {{name}}. => Hi Tater. To tinker?
diff --git a/test/context_test.js b/test/context_test.js
index 71c5830..167b9ca 100644
--- a/test/context_test.js
+++ b/test/context_test.js
@@ -1,6 +1,6 @@
-var assert = require("assert"),
- vows = require("vows"),
- Context = require("./../mustache").Context;
+var assert = require("assert");
+var vows = require("vows");
+var Context = require("./../mustache").Context;
vows.describe("Mustache.Context").addBatch({
"A Context": {
diff --git a/test/parse_test.js b/test/parse_test.js
index 8586a25..c54b57f 100644
--- a/test/parse_test.js
+++ b/test/parse_test.js
@@ -1,63 +1,78 @@
-var assert = require("assert"),
- vows = require("vows"),
- parse = require("./../mustache").parse;
+var assert = require("assert");
+var vows = require("vows");
+var parse = require("./../mustache").parse;
// A map of templates to their expected token output.
var expectations = {
- "{{hi}}" : [ { type: 'name', value: 'hi' } ],
- "{{hi.world}}" : [ { type: 'name', value: 'hi.world' } ],
- "{{hi . world}}" : [ { type: 'name', value: 'hi . world' } ],
- "{{ hi}}" : [ { type: 'name', value: 'hi' } ],
- "{{hi }}" : [ { type: 'name', value: 'hi' } ],
- "{{ hi }}" : [ { type: 'name', value: 'hi' } ],
- "{{{hi}}}" : [ { type: '{', value: 'hi' } ],
- "{{!hi}}" : [ { type: '!', value: 'hi' } ],
- "{{! hi}}" : [ { type: '!', value: 'hi' } ],
- "{{! hi }}" : [ { type: '!', value: 'hi' } ],
- "{{ !hi}}" : [ { type: '!', value: 'hi' } ],
- "{{ ! hi}}" : [ { type: '!', value: 'hi' } ],
- "{{ ! hi }}" : [ { type: '!', value: 'hi' } ],
- "a{{hi}}" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' } ],
- "a {{hi}}" : [ { type: 'text', value: 'a ' }, { type: 'name', value: 'hi' } ],
- " a{{hi}}" : [ { type: 'text', value: ' a' }, { type: 'name', value: 'hi' } ],
- " a {{hi}}" : [ { type: 'text', value: ' a ' }, { type: 'name', value: 'hi' } ],
- "a{{hi}}b" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: 'b' } ],
- "a{{hi}} b" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' b' } ],
- "a{{hi}}b " : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: 'b ' } ],
- "a\n{{hi}} b \n" : [ { type: 'text', value: 'a\n' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' b \n' } ],
- "a\n {{hi}} \nb" : [ { type: 'text', value: 'a\n ' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' \nb' } ],
- "a\n {{!hi}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '!', value: 'hi' }, { type: 'text', value: 'b' } ],
- "a\n{{#a}}{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n{{#a}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n{{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ],
- "a\n{{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ],
- "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ],
- "{{>abc}}" : [ { type: '>', value: 'abc' } ],
- "{{> abc }}" : [ { type: '>', value: 'abc' } ],
- "{{ > abc }}" : [ { type: '>', value: 'abc' } ],
- "{{=<% %>=}}" : [ { type: '=', value: '<% %>' } ],
- "{{= <% %> =}}" : [ { type: '=', value: '<% %>' } ],
- "{{=<% %>=}}<%={{ }}=%>" : [ { type: '=', value: '<% %>' }, { type: '=', value: '{{ }}' } ],
- "{{=<% %>=}}<%hi%>" : [ { type: '=', value: '<% %>' }, { type: 'name', value: 'hi' } ],
- "{{#a}}{{/a}}hi{{#b}}{{/b}}\n" : [ { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'hi' }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: '\n' } ],
- "{{a}}\n{{b}}\n\n{{#c}}\n{{/c}}\n" : [ { type: 'name', value: 'a' }, { type: 'text', value: '\n' }, { type: 'name', value: 'b' }, { type: 'text', value: '\n\n' }, { type: '#', value: 'c', tokens: [] } ],
+ "{{hi}}" : [ [ 'name', 'hi', 0, 6 ] ],
+ "{{hi.world}}" : [ [ 'name', 'hi.world', 0, 12 ] ],
+ "{{hi . world}}" : [ [ 'name', 'hi . world', 0, 14 ] ],
+ "{{ hi}}" : [ [ 'name', 'hi', 0, 7 ] ],
+ "{{hi }}" : [ [ 'name', 'hi', 0, 7 ] ],
+ "{{ hi }}" : [ [ 'name', 'hi', 0, 8 ] ],
+ "{{{hi}}}" : [ [ '{', 'hi', 0, 8 ] ],
+ "{{!hi}}" : [ [ '!', 'hi', 0, 7 ] ],
+ "{{! hi}}" : [ [ '!', 'hi', 0, 8 ] ],
+ "{{! hi }}" : [ [ '!', 'hi', 0, 9 ] ],
+ "{{ !hi}}" : [ [ '!', 'hi', 0, 8 ] ],
+ "{{ ! hi}}" : [ [ '!', 'hi', 0, 9 ] ],
+ "{{ ! hi }}" : [ [ '!', 'hi', 0, 10 ] ],
+ "a{{hi}}" : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ] ],
+ "a {{hi}}" : [ [ 'text', 'a ', 0, 2 ], [ 'name', 'hi', 2, 8 ] ],
+ " a{{hi}}" : [ [ 'text', ' a', 0, 2 ], [ 'name', 'hi', 2, 8 ] ],
+ " a {{hi}}" : [ [ 'text', ' a ', 0, 3 ], [ 'name', 'hi', 3, 9 ] ],
+ "a{{hi}}b" : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', 'b', 7, 8 ] ],
+ "a{{hi}} b" : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', ' b', 7, 9 ] ],
+ "a{{hi}}b " : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', 'b ', 7, 9 ] ],
+ "a\n{{hi}} b \n" : [ [ 'text', 'a\n', 0, 2 ], [ 'name', 'hi', 2, 8 ], [ 'text', ' b \n', 8, 12 ] ],
+ "a\n {{hi}} \nb" : [ [ 'text', 'a\n ', 0, 3 ], [ 'name', 'hi', 3, 9 ], [ 'text', ' \nb', 9, 12 ] ],
+ "a\n {{!hi}} \nb" : [ [ 'text', 'a\n', 0, 3 ], [ '!', 'hi', 3, 10 ], [ 'text', 'b', 10, 13 ] ],
+ "a\n{{#a}}{{/a}}\nb" : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [] ], [ 'text', 'b', 14, 16 ] ],
+ "a\n {{#a}}{{/a}}\nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ 'text', 'b', 15, 17 ] ],
+ "a\n {{#a}}{{/a}} \nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ 'text', 'b', 15, 18 ] ],
+ "a\n{{#a}}\n{{/a}}\nb" : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [] ], [ 'text', 'b', 15, 17 ] ],
+ "a\n {{#a}}\n{{/a}}\nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ 'text', 'b', 16, 18 ] ],
+ "a\n {{#a}}\n{{/a}} \nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ 'text', 'b', 16, 19 ] ],
+ "a\n{{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [] ], [ '#', 'b', 16, 22, [] ], [ 'text', 'b', 29, 31 ] ],
+ "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ '#', 'b', 17, 23, [] ], [ 'text', 'b', 30, 32 ] ],
+ "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}} \nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [] ], [ '#', 'b', 17, 23, [] ], [ 'text', 'b', 30, 33 ] ],
+ "a\n{{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [ [ '#', 'b', 9, 15, [] ] ] ], [ 'text', 'b', 29, 31 ] ],
+ "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [] ] ] ], [ 'text', 'b', 30, 32 ] ],
+ "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}} \nb" : [ [ 'text', 'a\n', 0, 3 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [] ] ] ], [ 'text', 'b', 30, 33 ] ],
+ "{{>abc}}" : [ [ '>', 'abc', 0, 8 ] ],
+ "{{> abc }}" : [ [ '>', 'abc', 0, 10 ] ],
+ "{{ > abc }}" : [ [ '>', 'abc', 0, 11 ] ],
+ "{{=<% %>=}}" : [ [ '=', '<% %>', 0, 11 ] ],
+ "{{= <% %> =}}" : [ [ '=', '<% %>', 0, 13 ] ],
+ "{{=<% %>=}}<%={{ }}=%>" : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ],
+ "{{=<% %>=}}<%hi%>" : [ [ '=', '<% %>', 0, 11 ], [ 'name', 'hi', 11, 17 ] ],
+ "{{#a}}{{/a}}hi{{#b}}{{/b}}\n" : [ [ '#', 'a', 0, 6, [] ], [ 'text', 'hi', 12, 14 ], [ '#', 'b', 14, 20, [] ], [ 'text', '\n', 26, 27 ] ],
+ "{{a}}\n{{b}}\n\n{{#c}}\n{{/c}}\n" : [ [ 'name', 'a', 0, 5 ], [ 'text', '\n', 5, 6 ], [ 'name', 'b', 6, 11 ], [ 'text', '\n\n', 11, 13 ], [ '#', 'c', 13, 19, [] ] ],
"{{#foo}}\n {{#a}}\n {{b}}\n {{/a}}\n{{/foo}}\n"
- : [ { type: "#", value: "foo", tokens: [ { type: "#", value: "a", tokens: [ { type: "text", value: " " }, { type: "name", value: "b" }, { type: "text", value: "\n" } ] } ] } ]
+ : [ [ '#', 'foo', 0, 8, [ [ '#', 'a', 11, 17, [ [ 'text', ' ', 17, 22 ], [ 'name', 'b', 22, 27 ], [ 'text', '\n', 27, 30 ] ] ] ] ] ]
};
+function makeToken(tokenArray) {
+ var token = {
+ type: tokenArray[0],
+ value: tokenArray[1],
+ start: tokenArray[2],
+ end: tokenArray[3]
+ };
+
+ if (tokenArray[4]) {
+ token.tokens = tokenArray[4].map(makeToken);
+ }
+
+ return token;
+}
+
var spec = {};
for (var template in expectations) {
(function (template, tokens) {
spec["knows how to parse " + JSON.stringify(template)] = function () {
- assert.deepEqual(parse(template), tokens);
+ assert.deepEqual(parse(template), tokens.map(makeToken));
};
})(template, expectations[template]);
}
diff --git a/test/render_test.js b/test/render_test.js
index 9638638..859cb71 100644
--- a/test/render_test.js
+++ b/test/render_test.js
@@ -1,7 +1,7 @@
-var fs = require("fs"),
- path = require("path"),
- assert = require("assert"),
- vows = require("vows");
+var fs = require("fs");
+var path = require("path");
+var assert = require("assert");
+var vows = require("vows");
var Mustache = require(path.join(__dirname, "..", "mustache"));
var _files = path.join(__dirname, "_files");
diff --git a/test/scanner_test.js b/test/scanner_test.js
index 7c03a2a..3e9277e 100644
--- a/test/scanner_test.js
+++ b/test/scanner_test.js
@@ -1,6 +1,6 @@
-var assert = require("assert"),
- vows = require("vows"),
- Scanner = require("./../mustache").Scanner;
+var assert = require("assert");
+var vows = require("vows");
+var Scanner = require("./../mustache").Scanner;
vows.describe("Mustache.Scanner").addBatch({
"A Scanner": {