Merge branch 'master' of https://github.com/wycats/handlebars.js
Conflicts: package.json
This commit is contained in:
@@ -6,3 +6,4 @@ lib/handlebars/compiler/parser.js
|
|||||||
node_modules
|
node_modules
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
npm-debug.log
|
||||||
|
|||||||
+7
-6
@@ -19,7 +19,7 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
|
|||||||
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
|
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||||
"{{kids.length}} kids:</p>" +
|
"{{kids.length}} kids:</p>" +
|
||||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||||
var template = Handlebars.compile(source);
|
var template = Handlebars.compile(source);
|
||||||
@@ -112,7 +112,7 @@ instance:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
Handlebars.registerHelper('link_to', function(title, context) {
|
Handlebars.registerHelper('link_to', function(title, context) {
|
||||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
return "<a href='/posts" + context.url + "'>" + title + "!</a>"
|
||||||
});
|
});
|
||||||
|
|
||||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||||
@@ -124,7 +124,7 @@ template(context);
|
|||||||
// Would render:
|
// Would render:
|
||||||
//
|
//
|
||||||
// <ul>
|
// <ul>
|
||||||
// <li><a href='/hello-world'>Post!</a></li>
|
// <li><a href='/posts/hello-world'>Post!</a></li>
|
||||||
// </ul>
|
// </ul>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -137,9 +137,9 @@ gets passed to the helper function.
|
|||||||
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
|
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||||
Handlebars.registerHelper('link', function(context, fn) {
|
Handlebars.registerHelper('link', function(context, options) {
|
||||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
return '<a href="/people/' + this.id + '">' + context.fn(this) + '</a>';
|
||||||
});
|
});
|
||||||
var template = Handlebars.compile(source);
|
var template = Handlebars.compile(source);
|
||||||
|
|
||||||
@@ -220,6 +220,7 @@ Precompile handlebar templates.
|
|||||||
Usage: handlebars template...
|
Usage: handlebars template...
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||||
-f, --output Output File [string]
|
-f, --output Output File [string]
|
||||||
-k, --known Known helpers [string]
|
-k, --known Known helpers [string]
|
||||||
-o, --knownOnly Known helpers only [boolean]
|
-o, --knownOnly Known helpers only [boolean]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require "rubygems"
|
|||||||
require "bundler/setup"
|
require "bundler/setup"
|
||||||
|
|
||||||
def compile_parser
|
def compile_parser
|
||||||
system "jison src/handlebars.yy src/handlebars.l"
|
system "./node_modules/jison/lib/jison/cli-wrapper.js src/handlebars.yy src/handlebars.l"
|
||||||
if $?.success?
|
if $?.success?
|
||||||
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
||||||
file.puts File.read("handlebars.js") + ";"
|
file.puts File.read("handlebars.js") + ";"
|
||||||
@@ -15,11 +15,11 @@ def compile_parser
|
|||||||
end
|
end
|
||||||
|
|
||||||
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||||
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
|
if File.exists?('./node_modules/jison/lib/jison/cli-wrapper.js')
|
||||||
compile_parser
|
compile_parser
|
||||||
else
|
else
|
||||||
puts "Jison is not installed. Trying `npm install jison`."
|
puts "Jison is not installed. Trying `npm install jison`."
|
||||||
sh "npm install jison -g"
|
sh "npm install jison"
|
||||||
compile_parser
|
compile_parser
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,7 +28,8 @@ task :compile => "lib/handlebars/compiler/parser.js"
|
|||||||
|
|
||||||
desc "run the spec suite"
|
desc "run the spec suite"
|
||||||
task :spec => [:release] do
|
task :spec => [:release] do
|
||||||
system "rspec -cfs spec"
|
rc = system "rspec -cfs spec"
|
||||||
|
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
task :default => [:compile, :spec]
|
task :default => [:compile, :spec]
|
||||||
|
|||||||
+10
-2
@@ -12,11 +12,17 @@ var optimist = require('optimist')
|
|||||||
'description': 'Exports amd style (require.js)',
|
'description': 'Exports amd style (require.js)',
|
||||||
'alias': 'amd'
|
'alias': 'amd'
|
||||||
},
|
},
|
||||||
|
'c': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||||
|
'alias': 'commonjs',
|
||||||
|
'default': null
|
||||||
|
},
|
||||||
'h': {
|
'h': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||||
'alias': 'handlebarPath',
|
'alias': 'handlebarPath',
|
||||||
'default': ''
|
'default': ''
|
||||||
},
|
},
|
||||||
'k': {
|
'k': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
@@ -91,6 +97,8 @@ var output = [];
|
|||||||
if (!argv.simple) {
|
if (!argv.simple) {
|
||||||
if (argv.amd) {
|
if (argv.amd) {
|
||||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
||||||
|
} else if (argv.commonjs) {
|
||||||
|
output.push('var Handlebars = require("' + argv.commonjs + '");');
|
||||||
} else {
|
} else {
|
||||||
output.push('(function() {\n');
|
output.push('(function() {\n');
|
||||||
}
|
}
|
||||||
@@ -139,7 +147,7 @@ argv._.forEach(function(template) {
|
|||||||
if (!argv.simple) {
|
if (!argv.simple) {
|
||||||
if (argv.amd) {
|
if (argv.amd) {
|
||||||
output.push('});');
|
output.push('});');
|
||||||
} else {
|
} else if (!argv.commonjs) {
|
||||||
output.push('})();');
|
output.push('})();');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-11
@@ -44,13 +44,10 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
|||||||
return inverse(this);
|
return inverse(this);
|
||||||
} else if(type === "[object Array]") {
|
} else if(type === "[object Array]") {
|
||||||
if(context.length > 0) {
|
if(context.length > 0) {
|
||||||
for(var i=0, j=context.length; i<j; i++) {
|
return Handlebars.helpers.each(context, options);
|
||||||
ret = ret + fn(context[i]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ret = inverse(this);
|
return inverse(this);
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
} else {
|
} else {
|
||||||
return fn(context);
|
return fn(context);
|
||||||
}
|
}
|
||||||
@@ -67,20 +64,33 @@ Handlebars.createFrame = Object.create || function(object) {
|
|||||||
|
|
||||||
Handlebars.registerHelper('each', function(context, options) {
|
Handlebars.registerHelper('each', function(context, options) {
|
||||||
var fn = options.fn, inverse = options.inverse;
|
var fn = options.fn, inverse = options.inverse;
|
||||||
var ret = "", data;
|
var i = 0, ret = "", data;
|
||||||
|
|
||||||
if (options.data) {
|
if (options.data) {
|
||||||
data = Handlebars.createFrame(options.data);
|
data = Handlebars.createFrame(options.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(context && context.length > 0) {
|
if(context && typeof context === 'object') {
|
||||||
for(var i=0, j=context.length; i<j; i++) {
|
if(context instanceof Array){
|
||||||
if (data) { data.index = i; }
|
for(var j = context.length; i<j; i++) {
|
||||||
ret = ret + fn(context[i], { data: data });
|
if (data) { data.index = i; }
|
||||||
|
ret = ret + fn(context[i], { data: data });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(var key in context) {
|
||||||
|
if(context.hasOwnProperty(key)) {
|
||||||
|
if(data) { data.key = key; }
|
||||||
|
ret = ret + fn(context[key], {data: data});
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if(i === 0){
|
||||||
ret = inverse(this);
|
ret = inverse(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -642,7 +642,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
this.context.aliases.functionType = '"function"';
|
this.context.aliases.functionType = '"function"';
|
||||||
|
|
||||||
this.replaceStack(function(current) {
|
this.replaceStack(function(current) {
|
||||||
return "typeof " + current + " === functionType ? " + current + "() : " + current;
|
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -787,7 +787,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
var nextStack = this.nextStack();
|
var nextStack = this.nextStack();
|
||||||
|
|
||||||
this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
|
this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
|
||||||
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
|
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
|
||||||
},
|
},
|
||||||
|
|
||||||
// [invokePartial]
|
// [invokePartial]
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Handlebars.VM = {
|
|||||||
} else if (!Handlebars.compile) {
|
} else if (!Handlebars.compile) {
|
||||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||||
} else {
|
} else {
|
||||||
partials[name] = Handlebars.compile(partial);
|
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||||
return partials[name](context, options);
|
return partials[name](context, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
var Handlebars = require("./base");
|
var Handlebars = require("./base");
|
||||||
|
|
||||||
// BEGIN(BROWSER)
|
// BEGIN(BROWSER)
|
||||||
|
|
||||||
|
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||||
|
|
||||||
Handlebars.Exception = function(message) {
|
Handlebars.Exception = function(message) {
|
||||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||||
|
|
||||||
for (var p in tmp) {
|
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||||
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
|
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||||
|
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.message = tmp.message;
|
|
||||||
};
|
};
|
||||||
Handlebars.Exception.prototype = new Error();
|
Handlebars.Exception.prototype = new Error();
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ Handlebars.SafeString.prototype.toString = function() {
|
|||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var escape = {
|
var escape = {
|
||||||
|
"&": "&",
|
||||||
"<": "<",
|
"<": "<",
|
||||||
">": ">",
|
">": ">",
|
||||||
'"': """,
|
'"': """,
|
||||||
@@ -29,7 +32,7 @@ Handlebars.SafeString.prototype.toString = function() {
|
|||||||
"`": "`"
|
"`": "`"
|
||||||
};
|
};
|
||||||
|
|
||||||
var badChars = /&(?!\w+;)|[<>"'`]/g;
|
var badChars = /[&<>"'`]/g;
|
||||||
var possible = /[&<>"'`]/;
|
var possible = /[&<>"'`]/;
|
||||||
|
|
||||||
var escapeChar = function(chr) {
|
var escapeChar = function(chr) {
|
||||||
|
|||||||
+2
-2
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"description": "Extension of the Mustache logicless template language",
|
"description": "Extension of the Mustache logicless template language",
|
||||||
"version": "1.0.6-2",
|
"version": "1.0.7",
|
||||||
"homepage": "http://www.handlebarsjs.com/",
|
"homepage": "http://www.handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars mustache template html"
|
"handlebars mustache template html"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/kpdecker/handlebars.js.git"
|
"url": "git://github.com/wycats/handlebars.js.git"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.7"
|
"node": ">=0.4.7"
|
||||||
|
|||||||
+68
-7
@@ -33,7 +33,13 @@ Handlebars.registerHelper('helperMissing', function(helper, context) {
|
|||||||
function shouldCompileTo(string, hashOrArray, expected, message) {
|
function shouldCompileTo(string, hashOrArray, expected, message) {
|
||||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
||||||
|
var result = compileWithPartials(string, hashOrArray, partials);
|
||||||
|
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileWithPartials(string, hashOrArray, partials) {
|
||||||
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
||||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
||||||
var helpers = hashOrArray[1];
|
var helpers = hashOrArray[1];
|
||||||
@@ -51,18 +57,31 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me
|
|||||||
ary = [hashOrArray];
|
ary = [hashOrArray];
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = template.apply(this, ary);
|
return template.apply(this, ary);
|
||||||
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldThrow(fn, exception, message) {
|
function shouldThrow(fn, exception, message) {
|
||||||
var caught = false;
|
var caught = false,
|
||||||
|
exType, exMessage;
|
||||||
|
|
||||||
|
if (exception instanceof Array) {
|
||||||
|
exType = exception[0];
|
||||||
|
exMessage = exception[1];
|
||||||
|
} else if (typeof exception === 'string') {
|
||||||
|
exType = Error;
|
||||||
|
exMessage = exception;
|
||||||
|
} else {
|
||||||
|
exType = exception;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (e instanceof exception) {
|
if (e instanceof exType) {
|
||||||
caught = true;
|
if (!exMessage || e.message === exMessage) {
|
||||||
|
caught = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +142,8 @@ test("escaping expressions", function() {
|
|||||||
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||||
"by default expressions should be escaped");
|
"by default expressions should be escaped");
|
||||||
|
|
||||||
|
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||||
|
"escaping should properly handle amperstands");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions returning safestrings shouldn't be escaped", function() {
|
test("functions returning safestrings shouldn't be escaped", function() {
|
||||||
@@ -134,10 +155,14 @@ test("functions returning safestrings shouldn't be escaped", function() {
|
|||||||
test("functions", function() {
|
test("functions", function() {
|
||||||
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
||||||
"functions are called and render their output");
|
"functions are called and render their output");
|
||||||
|
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
||||||
|
"functions are bound to the context");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("paths with hyphens", function() {
|
test("paths with hyphens", function() {
|
||||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
||||||
|
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||||
|
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("nested paths", function() {
|
test("nested paths", function() {
|
||||||
@@ -234,6 +259,16 @@ test("array", function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("array with @index", function() {
|
||||||
|
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||||
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||||
|
|
||||||
|
var template = CompilerContext.compile(string);
|
||||||
|
var result = template(hash);
|
||||||
|
|
||||||
|
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||||
|
});
|
||||||
|
|
||||||
test("empty block", function() {
|
test("empty block", function() {
|
||||||
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||||
@@ -459,14 +494,14 @@ test("rendering undefined partial throws an exception", function() {
|
|||||||
shouldThrow(function() {
|
shouldThrow(function() {
|
||||||
var template = CompilerContext.compile("{{> whatever}}");
|
var template = CompilerContext.compile("{{> whatever}}");
|
||||||
template();
|
template();
|
||||||
}, Handlebars.Exception, "Should throw exception");
|
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rendering template partial in vm mode throws an exception", function() {
|
test("rendering template partial in vm mode throws an exception", function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(function() {
|
||||||
var template = CompilerContext.compile("{{> whatever}}");
|
var template = CompilerContext.compile("{{> whatever}}");
|
||||||
template();
|
template();
|
||||||
}, Handlebars.Exception, "Should throw exception");
|
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rendering function partial in vm mode", function() {
|
test("rendering function partial in vm mode", function() {
|
||||||
@@ -602,6 +637,11 @@ test("Invert blocks work in knownHelpers only mode", function() {
|
|||||||
var result = template({foo: false});
|
var result = template({foo: false});
|
||||||
equal(result, "bar", "'bar' should === '" + result);
|
equal(result, "bar", "'bar' should === '" + result);
|
||||||
});
|
});
|
||||||
|
test("Functions are bound to the context in knownHelpers only mode", function() {
|
||||||
|
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||||
|
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
|
||||||
|
equal(result, "bar", "'bar' should === '" + result);
|
||||||
|
});
|
||||||
|
|
||||||
suite("blockHelperMissing");
|
suite("blockHelperMissing");
|
||||||
|
|
||||||
@@ -610,6 +650,11 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
|
|||||||
var data = { truthy: function() { return true; } };
|
var data = { truthy: function() { return true; } };
|
||||||
shouldCompileTo(string, data, "yep");
|
shouldCompileTo(string, data, "yep");
|
||||||
});
|
});
|
||||||
|
test("lambdas resolved by blockHelperMissing are bound to the context", function() {
|
||||||
|
var string = "{{#truthy}}yep{{/truthy}}";
|
||||||
|
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
|
||||||
|
shouldCompileTo(string, boundData, "");
|
||||||
|
});
|
||||||
|
|
||||||
var teardown;
|
var teardown;
|
||||||
suite("built-in helpers", {
|
suite("built-in helpers", {
|
||||||
@@ -659,6 +704,22 @@ test("each", function() {
|
|||||||
"each with array argument ignores the contents when empty");
|
"each with array argument ignores the contents when empty");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("each with an object and @key", function() {
|
||||||
|
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||||
|
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
|
||||||
|
|
||||||
|
// Object property iteration order is undefined according to ECMA spec,
|
||||||
|
// so we need to check both possible orders
|
||||||
|
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||||
|
var actual = compileWithPartials(string, hash);
|
||||||
|
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
||||||
|
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
||||||
|
|
||||||
|
ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty");
|
||||||
|
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||||
|
"each with object argument ignores the contents when empty");
|
||||||
|
});
|
||||||
|
|
||||||
test("each with @index", function() {
|
test("each with @index", function() {
|
||||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ describe "Tokenizer" do
|
|||||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "supports escaping multiple delimiters" do
|
||||||
|
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
|
||||||
|
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
|
||||||
|
|
||||||
|
result[3].should be_token("CONTENT", " ")
|
||||||
|
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||||
|
result[5].should be_token("CONTENT", "{{baz}}")
|
||||||
|
end
|
||||||
|
|
||||||
it "supports escaping a triple stash" do
|
it "supports escaping a triple stash" do
|
||||||
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
||||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||||
@@ -149,6 +158,18 @@ describe "Tokenizer" do
|
|||||||
result[1].should be_token("COMMENT", " this is a comment ")
|
result[1].should be_token("COMMENT", " this is a comment ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "tokenizes a block comment as 'COMMENT'" do
|
||||||
|
result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
|
||||||
|
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||||
|
result[1].should be_token("COMMENT", " this is a {{comment}} ")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "tokenizes a block comment with whitespace as 'COMMENT'" do
|
||||||
|
result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
|
||||||
|
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||||
|
result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
|
||||||
|
end
|
||||||
|
|
||||||
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
||||||
result = tokenize("{{#foo}}content{{/foo}}")
|
result = tokenize("{{#foo}}content{{/foo}}")
|
||||||
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
||||||
@@ -186,6 +207,12 @@ describe "Tokenizer" do
|
|||||||
result[3].should be_token("STRING", "baz")
|
result[3].should be_token("STRING", "baz")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
|
||||||
|
result = tokenize("{{ foo bar \'baz\' }}")
|
||||||
|
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||||
|
result[3].should be_token("STRING", "baz")
|
||||||
|
end
|
||||||
|
|
||||||
it "tokenizes String params with spaces inside as 'STRING'" do
|
it "tokenizes String params with spaces inside as 'STRING'" do
|
||||||
result = tokenize("{{ foo bar \"baz bat\" }}")
|
result = tokenize("{{ foo bar \"baz bat\" }}")
|
||||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||||
@@ -198,6 +225,12 @@ describe "Tokenizer" do
|
|||||||
result[2].should be_token("STRING", %{bar"baz})
|
result[2].should be_token("STRING", %{bar"baz})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
|
||||||
|
result = tokenize(%|{{ foo 'bar\\'baz' }}|)
|
||||||
|
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
||||||
|
result[2].should be_token("STRING", %{bar'baz})
|
||||||
|
end
|
||||||
|
|
||||||
it "tokenizes numbers" do
|
it "tokenizes numbers" do
|
||||||
result = tokenize(%|{{ foo 1 }}|)
|
result = tokenize(%|{{ foo 1 }}|)
|
||||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||||
|
|||||||
+10
-3
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
%x mu emu
|
%x mu emu com
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
@@ -11,7 +11,13 @@
|
|||||||
|
|
||||||
[^\x00]+ { return 'CONTENT'; }
|
[^\x00]+ { return 'CONTENT'; }
|
||||||
|
|
||||||
<emu>[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; }
|
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||||
|
if(yytext.slice(-1) !== "\\") this.popState();
|
||||||
|
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
|
||||||
|
return 'CONTENT';
|
||||||
|
}
|
||||||
|
|
||||||
|
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
|
||||||
|
|
||||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||||
@@ -20,6 +26,7 @@
|
|||||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||||
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
||||||
|
<mu>"{{!--" { this.popState(); this.begin('com'); }
|
||||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
||||||
<mu>"{{" { return 'OPEN'; }
|
<mu>"{{" { return 'OPEN'; }
|
||||||
|
|
||||||
@@ -31,7 +38,7 @@
|
|||||||
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
||||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||||
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
|
||||||
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
|
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
|
||||||
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
||||||
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
||||||
|
|||||||
Reference in New Issue
Block a user