Merge branch 'master' of github.com:wycats/handlebars.js
This commit is contained in:
+9
-9
@@ -4,15 +4,15 @@ GEM
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (0.9.2.2)
|
||||
rspec (2.7.0)
|
||||
rspec-core (~> 2.7.0)
|
||||
rspec-expectations (~> 2.7.0)
|
||||
rspec-mocks (~> 2.7.0)
|
||||
rspec-core (2.7.1)
|
||||
rspec-expectations (2.7.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.7.0)
|
||||
therubyracer (0.9.9)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.0)
|
||||
rspec-expectations (2.11.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.1)
|
||||
therubyracer (0.10.1)
|
||||
libv8 (~> 3.3.10)
|
||||
|
||||
PLATFORMS
|
||||
|
||||
+16
-2
@@ -56,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) {
|
||||
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) {
|
||||
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 {
|
||||
ret = inverse(this);
|
||||
|
||||
@@ -93,6 +93,11 @@ var Handlebars = require('./base');
|
||||
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) {
|
||||
this.type = "STRING";
|
||||
this.string = string;
|
||||
|
||||
@@ -210,17 +210,17 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
simpleMustache: function(mustache, program, inverse) {
|
||||
var id = mustache.id;
|
||||
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
if (id.parts.length) {
|
||||
this.opcode('lookupOnContext', id.parts[0]);
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
}
|
||||
if (id.type === 'DATA') {
|
||||
this.DATA(id);
|
||||
} else if (id.parts.length) {
|
||||
this.ID(id);
|
||||
} else {
|
||||
// Simplified ID for `this`
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('pushContext');
|
||||
}
|
||||
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
@@ -247,6 +247,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
}
|
||||
},
|
||||
|
||||
DATA: function(data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', data.id);
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
},
|
||||
@@ -271,6 +276,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
if(isNaN(depth)) { throw new Error("EWOT"); }
|
||||
if(depth === 0) { return; }
|
||||
|
||||
if(!this.depths[depth]) {
|
||||
@@ -437,7 +443,8 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
if (!this.isChild) {
|
||||
var namespace = this.namespace;
|
||||
var copies = "helpers = helpers || " + namespace + ".helpers;";
|
||||
if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
|
||||
if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
|
||||
if (this.options.data) { copies = copies + " data = data || {};"; }
|
||||
out.push(copies);
|
||||
} else {
|
||||
out.push('');
|
||||
@@ -646,6 +653,16 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
});
|
||||
},
|
||||
|
||||
// [lookupData]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: data[id], ...
|
||||
//
|
||||
// Push the result of looking up `id` on the current data
|
||||
lookupData: function(id) {
|
||||
this.pushStack(this.nameLookup('data', id, 'data'));
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
//
|
||||
// On stack, before: ...
|
||||
|
||||
@@ -111,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + data.id;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
|
||||
@@ -39,11 +39,11 @@ Module.new do
|
||||
end
|
||||
end
|
||||
|
||||
js_context["p"] = proc do |str|
|
||||
js_context["p"] = proc do |this, str|
|
||||
p str
|
||||
end
|
||||
|
||||
js_context["ok"] = proc do |ok, message|
|
||||
js_context["ok"] = proc do |this, ok, message|
|
||||
js_context["$$RSPEC1$$"] = ok
|
||||
|
||||
result = js_context.eval("!!$$RSPEC1$$")
|
||||
@@ -58,7 +58,7 @@ Module.new do
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equals"] = proc do |first, second, message|
|
||||
js_context["equals"] = proc do |this, first, second, message|
|
||||
js_context["$$RSPEC1$$"] = first
|
||||
js_context["$$RSPEC2$$"] = second
|
||||
|
||||
@@ -77,11 +77,11 @@ Module.new do
|
||||
|
||||
js_context["equal"] = js_context["equals"]
|
||||
|
||||
js_context["module"] = proc do |name|
|
||||
js_context["module"] = proc do |this, name|
|
||||
test_context.module(name)
|
||||
end
|
||||
|
||||
js_context["test"] = proc do |name, function|
|
||||
js_context["test"] = proc do |this, name, function|
|
||||
test_context.test(name, function)
|
||||
end
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ describe "Parser" do
|
||||
"ID:#{id}"
|
||||
end
|
||||
|
||||
def data(id)
|
||||
"@#{id}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
@@ -119,6 +123,10 @@ describe "Parser" do
|
||||
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
|
||||
|
||||
it "parses mustaches with paths" do
|
||||
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
|
||||
end
|
||||
@@ -152,6 +160,10 @@ describe "Parser" do
|
||||
mustache id("foo"), [], hash(["bar", boolean("false")])
|
||||
end
|
||||
|
||||
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"])
|
||||
end
|
||||
@@ -190,6 +202,10 @@ describe "Parser" do
|
||||
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
|
||||
|
||||
it "parses contents followed by a mustache" do
|
||||
ast_for("foo bar {{baz}}").should == root do
|
||||
content "foo bar "
|
||||
|
||||
+76
-1
@@ -616,7 +616,7 @@ test("if with function argument", 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"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"each with array argument iterates over the contents when not empty");
|
||||
@@ -624,6 +624,16 @@ test("each", function() {
|
||||
"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() {
|
||||
var string = "{{log blah}}"
|
||||
var hash = { blah: "whee" };
|
||||
@@ -655,6 +665,71 @@ test("passing in data to a compiled function that expects data - works with help
|
||||
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() {
|
||||
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
|
||||
|
||||
|
||||
+7
-7
@@ -47,15 +47,15 @@ module Handlebars
|
||||
def self.load_helpers(context)
|
||||
context["exports"] = nil
|
||||
|
||||
context["p"] = proc do |val|
|
||||
context["p"] = proc do |this, val|
|
||||
p val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts"] = proc do |val|
|
||||
context["puts"] = proc do |this, val|
|
||||
puts val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts_node"] = proc do |val|
|
||||
context["puts_node"] = proc do |this, val|
|
||||
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
||||
puts
|
||||
end
|
||||
@@ -82,12 +82,12 @@ module Handlebars
|
||||
|
||||
context["CompilerContext"] = {}
|
||||
CompilerContext = context["CompilerContext"]
|
||||
CompilerContext["compile"] = proc do |*args|
|
||||
CompilerContext["compile"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
|
||||
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
|
||||
end
|
||||
CompilerContext["compileWithPartial"] = proc do |*args|
|
||||
CompilerContext["compileWithPartial"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
|
||||
end
|
||||
@@ -108,7 +108,7 @@ module Handlebars
|
||||
|
||||
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
|
||||
|
||||
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"]["log"] = proc do |level, str|
|
||||
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
|
||||
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||
|
||||
if logger_level <= level
|
||||
|
||||
@@ -244,6 +244,20 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("ID", "omg")
|
||||
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
|
||||
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
|
||||
end
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||
<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>"false"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
|
||||
|
||||
@@ -58,6 +58,7 @@ inMustache
|
||||
| path params { $$ = [[$1].concat($2), null]; }
|
||||
| path hash { $$ = [[$1], $2]; }
|
||||
| path { $$ = [[$1], null]; }
|
||||
| DATA { $$ = [[new yy.DataNode($1)], null]; }
|
||||
;
|
||||
|
||||
params
|
||||
@@ -70,6 +71,7 @@ param
|
||||
| STRING { $$ = new yy.StringNode($1); }
|
||||
| INTEGER { $$ = new yy.IntegerNode($1); }
|
||||
| BOOLEAN { $$ = new yy.BooleanNode($1); }
|
||||
| DATA { $$ = new yy.DataNode($1); }
|
||||
;
|
||||
|
||||
hash
|
||||
@@ -86,6 +88,7 @@ hashSegment
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
|
||||
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
|
||||
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
|
||||
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
|
||||
;
|
||||
|
||||
path
|
||||
|
||||
Reference in New Issue
Block a user