Compare commits

..

37 Commits

Author SHA1 Message Date
kpdecker f5289a35ec 1.0.6 2012-07-23 15:40:07 -05:00
kpdecker fc99d90337 Fix loading under node 2012-07-23 13:48:27 -05:00
kpdecker eb509a4b8b Execute mocha test from npm test 2012-07-23 13:47:52 -05:00
kpdecker 09ac8ac24b Make jshint happy(er) 2012-07-23 13:45:53 -05:00
kpdecker adb8486e5f Use suite rather than module for module decl 2012-07-23 13:44:45 -05:00
kpdecker 1e151e2030 Ignore sublime projects 2012-07-23 13:42:38 -05:00
kpdecker fe7d16de91 1.0.6beta 2012-07-23 10:36:13 -05:00
tomhuda 8be16c64f9 Add a .rspec 2012-07-12 10:14:21 -07:00
tomhuda f79af6bfa3 Merge branch 'master' of github.com:wycats/handlebars.js 2012-07-12 10:07:10 -07:00
Yehuda Katz efb1e25690 Update for change in TRR API 2012-07-12 08:15:10 -07:00
Yehuda Katz 36b242a710 Document lookupData 2012-07-05 23:10:39 -07:00
Yehuda Katz 659be5a690 Reduce duplication 2012-07-05 23:08:57 -07:00
Yehuda Katz 46c04fa71e Make deep @data trigger the data option 2012-07-05 23:05:55 -07:00
Yehuda Katz 5e8be14d78 Add support for @index in #each 2012-07-05 22:43:50 -07:00
Yehuda Katz 72e05d623c Add support for @data variables 2012-07-05 22:43:05 -07:00
Yehuda Katz ff1547ea04 Merge pull request #262 from naholyr/patch-1
FIX global leak
2012-06-25 07:40:52 -07:00
Nicolas Chambrier 7a393a972b FIX global leak 2012-06-25 17:38:27 +03:00
tomhuda 92b6c1401a Bump version to RC1 2012-05-28 20:05:55 -07:00
tomhuda 0afc8b58d2 Clean up parser tests and AST printer 2012-05-28 19:06:26 -07:00
tomhuda 1082ec2414 Fix a bug where registers were shared 2012-05-28 19:06:08 -07:00
tomhuda 727eb26cb6 Remove unneeded code and add docs
There were a few operations that are no longer
needed, so remove them. Also document all
operations.
2012-05-28 17:35:21 -07:00
tomhuda 175c6fae0f More cleanup
* Make block and inverse use the main helper path
* Eliminate separate inverse AST node
2012-05-28 17:00:41 -07:00
tomhuda 3486b530be Disambiguate more ahead of time 2012-05-28 14:19:48 -07:00
tomhuda bc5efc1767 Remove unneeded binary opcode stuff
It turns out that we don't need to store the
opcodes in a compact way, so don't bother.
2012-05-28 13:28:51 -07:00
Yehuda Katz 90adaa3cc8 Continue work on cleaning up helpers 2012-05-28 13:03:22 -07:00
Yehuda Katz 8786a6c6e2 Start doing earlier work on helpers 2012-05-28 00:15:52 -07:00
Yehuda Katz 246325085f Simple spec for isolation analysis 2012-05-26 22:17:31 -07:00
Yehuda Katz 68c76c9b2e Clean up the compiler a bit 2012-05-26 22:17:22 -07:00
tomhuda facefe8fed Several improvements to compiled output:
* Eliminate legacy support for an options hash
  that doubles as a function. This prevented us
  from building the hash as a literal, and added
  a bunch of code weight
* Create a new "stack literal" construct, that
  allows an opcode to push a literal expression
  onto the stack. This will not allocate a new
  stack variable, and when popped, will simply
  return the literal expression as a String.
