Add Hash arguments to simple mustaches (TODO: add Hash args to block helpers)

This commit is contained in:
tomhuda
2011-03-04 00:11:03 -08:00
parent e0aa705f71
commit ca9b9671a5
6 changed files with 105 additions and 48 deletions
+2 -1
View File
@@ -11,10 +11,11 @@ var Handlebars = require("handlebars");
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
}; };
Handlebars.AST.MustacheNode = function(params, unescaped) { Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
this.type = "mustache"; this.type = "mustache";
this.id = params[0]; this.id = params[0];
this.params = params.slice(1); this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped; this.escaped = !unescaped;
}; };
+5 -2
View File
@@ -73,14 +73,17 @@ Handlebars.PrintVisitor.prototype.inverse = function(block) {
Handlebars.PrintVisitor.prototype.mustache = function(mustache) { Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = []; var params = mustache.params, paramStrings = [], hash;
for(var i=0, l=params.length; i<l; i++) { for(var i=0, l=params.length; i<l; i++) {
paramStrings.push(this.accept(params[i])); paramStrings.push(this.accept(params[i]));
} }
params = "[" + paramStrings.join(", ") + "]"; params = "[" + paramStrings.join(", ") + "]";
return this.pad("{{ " + this.accept(mustache.id) + " " + params + " }}");
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
}; };
Handlebars.PrintVisitor.prototype.partial = function(partial) { Handlebars.PrintVisitor.prototype.partial = function(partial) {
+47 -6
View File
@@ -19,7 +19,8 @@ Handlebars.JavaScriptCompiler = function() {};
invokeProgram: 11, invokeProgram: 11,
invokePartial: 12, invokePartial: 12,
push: 13, push: 13,
invokeInverse: 14 invokeInverse: 14,
assignToHash: 15
}; };
Compiler.MULTI_PARAM_OPCODES = { Compiler.MULTI_PARAM_OPCODES = {
@@ -34,7 +35,8 @@ Handlebars.JavaScriptCompiler = function() {};
invokeProgram: 2, invokeProgram: 2,
invokePartial: 1, invokePartial: 1,
push: 1, push: 1,
invokeInverse: 1 invokeInverse: 1,
assignToHash: 1
}; };
Compiler.DISASSEMBLE_MAP = {}; Compiler.DISASSEMBLE_MAP = {};
@@ -163,6 +165,20 @@ Handlebars.JavaScriptCompiler = function() {};
this.opcode('append'); this.opcode('append');
}, },
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('push', '{}');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
this.accept(val);
this.opcode('assignToHash', pair[0]);
}
},
partial: function(partial) { partial: function(partial) {
var id = partial.id; var id = partial.id;
this.usePartial = true; this.usePartial = true;
@@ -185,6 +201,13 @@ Handlebars.JavaScriptCompiler = function() {};
var params = mustache.params; var params = mustache.params;
this.pushParams(params); this.pushParams(params);
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('push', '{}');
}
this.ID(mustache.id); this.ID(mustache.id);
this.opcode('invokeMustache', params.length, mustache.id.original); this.opcode('invokeMustache', params.length, mustache.id.original);
@@ -466,11 +489,19 @@ Handlebars.JavaScriptCompiler = function() {};
fn = this.popStack(); fn = this.popStack();
var hash = this.popStack();
for(var i=0; i<paramSize; i++) { for(var i=0; i<paramSize; i++) {
params.push(this.popStack()); params.push(this.popStack());
} }
if(this.data) { params.push("data"); } this.register('tmp1', '{hash: ' + hash + '}');
if(this.data) {
this.source.push('tmp1.data = data');
}
params.push('tmp1');
var paramString = params.join(", "); var paramString = params.join(", ");
var helperMissing = ["context"].concat(this.quotedString(original)).concat(params.slice(1)); var helperMissing = ["context"].concat(this.quotedString(original)).concat(params.slice(1));
@@ -479,7 +510,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(paramSize === 0) { if(paramSize === 0) {
// TODO: This case is not listed in the mustache spec. Is it important? // TODO: This case is not listed in the mustache spec. Is it important?
this.source.push("if(typeof " + fn + " === 'function') { " + slot + " = " + fn + ".call(" + paramString + "); }"); this.source.push("if(typeof " + fn + " === 'function') { " + slot + " = " + fn + ".call(" + paramString + "); } else { " + slot + " = " + fn + "; }");
} else { } else {
this.source.push("if(typeof " + fn + " === 'function') { " + slot + " = " + fn + ".call(" + paramString + "); } else { " + slot + " = helpers.helperMissing.call(" + helperMissing + ") }"); this.source.push("if(typeof " + fn + " === 'function') { " + slot + " = " + fn + ".call(" + paramString + "); } else { " + slot + " = helpers.helperMissing.call(" + helperMissing + ") }");
} }
@@ -506,8 +537,11 @@ Handlebars.JavaScriptCompiler = function() {};
blockMissingParams.push(mainProgram, inverse); blockMissingParams.push(mainProgram, inverse);
if(this.data) { if(this.data) {
params.push("data"); this.register('tmp1', '{data: data}');
blockMissingParams.push("data"); params.push("tmp1");
params.push("tmp1");
blockMissingParams.push("tmp1");
} }
var nextStack = this.nextStack(); var nextStack = this.nextStack();
@@ -527,6 +561,13 @@ Handlebars.JavaScriptCompiler = function() {};
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);"); this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
}, },
assignToHash: function(key) {
var value = this.popStack();
var hash = this.topStack();
this.source.push(hash + "['" + key + "'] = " + value + ";");
},
// HELPERS // HELPERS
compiler: JavaScriptCompiler, compiler: JavaScriptCompiler,
+9 -8
View File
@@ -65,8 +65,9 @@ describe "Parser" do
with_padding { yield } with_padding { yield }
end end
def mustache(id, *params) def mustache(id, params = [], hash = nil)
pad("{{ #{id} [#{params.join(", ")}] }}") hash = " #{hash}" if hash
pad("{{ #{id} [#{params.join(", ")}]#{hash} }}")
end end
def partial(id, context = nil) def partial(id, context = nil)
@@ -113,29 +114,29 @@ describe "Parser" do
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 == program { 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 == program do
mustache id("foo"), hash(["bar", "ID:baz"]) mustache id("foo"), [], hash(["bar", "ID:baz"])
end end
ast_for("{{foo bar=baz bat=bam}}").should == program do ast_for("{{foo bar=baz bat=bam}}").should == program 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 == program 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 == program 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
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 == program { mustache id("foo"), [id("bar"), string("baz")] }
end end
it "parses contents followed by a mustache" do it "parses contents followed by a mustache" do
+34 -23
View File
@@ -420,13 +420,6 @@ test("GH-14: a partial preceding a selector", function() {
shouldCompileTo(string, [hash, {}, {dude:dude}], "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); shouldCompileTo(string, [hash, {}, {dude:dude}], "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
}); });
test("Partial containing complex expression", function() {
var template = "Dudes: {{#dudes}}{{> dude}} {{/dudes}}";
var dude = "{{../salutation}} {{name}}";
var hash = {salutation: "Mr.", dudes: [{name: "Yehuda"}, {name: "Alan"}]};
shouldCompileTo(template, [hash, {}, {dude: dude}], "Dudes: Mr. Yehuda Mr. Alan ");
});
module("String literal parameters"); module("String literal parameters");
test("simple literals work", function() { test("simple literals work", function() {
@@ -529,8 +522,8 @@ test("passing in data to a compiled function that expects data - works with help
var template = Handlebars.compile("{{hello}}", true); var template = Handlebars.compile("{{hello}}", true);
var helpers = { var helpers = {
hello: function(data) { hello: function(options) {
return data.adjective + " " + this.noun; return options.data.adjective + " " + this.noun;
} }
}; };
@@ -542,8 +535,8 @@ test("passing in data to a compiled function that expects data - works with help
var template = Handlebars.compile("{{hello world}}", true); var template = Handlebars.compile("{{hello world}}", true);
var helpers = { var helpers = {
hello: function(noun, data) { hello: function(noun, options) {
return data.adjective + " " + noun + (this.exclaim ? "!" : ""); return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
} }
}; };
@@ -558,8 +551,8 @@ test("passing in data to a compiled function that expects data - works with bloc
hello: function(fn) { hello: function(fn) {
return fn(this); return fn(this);
}, },
world: function(data) { world: function(options) {
return data.adjective + " world" + (this.exclaim ? "!" : ""); return options.data.adjective + " world" + (this.exclaim ? "!" : "");
} }
}; };
@@ -574,8 +567,8 @@ test("passing in data to a compiled function that expects data - works with bloc
hello: function(fn) { hello: function(fn) {
return fn({exclaim: "?"}); return fn({exclaim: "?"});
}, },
world: function(thing, data) { world: function(thing, options) {
return data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
} }
}; };
@@ -587,11 +580,11 @@ test("passing in data to a compiled function that expects data - works with bloc
var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", true); var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", true);
var helpers = { var helpers = {
hello: function(fn, inverse, data) { hello: function(fn, inverse, options) {
return data.accessData + " " + fn({exclaim: "?"}); return options.data.accessData + " " + fn({exclaim: "?"});
}, },
world: function(thing, data) { world: function(thing, options) {
return data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
} }
}; };
@@ -606,8 +599,8 @@ test("you can override inherited data when invoking a helper", function() {
hello: function(fn) { hello: function(fn) {
return fn({exclaim: "?", zomg: "world"}, null, null, {adjective: "sad"}); return fn({exclaim: "?", zomg: "world"}, null, null, {adjective: "sad"});
}, },
world: function(thing, data) { world: function(thing, options) {
return data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
} }
}; };
@@ -623,8 +616,8 @@ test("you can override inherited data when invoking a helper with depth", functi
hello: function(fn) { hello: function(fn) {
return fn({exclaim: "?"}, null, null, {adjective: "sad"}); return fn({exclaim: "?"}, null, null, {adjective: "sad"});
}, },
world: function(thing, data) { world: function(thing, options) {
return data.adjective + " " + thing + (this.exclaim || ""); return options.data.adjective + " " + thing + (this.exclaim || "");
} }
}; };
@@ -676,3 +669,21 @@ test("helpers take precedence over same-named context properties", function() {
equals(result, "GOODBYE cruel WORLD"); equals(result, "GOODBYE cruel WORLD");
}); });
test("helpers can take an optional hash", function() {
var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD"}}');
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
}
};
var context = {};
var result = template(context, helpers);
equals(result, "GOODBYE CRUEL WORLD");
});
// test("helpers can take an optional hash", function() {
// var template = Handlebars.compile('{{#goodbye cruel="CRUEL"}}world{{/goodbye}}')
// });
+8 -8
View File
@@ -27,11 +27,11 @@ statement
; ;
openBlock openBlock
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2) } : OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
; ;
openInverse openInverse
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2) } : OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
; ;
closeBlock closeBlock
@@ -39,8 +39,8 @@ closeBlock
; ;
mustache mustache
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2) } : OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2, true) } | OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1], true) }
; ;
@@ -54,10 +54,10 @@ simpleInverse
; ;
inMustache inMustache
: path params hash { $$ = [$1].concat($2).concat([$3]) } : path params hash { $$ = [[$1].concat($2), $3] }
| path params { $$ = [$1].concat($2) } | path params { $$ = [[$1].concat($2), null] }
| path hash { $$ = [$1].concat([$2]) } | path hash { $$ = [[$1], $2] }
| path { $$ = [$1] } | path { $$ = [[$1], null] }
; ;
params params