Sfoglia il codice sorgente

Text passed to higher-order sections unrendered

Fixes #187
tags/0.7.0
Michael Jackson 13 anni fa
parent
commit
be4fd1cc29
8 ha cambiato i file con 133 aggiunte e 93 eliminazioni
  1. +5
    -0
      CHANGES
  2. +52
    -32
      mustache.js
  3. +1
    -1
      test/_files/higher_order_sections.js
  4. +1
    -1
      test/_files/higher_order_sections.txt
  5. +3
    -3
      test/context_test.js
  6. +64
    -49
      test/parse_test.js
  7. +4
    -4
      test/render_test.js
  8. +3
    -3
      test/scanner_test.js

+ 5
- 0
CHANGES Vedi File

@@ -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


+ 52
- 32
mustache.js Vedi File

@@ -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;


+ 1
- 1
test/_files/higher_order_sections.js Vedi File

@@ -3,7 +3,7 @@
helper: "To tinker?",
bolder: function () {
return function (text, render) {
return "<b>" + render(text) + '</b> ' + this.helper;
return text + ' => <b>' + render(text) + '</b> ' + this.helper;
}
}
})

+ 1
- 1
test/_files/higher_order_sections.txt Vedi File

@@ -1 +1 @@
<b>Hi Tater.</b> To tinker?
Hi {{name}}. => <b>Hi Tater.</b> To tinker?

+ 3
- 3
test/context_test.js Vedi File

@@ -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": {


+ 64
- 49
test/parse_test.js Vedi File

@@ -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]);
}


+ 4
- 4
test/render_test.js Vedi File

@@ -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");


+ 3
- 3
test/scanner_test.js Vedi File

@@ -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": {


Loading…
Annulla
Salva