2012-05-26 14:51:41 -07:00
tomhuda 8e70e3b58b Don't assume execution in the top context 2012-05-26 14:47:32 -07:00
Yehuda Katz 730c412925 Merge pull request #218 from SlexAxton/master
Adding using a 'class' variable name as a 'known issue'
2012-05-06 20:53:52 -07:00
Peter Wagenet 2418feaf19 Wrap base Handlebars in a closure to prevent var leakage - fixes #205 2012-05-04 15:27:50 -07:00
Alex Sexton c2591f428b Adding warning in about using 'class' as a variable name. 2012-04-11 21:46:28 -05:00
Peter Wagenet 98b3327c75 Merge pull request #202 from neomorphic/master
vm -> runtime change in README
2012-03-06 07:17:58 -08:00
Jody Clements 992fdf2071 Update README to reflect vm -> runtime change
Looks like one handlebars.vm.js was missed in the README file during the
change to handlebars.runtime.js
2012-03-06 08:32:01 -05:00
tomhuda 24e04bad94 JSHint the library and fix a few bugs 2012-02-17 16:51:35 -08:00
Peter Wagenet e6079ca5e2 Merge pull request #177 from kpdecker/master
Rev npm package to 1.0.5beta
2012-02-09 09:56:22 -08:00
17 changed files with 982 additions and 511 deletions
+2
View File
@@ -4,3 +4,5 @@ vendor
.DS_Store .DS_Store
lib/handlebars/compiler/parser.js lib/handlebars/compiler/parser.js
node_modules node_modules
*.sublime-project
*.sublime-workspace
+52
View File
@@ -0,0 +1,52 @@
{
"predef": [
"console",
"Ember",
"DS",
"Handlebars",
"Metamorph",
"ember_assert",
"ember_warn",
"ember_deprecate",
"ember_deprecateFunc",
"require",
"suite",
"equal",
"equals",
"test",
"testBoth",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module"
],
"node" : true,
"es5" : true,
"browser" : true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false
}
+1
View File
@@ -0,0 +1 @@
-cfs
+9 -9
View File
@@ -4,15 +4,15 @@ GEM
diff-lcs (1.1.3) diff-lcs (1.1.3)
libv8 (3.3.10.4) libv8 (3.3.10.4)
rake (0.9.2.2) rake (0.9.2.2)
rspec (2.7.0) rspec (2.11.0)
rspec-core (~> 2.7.0) rspec-core (~> 2.11.0)
rspec-expectations (~> 2.7.0) rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.7.0) rspec-mocks (~> 2.11.0)
rspec-core (2.7.1) rspec-core (2.11.0)
rspec-expectations (2.7.0) rspec-expectations (2.11.1)
diff-lcs (~> 1.1.2) diff-lcs (~> 1.1.3)
rspec-mocks (2.7.0) rspec-mocks (2.11.1)
therubyracer (0.9.9) therubyracer (0.10.1)
libv8 (~> 3.3.10) libv8 (~> 3.3.10)
PLATFORMS PLATFORMS
+2 -1
View File
@@ -239,7 +239,7 @@ method and the resulting object may be as normal.
### Optimizations ### Optimizations
- Rather than using the full _handlebars.js_ library, implementations that - Rather than using the full _handlebars.js_ library, implementations that
do not need to compile templates at runtime may include _handlebars.vm.js_ do not need to compile templates at runtime may include _handlebars.runtime.js_
whose min+gzip size is approximately 1k. whose min+gzip size is approximately 1k.
- If a helper is known to exist in the target environment they may be defined - If a helper is known to exist in the target environment they may be defined
using the `--known name` argument may be used to optimize accesses to these using the `--known name` argument may be used to optimize accesses to these
@@ -283,6 +283,7 @@ template(context, {helpers: helpers, partials: partials, data: data})
Known Issues Known Issues
------------ ------------
* Handlebars.js can be cryptic when there's an error while rendering. * Handlebars.js can be cryptic when there's an error while rendering.
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
Handlebars in the Wild Handlebars in the Wild
----------------- -----------------
+25 -5
View File
@@ -1,7 +1,11 @@
// BEGIN(BROWSER) // BEGIN(BROWSER)
var Handlebars = {};
Handlebars.VERSION = "1.0.beta.5"; /*jshint eqnull:true*/
this.Handlebars = {};
(function(Handlebars) {
Handlebars.VERSION = "1.0.rc.1";
Handlebars.helpers = {}; Handlebars.helpers = {};
Handlebars.partials = {}; Handlebars.partials = {};
@@ -52,13 +56,27 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
} }
}); });
Handlebars.K = function() {};
Handlebars.createFrame = Object.create || function(object) {
Handlebars.K.prototype = object;
var obj = new Handlebars.K();
Handlebars.K.prototype = null;
return obj;
};
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 = ""; var ret = "", data;
if (options.data) {
data = Handlebars.createFrame(options.data);
}
if(context && context.length > 0) { if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) { for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]); if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
} }
} else { } else {
ret = inverse(this); ret = inverse(this);
@@ -93,7 +111,9 @@ Handlebars.registerHelper('log', function(context) {
Handlebars.log(context); Handlebars.log(context);
}); });
}(this.Handlebars));
// END(BROWSER) // END(BROWSER)
module.exports = Handlebars; module.exports = this.Handlebars;
+32 -12
View File
@@ -11,12 +11,26 @@ var Handlebars = require('./base');
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
}; };
Handlebars.AST.MustacheNode = function(params, hash, unescaped) { Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
this.type = "mustache"; this.type = "mustache";
this.id = params[0];
this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped; this.escaped = !unescaped;
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var eligibleHelper = this.eligibleHelper = id.isSimple;
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = eligibleHelper && (params.length || hash);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
}; };
Handlebars.AST.PartialNode = function(id, context) { Handlebars.AST.PartialNode = function(id, context) {
@@ -34,18 +48,16 @@ var Handlebars = require('./base');
} }
}; };
Handlebars.AST.BlockNode = function(mustache, program, close) { Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
verifyMatch(mustache.id, close); verifyMatch(mustache.id, close);
this.type = "block"; this.type = "block";
this.mustache = mustache; this.mustache = mustache;
this.program = program; this.program = program;
}; this.inverse = inverse;
Handlebars.AST.InverseNode = function(mustache, program, close) { if (this.inverse && !this.program) {
verifyMatch(mustache.id, close); this.isInverse = true;
this.type = "inverse"; }
this.mustache = mustache;
this.program = program;
}; };
Handlebars.AST.ContentNode = function(string) { Handlebars.AST.ContentNode = function(string) {
@@ -75,7 +87,15 @@ var Handlebars = require('./base');
this.parts = dig; this.parts = dig;
this.string = dig.join('.'); this.string = dig.join('.');
this.depth = depth; this.depth = depth;
this.isSimple = (dig.length === 1) && (depth === 0);
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
};
Handlebars.AST.DataNode = function(id) {
this.type = "DATA";
this.id = id;
}; };
Handlebars.AST.StringNode = function(string) { Handlebars.AST.StringNode = function(string) {
File diff suppressed because it is too large Load Diff
+19 -29
View File
@@ -18,31 +18,17 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
}; };
Handlebars.PrintVisitor.prototype.program = function(program) { Handlebars.PrintVisitor.prototype.program = function(program) {
var out = this.pad("PROGRAM:"), var out = "",
statements = program.statements, statements = program.statements,
inverse = program.inverse, inverse = program.inverse,
i, l; i, l;
this.padding++;
for(i=0, l=statements.length; i<l; i++) { for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]); out = out + this.accept(statements[i]);
} }
this.padding--; this.padding--;
if(inverse) {
out = out + this.pad("{{^}}");
this.padding++;
for(i=0, l=inverse.statements.length; i<l; i++) {
out = out + this.accept(inverse.statements[i]);
}
}
this.padding--;
return out; return out;
}; };
@@ -52,25 +38,25 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
out = out + this.pad("BLOCK:"); out = out + this.pad("BLOCK:");
this.padding++; this.padding++;
out = out + this.accept(block.mustache); out = out + this.accept(block.mustache);
out = out + this.accept(block.program); if (block.program) {
out = out + this.pad("PROGRAM:");
this.padding++;
out = out + this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
out = out + this.pad("{{^}}");
this.padding++;
out = out + this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
}
this.padding--; this.padding--;
return out; return out;
}; };
Handlebars.PrintVisitor.prototype.inverse = function(block) {
var out = "";
out = out + this.pad("INVERSE:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.mustache = function(mustache) { Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = [], hash; var params = mustache.params, paramStrings = [], hash;
@@ -125,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
} }
}; };
Handlebars.PrintVisitor.prototype.DATA = function(data) {
return "@" + data.id;
};
Handlebars.PrintVisitor.prototype.content = function(content) { Handlebars.PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]"); return this.pad("CONTENT[ '" + content.string + "' ]");
}; };
+10 -4
View File
@@ -1,7 +1,7 @@
{ {
"name": "handlebars", "name": "handlebars",
"description": "Extension of the Mustache logicless template language", "description": "Extension of the Mustache logicless template language",
"version": "1.0.5beta", "version": "1.0.6",
"homepage": "http://www.handlebarsjs.com/", "homepage": "http://www.handlebarsjs.com/",
"keywords": [ "keywords": [
"handlebars mustache template html" "handlebars mustache template html"
@@ -17,9 +17,15 @@
"optimist": "~0.3", "optimist": "~0.3",
"uglify-js": "~1.2" "uglify-js": "~1.2"
}, },
"devDependencies": {}, "devDependencies": {
"mocha": "*"
},
"main": "lib/handlebars.js", "main": "lib/handlebars.js",
"bin": { "bin": {
"handlebars": "bin/handlebars" "handlebars": "bin/handlebars"
} },
} "scripts": {
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
},
"optionalDependencies": {}
}
+5 -5
View File
@@ -39,11 +39,11 @@ Module.new do
end end
end end
js_context["p"] = proc do |str| js_context["p"] = proc do |this, str|
p str p str
end end
js_context["ok"] = proc do |ok, message| js_context["ok"] = proc do |this, ok, message|
js_context["$$RSPEC1$$"] = ok js_context["$$RSPEC1$$"] = ok
result = js_context.eval("!!$$RSPEC1$$") result = js_context.eval("!!$$RSPEC1$$")
@@ -58,7 +58,7 @@ Module.new do
assert result, message assert result, message
end end
js_context["equals"] = proc do |first, second, message| js_context["equals"] = proc do |this, first, second, message|
js_context["$$RSPEC1$$"] = first js_context["$$RSPEC1$$"] = first
js_context["$$RSPEC2$$"] = second js_context["$$RSPEC2$$"] = second
@@ -77,11 +77,11 @@ Module.new do
js_context["equal"] = js_context["equals"] js_context["equal"] = js_context["equals"]
js_context["module"] = proc do |name| js_context["suite"] = proc do |this, name|
test_context.module(name) test_context.module(name)
end end
js_context["test"] = proc do |name, function| js_context["test"] = proc do |this, name, function|
test_context.test(name, function) test_context.test(name, function)
end end
+46 -32
View File
@@ -7,11 +7,9 @@ describe "Parser" do
@compiles = true @compiles = true
end end
def program(&block) def root(&block)
ASTBuilder.build do ASTBuilder.build do
program do instance_eval(&block)
instance_eval(&block)
end
end end
end end
@@ -112,115 +110,131 @@ describe "Parser" do
"ID:#{id}" "ID:#{id}"
end end
def data(id)
"@#{id}"
end
def path(*parts) def path(*parts)
"PATH:#{parts.join("/")}" "PATH:#{parts.join("/")}"
end end
end end
it "parses simple mustaches" do it "parses simple mustaches" do
ast_for("{{foo}}").should == program { mustache id("foo") } ast_for("{{foo}}").should == root { mustache id("foo") }
end
it "parses simple mustaches with data" do
ast_for("{{@foo}}").should == root { mustache data("foo") }
end end
it "parses mustaches with paths" do it "parses mustaches with paths" do
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") } ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
end end
it "parses mustaches with this/foo" do it "parses mustaches with this/foo" do
ast_for("{{this/foo}}").should == program { mustache id("foo") } ast_for("{{this/foo}}").should == root { mustache id("foo") }
end end
it "parses mustaches with - in a path" do it "parses mustaches with - in a path" do
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") } ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
end end
it "parses mustaches with parameters" do it "parses mustaches with parameters" do
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] } ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
end end
it "parses mustaches with hash arguments" do it "parses mustaches with hash arguments" do
ast_for("{{foo bar=baz}}").should == program do ast_for("{{foo bar=baz}}").should == root do
mustache id("foo"), [], hash(["bar", id("baz")]) mustache id("foo"), [], hash(["bar", id("baz")])
end end
ast_for("{{foo bar=1}}").should == program do ast_for("{{foo bar=1}}").should == root do
mustache id("foo"), [], hash(["bar", integer("1")]) mustache id("foo"), [], hash(["bar", integer("1")])
end end
ast_for("{{foo bar=true}}").should == program do ast_for("{{foo bar=true}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("true")]) mustache id("foo"), [], hash(["bar", boolean("true")])
end end
ast_for("{{foo bar=false}}").should == program do ast_for("{{foo bar=false}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("false")]) mustache id("foo"), [], hash(["bar", boolean("false")])
end end
ast_for("{{foo bar=baz bat=bam}}").should == program do ast_for("{{foo bar=@baz}}").should == root do
mustache id("foo"), [], hash(["bar", data("baz")])
end
ast_for("{{foo bar=baz bat=bam}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"]) mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
end end
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""]) mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
end end
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")]) mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
end end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == program do ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")]) mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
end end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == program do ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")]) mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
end end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == program do ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")]) mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
end end
end end
it "parses mustaches with string parameters" do it "parses mustaches with string parameters" do
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] } ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
end end
it "parses mustaches with INTEGER parameters" do it "parses mustaches with INTEGER parameters" do
ast_for("{{foo 1}}").should == program { mustache id("foo"), [integer("1")] } ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
end end
it "parses mustaches with BOOLEAN parameters" do it "parses mustaches with BOOLEAN parameters" do
ast_for("{{foo true}}").should == program { mustache id("foo"), [boolean("true")] } ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
ast_for("{{foo false}}").should == program { mustache id("foo"), [boolean("false")] } ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
end
it "parses mutaches with DATA parameters" do
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
end end
it "parses contents followed by a mustache" do it "parses contents followed by a mustache" do
ast_for("foo bar {{baz}}").should == program do ast_for("foo bar {{baz}}").should == root do
content "foo bar " content "foo bar "
mustache id("baz") mustache id("baz")
end end
end end
it "parses a partial" do it "parses a partial" do
ast_for("{{> foo }}").should == program { partial id("foo") } ast_for("{{> foo }}").should == root { partial id("foo") }
end end
it "parses a partial with context" do it "parses a partial with context" do
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") } ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
end end
it "parses a comment" do it "parses a comment" do
ast_for("{{! this is a comment }}").should == program do ast_for("{{! this is a comment }}").should == root do
comment " this is a comment " comment " this is a comment "
end end
end end
it "parses a multi-line comment" do it "parses a multi-line comment" do
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
multiline_comment "this is a multi-line comment" multiline_comment "this is a multi-line comment"
end end
end end
it "parses an inverse section" do it "parses an inverse section" do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
block do block do
mustache id("foo") mustache id("foo")
@@ -236,11 +250,11 @@ describe "Parser" do
end end
it "parses a standalone inverse section" do it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == program do ast_for("{{^foo}}bar{{/foo}}").should == root do
inverted_block do block do
mustache id("foo") mustache id("foo")
program do inverse do
content "bar" content "bar"
end end
end end
+219 -127
View File
@@ -1,4 +1,28 @@
module("basic context"); var Handlebars;
if (!Handlebars) {
// Setup for Node package testing
Handlebars = require('../lib/handlebars');
var assert = require("assert"),
equal = assert.equal,
equals = assert.equal,
ok = assert.ok;
// Note that this doesn't have the same context separation as the rspec test.
// Both should be run for full acceptance of the two libary modes.
var CompilerContext = {
compile: function(template, options) {
var templateSpec = Handlebars.precompile(template, options);
return Handlebars.template(eval('(' + templateSpec + ')'));
},
compileWithPartial: function(template, options) {
return Handlebars.compile(template, options);
}
};
}
suite("basic context");
Handlebars.registerHelper('helperMissing', function(helper, context) { Handlebars.registerHelper('helperMissing', function(helper, context) {
if(helper === "link_to") { if(helper === "link_to") {
@@ -6,13 +30,13 @@ Handlebars.registerHelper('helperMissing', function(helper, context) {
} }
}); });
var shouldCompileTo = function(string, hashOrArray, expected, message) { function shouldCompileTo(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message); shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
}; }
var shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) { function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
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]") {
helpers = hashOrArray[1]; var helpers = hashOrArray[1];
if(helpers) { if(helpers) {
for(var prop in Handlebars.helpers) { for(var prop in Handlebars.helpers) {
@@ -27,11 +51,11 @@ var shouldCompileToWithPartials = function(string, hashOrArray, partials, expect
ary = [hashOrArray]; ary = [hashOrArray];
} }
result = template.apply(this, ary); var result = template.apply(this, ary);
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message); equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
}; }
var shouldThrow = function(fn, exception, message) { function shouldThrow(fn, exception, message) {
var caught = false; var caught = false;
try { try {
fn(); fn();
@@ -45,6 +69,9 @@ var shouldThrow = function(fn, exception, message) {
ok(caught, message || null); ok(caught, message || null);
} }
test("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
test("compiling with a basic context", function() { test("compiling with a basic context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
@@ -67,8 +94,8 @@ test("boolean", function() {
}); });
test("zeros", function() { test("zeros", function() {
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0}, shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
"num1: 42, num2: 0"); "num1: 42, num2: 0");
shouldCompileTo("num: {{.}}", 0, "num: 0"); shouldCompileTo("num: {{.}}", 0, "num: 0");
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0"); shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
}); });
@@ -109,13 +136,6 @@ test("functions", function() {
"functions are called and render their output"); "functions are called and render their output");
}); });
test("functions with context argument", function() {
shouldCompileTo("{{awesome frank}}",
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
});
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 (-)");
}); });
@@ -162,12 +182,12 @@ test("this keyword in paths", function() {
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE", shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
"This keyword in paths evaluates to current context"); "This keyword in paths evaluates to current context");
string = "{{#hellos}}{{this/text}}{{/hellos}}" string = "{{#hellos}}{{this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]}; hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths"); shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
}); });
module("inverted sections"); suite("inverted sections");
test("inverted sections with unset value", function() { test("inverted sections with unset value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}"; var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
@@ -187,10 +207,10 @@ test("inverted section with empty set", function() {
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set."); shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
}); });
module("blocks"); suite("blocks");
test("array", function() { test("array", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" var string = "{{#goodbyes}}{{text}}! {{/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"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"Arrays iterate over the contents when not empty"); "Arrays iterate over the contents when not empty");
@@ -201,7 +221,7 @@ test("array", function() {
}); });
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"};
shouldCompileTo(string, hash, "cruel world!", shouldCompileTo(string, hash, "cruel world!",
"Arrays iterate over the contents when not empty"); "Arrays iterate over the contents when not empty");
@@ -215,30 +235,30 @@ test("nested iteration", function() {
}); });
test("block with complex lookup", function() { test("block with complex lookup", function() {
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}" var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]}; var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ", shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
"Templates can access variables in contexts up the stack with relative path syntax"); "Templates can access variables in contexts up the stack with relative path syntax");
}); });
test("helper with complex lookup", function() { test("helper with complex lookup$", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}" var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]}; var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function(prefix) { var helpers = {link: function(prefix) {
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>" return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
}}; }};
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>") shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
}); });
test("helper block with complex lookup expression", function() { test("helper block with complex lookup expression", function() {
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}" var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
var hash = {name: "Alan"}; var hash = {name: "Alan"};
var helpers = {goodbyes: function(fn) { var helpers = {goodbyes: function(options) {
var out = ""; var out = "";
var byes = ["Goodbye", "goodbye", "GOODBYE"]; var byes = ["Goodbye", "goodbye", "GOODBYE"];
for (var i = 0,j = byes.length; i < j; i++) { for (var i = 0,j = byes.length; i < j; i++) {
out += byes[i] + " " + fn(this) + "! "; out += byes[i] + " " + options.fn(this) + "! ";
} }
return out; return out;
}}; }};
@@ -248,8 +268,8 @@ test("helper block with complex lookup expression", function() {
test("helper with complex lookup and nested template", function() { test("helper with complex lookup and nested template", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, fn) { var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>"; return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}}; }};
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>"); shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
}); });
@@ -257,8 +277,8 @@ test("helper with complex lookup and nested template", function() {
test("helper with complex lookup and nested template in VM+Compiler", function() { test("helper with complex lookup and nested template in VM+Compiler", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, fn) { var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>"; return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}}; }};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>"); shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
}); });
@@ -274,22 +294,22 @@ test("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
result = template({world: "world"}, { helpers: {goodbyes: function(fn) { return fn({text: "GOODBYE"}); }}}); var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
equal(result, "GOODBYE! cruel world!", "Block helper executed"); equal(result, "GOODBYE! cruel world!", "Block helper executed");
}); });
test("block helper staying in the same context", function() { test("block helper staying in the same context", function() {
var string = "{{#form}}<p>{{name}}</p>{{/form}}" var string = "{{#form}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
result = template({name: "Yehuda"}, {helpers: {form: function(fn) { return "<form>" + fn(this) + "</form>" } }}); var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context"); equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
}); });
test("block helper should have context in this", function() { test("block helper should have context in this", function() {
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>"; var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
var link = function(fn) { var link = function(options) {
return '<a href="/people/' + this.id + '">' + fn(this) + '</a>'; return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
}; };
var data = { "people": [ var data = { "people": [
{ "name": "Alan", "id": 1 }, { "name": "Alan", "id": 1 },
@@ -304,31 +324,31 @@ test("block helper for undefined value", function() {
}); });
test("block helper passing a new context", function() { test("block helper passing a new context", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}" var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}}); var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved"); equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
}); });
test("block helper passing a complex path context", function() { test("block helper passing a complex path context", function() {
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}" var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}}); var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved"); equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
}); });
test("nested block helpers", function() { test("nested block helpers", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}" var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
result = template({ var result = template({
yehuda: {name: "Yehuda" } yehuda: {name: "Yehuda" }
}, { }, {
helpers: { helpers: {
link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>" }, link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
form: function(context, fn) { return "<form>" + fn(context) + "</form>" } form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
} }
}); });
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed"); equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
@@ -345,7 +365,7 @@ test("block inverted sections with empty arrays", function() {
}); });
test("block helper inverted sections", function() { test("block helper inverted sections", function() {
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}" var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
var list = function(context, options) { var list = function(context, options) {
if (context.length > 0) { if (context.length > 0) {
var out = "<ul>"; var out = "<ul>";
@@ -366,7 +386,7 @@ test("block helper inverted sections", function() {
var rootMessage = { var rootMessage = {
people: [], people: [],
message: "Nobody's here" message: "Nobody's here"
} };
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}"; var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
@@ -377,13 +397,13 @@ test("block helper inverted sections", function() {
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block"); shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
}); });
module("helpers hash"); suite("helpers hash");
test("providing a helpers hash", function() { test("providing a helpers hash", function() {
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!", shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
"helpers hash is available"); "helpers hash is available");
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}], shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
"Goodbye cruel world!", "helpers hash is available inside other blocks"); "Goodbye cruel world!", "helpers hash is available inside other blocks");
}); });
@@ -395,7 +415,7 @@ test("the helpers hash is available is nested contexts", function() {
}); });
module("partials"); suite("partials");
test("basic partials", function() { test("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
@@ -431,9 +451,6 @@ test("rendering undefined partial throws an exception", function() {
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}}");
var string = "Dudes: {{>dude}} {{another_dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
template(); template();
}, Handlebars.Exception, "Should throw exception"); }, Handlebars.Exception, "Should throw exception");
}); });
@@ -462,7 +479,7 @@ test("Partials with literal paths", function() {
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths"); shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
}); });
module("String literal parameters"); suite("String literal parameters");
test("simple literals work", function() { test("simple literals work", function() {
var string = 'Message: {{hello "world" 12 true false}}'; var string = 'Message: {{hello "world" 12 true false}}';
@@ -472,7 +489,7 @@ test("simple literals work", function() {
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; } if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; } if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2; return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
}} }};
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal"); shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
}); });
@@ -485,37 +502,37 @@ test("using a quote in the middle of a parameter raises an error", function() {
test("escaping a String is possible", function(){ test("escaping a String is possible", function(){
var string = 'Message: {{{hello "\\"world\\""}}}'; var string = 'Message: {{{hello "\\"world\\""}}}';
var hash = {} var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }} var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal"); shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
}); });
test("it works with ' marks", function() { test("it works with ' marks", function() {
var string = 'Message: {{{hello "Alan\'s world"}}}'; var string = 'Message: {{{hello "Alan\'s world"}}}';
var hash = {} var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }} var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark"); shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
}); });
module("multiple parameters"); suite("multiple parameters");
test("simple multi-params work", function() { test("simple multi-params work", function() {
var string = 'Message: {{goodbye cruel world}}'; var string = 'Message: {{goodbye cruel world}}';
var hash = {cruel: "cruel", world: "world"} var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }} var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params"); shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
}); });
test("block multi-params work", function() { test("block multi-params work", function() {
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'; var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
var hash = {cruel: "cruel", world: "world"} var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world, fn) { var helpers = {goodbye: function(cruel, world, options) {
return fn({greeting: "Goodbye", adj: cruel, noun: world}); return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
}} }};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params"); shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
}) });
module("safestring"); suite("safestring");
test("constructing a safestring from a string and checking its type", function() { test("constructing a safestring from a string and checking its type", function() {
var safe = new Handlebars.SafeString("testing 1, 2, 3"); var safe = new Handlebars.SafeString("testing 1, 2, 3");
@@ -523,56 +540,56 @@ test("constructing a safestring from a string and checking its type", function()
equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string"); equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
}); });
module("helperMissing"); suite("helperMissing");
test("if a context is not found, helperMissing is used", function() { test("if a context is not found, helperMissing is used", function() {
var string = "{{hello}} {{link_to world}}" var string = "{{hello}} {{link_to world}}";
var context = { hello: "Hello", world: "world" }; var context = { hello: "Hello", world: "world" };
shouldCompileTo(string, context, "Hello <a>world</a>") shouldCompileTo(string, context, "Hello <a>world</a>");
}); });
module("knownHelpers"); suite("knownHelpers");
test("Known helper should render helper", function() { test("Known helper should render helper", function() {
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}}) var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
var result = template({}, {helpers: {hello: function() { return "foo"; }}}); var result = template({}, {helpers: {hello: function() { return "foo"; }}});
equal(result, "foo", "'foo' should === '" + result); equal(result, "foo", "'foo' should === '" + result);
}); });
test("Unknown helper in knownHelpers only mode should be passed as undefined", function() { test("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true}) var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}}); var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
equal(result, "undefined", "'undefined' should === '" + result); equal(result, "undefined", "'undefined' should === '" + result);
}); });
test("Builtin helpers available in knownHelpers only mode", function() { test("Builtin helpers available in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true}) var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
var result = template({}); var result = template({});
equal(result, "bar", "'bar' should === '" + result); equal(result, "bar", "'bar' should === '" + result);
}); });
test("Field lookup works in knownHelpers only mode", function() { test("Field lookup works in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}) var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
var result = template({foo: 'bar'}); var result = template({foo: 'bar'});
equal(result, "bar", "'bar' should === '" + result); equal(result, "bar", "'bar' should === '" + result);
}); });
test("Conditional blocks work in knownHelpers only mode", function() { test("Conditional blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true}) var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: 'baz'}); var result = template({foo: 'baz'});
equal(result, "bar", "'bar' should === '" + result); equal(result, "bar", "'bar' should === '" + result);
}); });
test("Invert blocks work in knownHelpers only mode", function() { test("Invert blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true}) var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: false}); var result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result); equal(result, "bar", "'bar' should === '" + result);
}); });
module("blockHelperMissing"); suite("blockHelperMissing");
test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() { test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
var string = "{{#truthy}}yep{{/truthy}}"; var string = "{{#truthy}}yep{{/truthy}}";
@@ -581,7 +598,7 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
}); });
var teardown; var teardown;
module("built-in helpers", { suite("built-in helpers", {
setup: function(){ teardown = null; }, setup: function(){ teardown = null; },
teardown: function(){ if (teardown) { teardown(); } } teardown: function(){ if (teardown) { teardown(); } }
}); });
@@ -609,18 +626,18 @@ test("if", function() {
test("if with function argument", function() { test("if with function argument", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!"; var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: function() {return true}, world: "world"}, "GOODBYE cruel world!", shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns true"); "if with function shows the contents when function returns true");
shouldCompileTo(string, {goodbye: function() {return this.world}, world: "world"}, "GOODBYE cruel world!", shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns string"); "if with function shows the contents when function returns string");
shouldCompileTo(string, {goodbye: function() {return false}, world: "world"}, "cruel world!", shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns false"); "if with function does not show the contents when returns false");
shouldCompileTo(string, {goodbye: function() {return this.foo}, world: "world"}, "cruel world!", shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns undefined"); "if with function does not show the contents when returns undefined");
}); });
test("each", function() { test("each", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!" var string = "{{#each goodbyes}}{{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"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array argument iterates over the contents when not empty"); "each with array argument iterates over the contents when not empty");
@@ -628,14 +645,24 @@ test("each", function() {
"each with array argument ignores the contents when empty"); "each with array argument ignores the contents when empty");
}); });
test("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}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("log", function() { test("log", function() {
var string = "{{log blah}}" var string = "{{log blah}}";
var hash = { blah: "whee" }; var hash = { blah: "whee" };
var logArg; var logArg;
var originalLog = Handlebars.log; var originalLog = Handlebars.log;
Handlebars.log = function(arg){ logArg = arg; } Handlebars.log = function(arg){ logArg = arg; };
teardown = function(){ Handlebars.log = originalLog; } teardown = function(){ Handlebars.log = originalLog; };
shouldCompileTo(string, hash, "", "log should not display"); shouldCompileTo(string, hash, "", "log should not display");
equals("whee", logArg, "should call log with 'whee'"); equals("whee", logArg, "should call log with 'whee'");
@@ -659,6 +686,71 @@ test("passing in data to a compiled function that expects data - works with help
equals("happy cat", result, "Data output by helper"); equals("happy cat", result, "Data output by helper");
}); });
test("data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{@hello}}");
var result = template({}, { data: { hello: "hello" } });
equals("hello", result, "@foo retrieves template data");
});
var objectCreate = Handlebars.createFrame;
test("deep @foo triggers automatic top-level data", function() {
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
var helpers = objectCreate(Handlebars.helpers);
helpers.let = function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
}
return options.fn(this, { data: frame });
};
var result = template({ foo: true }, { helpers: helpers });
equals("Hello world", result, "Automatic data was triggered");
});
test("parameter data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello @world}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("hash values can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello noun=@world}}");
var helpers = {
hello: function(options) {
return "Hello " + options.hash.noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
var helpers = {
let: function(options) {
for (var prop in options.hash) {
options.data[prop] = options.hash[prop];
}
return options.fn(this);
}
};
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
equals("hello world", result, "data variables are inherited downstream");
});
test("passing in data to a compiled function that expects data - works with helpers in partials", function() { test("passing in data to a compiled function that expects data - works with helpers in partials", function() {
var template = CompilerContext.compile("{{>my_partial}}", {data: true}); var template = CompilerContext.compile("{{>my_partial}}", {data: true});
@@ -693,8 +785,8 @@ test("passing in data to a compiled function that expects data - works with bloc
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true}); var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
var helpers = { var helpers = {
hello: function(fn) { hello: function(options) {
return fn(this); return options.fn(this);
}, },
world: function(options) { world: function(options) {
return options.data.adjective + " world" + (this.exclaim ? "!" : ""); return options.data.adjective + " world" + (this.exclaim ? "!" : "");
@@ -709,8 +801,8 @@ test("passing in data to a compiled function that expects data - works with bloc
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = { var helpers = {
hello: function(fn) { hello: function(options) {
return fn({exclaim: "?"}); return options.fn({exclaim: "?"});
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -725,8 +817,8 @@ test("passing in data to a compiled function that expects data - data is passed
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = { var helpers = {
hello: function(fn, inverse) { hello: function(options) {
return fn.data.accessData + " " + fn({exclaim: "?"}); return options.data.accessData + " " + options.fn({exclaim: "?"});
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -741,8 +833,8 @@ test("you can override inherited data when invoking a helper", function() {
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true}); var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
var helpers = { var helpers = {
hello: function(fn) { hello: function(options) {
return fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} }); return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -758,8 +850,8 @@ test("you can override inherited data when invoking a helper with depth", functi
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true}); var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = { var helpers = {
hello: function(fn) { hello: function(options) {
return fn({exclaim: "?"}, { data: {adjective: "sad"} }); return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -776,14 +868,14 @@ test("helpers take precedence over same-named context properties", function() {
var helpers = { var helpers = {
goodbye: function() { goodbye: function() {
return this.goodbye.toUpperCase(); return this.goodbye.toUpperCase();
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
} }
}; };
var context = { var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye", goodbye: "goodbye",
world: "world" world: "world"
}; };
@@ -792,20 +884,20 @@ test("helpers take precedence over same-named context properties", function() {
equals(result, "GOODBYE cruel WORLD", "Helper executed"); equals(result, "GOODBYE cruel WORLD", "Helper executed");
}); });
test("helpers take precedence over same-named context properties", function() { test("helpers take precedence over same-named context properties$", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}"); var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
var helpers = { var helpers = {
goodbye: function(fn) { goodbye: function(options) {
return this.goodbye.toUpperCase() + fn(this); return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
} }
}; };
var context = { var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye", goodbye: "goodbye",
world: "world" world: "world"
}; };
@@ -820,14 +912,14 @@ test("Scoped names take precedence over helpers", function() {
var helpers = { var helpers = {
goodbye: function() { goodbye: function() {
return this.goodbye.toUpperCase(); return this.goodbye.toUpperCase();
} },
};
var context = {
cruel: function(world) { cruel: function(world) {
return "cruel " + world.toUpperCase(); return "cruel " + world.toUpperCase();
}, },
};
var context = {
goodbye: "goodbye", goodbye: "goodbye",
world: "world" world: "world"
}; };
@@ -840,16 +932,16 @@ test("Scoped names take precedence over block helpers", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}"); var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
var helpers = { var helpers = {
goodbye: function(fn) { goodbye: function(options) {
return this.goodbye.toUpperCase() + fn(this); return this.goodbye.toUpperCase() + options.fn(this);
} },
};
var context = {
cruel: function(world) { cruel: function(world) {
return "cruel " + world.toUpperCase(); return "cruel " + world.toUpperCase();
}, },
};
var context = {
goodbye: "goodbye", goodbye: "goodbye",
world: "world" world: "world"
}; };
@@ -892,8 +984,8 @@ test("helpers can take an optional hash with booleans", function() {
var result = template(context, {helpers: helpers}); var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash"); equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}'); template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
var result = template(context, {helpers: helpers}); result = template(context, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean helper parameter honored"); equals(result, "NOT PRINTING", "Boolean helper parameter honored");
}); });
@@ -927,8 +1019,8 @@ test("block helpers can take an optional hash with booleans", function() {
var result = template({}, {helpers: helpers}); var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored"); equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}'); template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
var result = template({}, {helpers: helpers}); result = template({}, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean hash parameter honored"); equals(result, "NOT PRINTING", "Boolean hash parameter honored");
}); });
@@ -1009,7 +1101,7 @@ test("when inside a block in String mode, .. passes the appropriate context in t
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output"); equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
}); });
module("Regressions") suite("Regressions");
test("GH-94: Cannot read property of undefined", function() { test("GH-94: Cannot read property of undefined", function() {
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]}; var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
@@ -1028,13 +1120,13 @@ test("GH-150: Inverted sections print when they shouldn't", function() {
}); });
test("Mustache man page", function() { test("Mustache man page", function() {
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}" var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
var data = { var data = {
"name": "Chris", "name": "Chris",
"value": 10000, "value": 10000,
"taxed_value": 10000 - (10000 * 0.4), "taxed_value": 10000 - (10000 * 0.4),
"in_ca": true "in_ca": true
} };
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works"); shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
}); });
+7 -7
View File
@@ -47,15 +47,15 @@ module Handlebars
def self.load_helpers(context) def self.load_helpers(context)
context["exports"] = nil context["exports"] = nil
context["p"] = proc do |val| context["p"] = proc do |this, val|
p val if ENV["DEBUG_JS"] p val if ENV["DEBUG_JS"]
end end
context["puts"] = proc do |val| context["puts"] = proc do |this, val|
puts val if ENV["DEBUG_JS"] puts val if ENV["DEBUG_JS"]
end end
context["puts_node"] = proc do |val| context["puts_node"] = proc do |this, val|
puts context["Handlebars"]["PrintVisitor"].new.accept(val) puts context["Handlebars"]["PrintVisitor"].new.accept(val)
puts puts
end end
@@ -82,12 +82,12 @@ module Handlebars
context["CompilerContext"] = {} context["CompilerContext"] = {}
CompilerContext = context["CompilerContext"] CompilerContext = context["CompilerContext"]
CompilerContext["compile"] = proc do |*args| CompilerContext["compile"] = proc do |this, *args|
template, options = args[0], args[1] || nil template, options = args[0], args[1] || nil
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options); templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})")); context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
end end
CompilerContext["compileWithPartial"] = proc do |*args| CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil template, options = args[0], args[1] || nil
FULL_CONTEXT["Handlebars"]["compile"].call(template, options); FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
end end
@@ -108,7 +108,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str| context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level if logger_level <= level
@@ -133,7 +133,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str| context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level if logger_level <= level
+14
View File
@@ -244,6 +244,20 @@ describe "Tokenizer" do
result[2].should be_token("ID", "omg") result[2].should be_token("ID", "omg")
end end
it "tokenizes special @ identifiers" do
result = tokenize("{{ @foo }}")
result.should match_tokens %w( OPEN DATA CLOSE )
result[1].should be_token("DATA", "foo")
result = tokenize("{{ foo @bar }}")
result.should match_tokens %w( OPEN ID DATA CLOSE )
result[2].should be_token("DATA", "bar")
result = tokenize("{{ foo bar=@baz }}")
result.should match_tokens %w( OPEN ID ID EQUALS DATA CLOSE )
result[4].should be_token("DATA", "baz")
end
it "does not time out in a mustache with a single } followed by EOF" do it "does not time out in a mustache with a single } followed by EOF" do
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) } Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
end end
+1
View File
@@ -31,6 +31,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>"@"[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'; }
<mu>[0-9]+/[}\s] { return 'INTEGER'; } <mu>[0-9]+/[}\s] { return 'INTEGER'; }
+5 -2
View File
@@ -18,8 +18,8 @@ statements
; ;
statement statement
: openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3); } : openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3); } | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
| mustache { $$ = $1; } | mustache { $$ = $1; }
| partial { $$ = $1; } | partial { $$ = $1; }
| CONTENT { $$ = new yy.ContentNode($1); } | CONTENT { $$ = new yy.ContentNode($1); }
@@ -58,6 +58,7 @@ inMustache
| path params { $$ = [[$1].concat($2), null]; } | path params { $$ = [[$1].concat($2), null]; }
| path hash { $$ = [[$1], $2]; } | path hash { $$ = [[$1], $2]; }
| path { $$ = [[$1], null]; } | path { $$ = [[$1], null]; }
| DATA { $$ = [[new yy.DataNode($1)], null]; }
; ;
params params
@@ -70,6 +71,7 @@ param
| STRING { $$ = new yy.StringNode($1); } | STRING { $$ = new yy.StringNode($1); }
| INTEGER { $$ = new yy.IntegerNode($1); } | INTEGER { $$ = new yy.IntegerNode($1); }
| BOOLEAN { $$ = new yy.BooleanNode($1); } | BOOLEAN { $$ = new yy.BooleanNode($1); }
| DATA { $$ = new yy.DataNode($1); }
; ;
hash hash
@@ -86,6 +88,7 @@ hashSegment
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; } | ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; } | ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; } | ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
; ;
path path