Merge remote-tracking branch 'upstream/master'
This commit is contained in:
+10
-10
@@ -2,18 +2,18 @@ GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (0.9.2.2)
|
||||
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)
|
||||
ref (1.0.2)
|
||||
rspec (2.12.0)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rspec-core (2.12.2)
|
||||
rspec-expectations (2.12.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.1)
|
||||
therubyracer (0.10.1)
|
||||
libv8 (~> 3.3.10)
|
||||
rspec-mocks (2.12.1)
|
||||
therubyracer (0.11.0)
|
||||
ref
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
+21
-2
@@ -62,6 +62,24 @@ Handlebars.createFrame = Object.create || function(object) {
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
@@ -117,8 +135,9 @@ Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context) {
|
||||
Handlebars.log(context);
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
|
||||
}(this.Handlebars));
|
||||
|
||||
@@ -33,13 +33,10 @@ var Handlebars = require('./base');
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
|
||||
// TODO: disallow complex IDs
|
||||
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
Handlebars.AST.PartialNode = function(partialName, context) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
var verifyMatch = function(open, close) {
|
||||
@@ -93,6 +90,11 @@ var Handlebars = require('./base');
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNameNode = function(name) {
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
Handlebars.AST.DataNode = function(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
|
||||
@@ -12,16 +12,6 @@ Handlebars.parse = function(string) {
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
|
||||
@@ -160,7 +160,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var id = partial.id;
|
||||
var partialName = partial.partialName;
|
||||
this.usePartial = true;
|
||||
|
||||
if(partial.context) {
|
||||
@@ -169,7 +169,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
this.opcode('push', 'depth0');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', id.original);
|
||||
this.opcode('invokePartial', partialName.name);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
@@ -1035,16 +1035,28 @@ Handlebars.JavaScriptCompiler = function() {};
|
||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
||||
|
||||
Handlebars.precompile = function(string, options) {
|
||||
options = options || {};
|
||||
if (typeof string !== 'string') {
|
||||
throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
var ast = Handlebars.parse(string);
|
||||
var environment = new Handlebars.Compiler().compile(ast, options);
|
||||
return new Handlebars.JavaScriptCompiler().compile(environment, options);
|
||||
};
|
||||
|
||||
Handlebars.compile = function(string, options) {
|
||||
options = options || {};
|
||||
if (typeof string !== 'string') {
|
||||
throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
var compiled;
|
||||
function compile() {
|
||||
var ast = Handlebars.parse(string);
|
||||
|
||||
@@ -72,7 +72,7 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.id);
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
return this.pad("{{> " + content + " }}");
|
||||
};
|
||||
@@ -111,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
return "PARTIAL:" + partialName.name;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + data.id;
|
||||
};
|
||||
|
||||
+134
-2
@@ -114,6 +114,10 @@ describe "Parser" do
|
||||
"@#{id}"
|
||||
end
|
||||
|
||||
def partial_name(name)
|
||||
"PARTIAL:#{name}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
@@ -218,11 +222,15 @@ describe "Parser" do
|
||||
end
|
||||
|
||||
it "parses a partial" do
|
||||
ast_for("{{> foo }}").should == root { partial id("foo") }
|
||||
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
|
||||
end
|
||||
|
||||
it "parses a partial with context" do
|
||||
ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
|
||||
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
|
||||
end
|
||||
|
||||
it "parses a partial with a complex name" do
|
||||
ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") }
|
||||
end
|
||||
|
||||
it "parses a comment" do
|
||||
@@ -253,6 +261,130 @@ describe "Parser" do
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " baz "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks" do
|
||||
ast_for("{{#foo}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == root do
|
||||
block do
|
||||
|
||||
+42
-6
@@ -456,12 +456,17 @@ test("providing a helpers hash", function() {
|
||||
"Goodbye cruel world!", "helpers hash is available inside other blocks");
|
||||
});
|
||||
|
||||
test("in cases of conflict, the explicit hash wins", function() {
|
||||
|
||||
test("in cases of conflict, helpers win", function() {
|
||||
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence escaped expansion");
|
||||
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence simple expansion");
|
||||
});
|
||||
|
||||
test("the helpers hash is available is nested contexts", function() {
|
||||
|
||||
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
||||
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
|
||||
"helpers hash is available in nested contexts.");
|
||||
});
|
||||
|
||||
suite("partials");
|
||||
@@ -521,13 +526,21 @@ test("GH-14: a partial preceding a selector", function() {
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
|
||||
});
|
||||
|
||||
test("Partials with literal paths", function() {
|
||||
var string = "Dudes: {{> [dude]}}";
|
||||
test("Partials with slash paths", function() {
|
||||
var string = "Dudes: {{> shared/dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
test("Partials with integer path", function() {
|
||||
var string = "Dudes: {{> 404}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
|
||||
suite("String literal parameters");
|
||||
|
||||
test("simple literals work", function() {
|
||||
@@ -730,6 +743,23 @@ test("each with @index", function() {
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
test("data passed to helpers", function() {
|
||||
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash, {
|
||||
data: {
|
||||
exclaim: '!'
|
||||
}
|
||||
});
|
||||
equal(result, 'a!b!c!');
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('detectDataInsideEach', function(options) {
|
||||
return options.data && options.data.exclaim;
|
||||
});
|
||||
|
||||
test("log", function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
@@ -1245,3 +1275,9 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct
|
||||
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
|
||||
shouldCompileTo(string, data, output);
|
||||
});
|
||||
|
||||
test("Passing falsy values to Handlebars.compile throws an error", function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(null);
|
||||
}, "You must pass a string to Handlebars.compile. You passed null");
|
||||
});
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
require "v8"
|
||||
|
||||
# Monkey patches due to bugs in RubyRacer
|
||||
class V8::JSError
|
||||
def initialize(try, to)
|
||||
@to = to
|
||||
begin
|
||||
super(initialize_unsafe(try))
|
||||
rescue Exception => e
|
||||
# Original code does not make an Array here
|
||||
@boundaries = [Boundary.new(:rbframes => e.backtrace)]
|
||||
@value = e
|
||||
super("BUG! please report. JSError#initialize failed!: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
def parse_js_frames(try)
|
||||
raw = @to.rb(try.StackTrace())
|
||||
if raw && !raw.empty?
|
||||
raw.split("\n")[1..-1].tap do |frames|
|
||||
# Original code uses strip!, and the frames are not guaranteed to be strippable
|
||||
frames.each {|frame| frame.strip.chomp!(",")}
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Handlebars
|
||||
module Spec
|
||||
def self.js_backtrace(context)
|
||||
|
||||
@@ -132,24 +132,24 @@ describe "Tokenizer" do
|
||||
result[4].should be_token("CONTENT", " baz")
|
||||
end
|
||||
|
||||
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{> foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
|
||||
it "tokenizes a partial with context as 'OPEN_PARTIAL PARTIAL_NAME ID CLOSE'" do
|
||||
result = tokenize("{{> foo bar }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{>foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
|
||||
result = tokenize("{{>foo }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a comment as 'COMMENT'" do
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
|
||||
%x mu emu com
|
||||
%x mu emu com par
|
||||
|
||||
%%
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
|
||||
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{>" { this.begin("par"); return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
@@ -46,6 +46,8 @@
|
||||
<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
|
||||
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
<par>\s+ { /*ignore whitespace*/ }
|
||||
<par>[a-zA-Z0-9_$-/]+ { this.popState(); return 'PARTIAL_NAME'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
|
||||
|
||||
+10
-3
@@ -7,8 +7,11 @@ root
|
||||
;
|
||||
|
||||
program
|
||||
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
: simpleInverse statements { $$ = new yy.ProgramNode([], $2); }
|
||||
| statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
| statements simpleInverse { $$ = new yy.ProgramNode($1, []); }
|
||||
| statements { $$ = new yy.ProgramNode($1); }
|
||||
| simpleInverse { $$ = new yy.ProgramNode([], []); }
|
||||
| "" { $$ = new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
@@ -45,8 +48,8 @@ mustache
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
: OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
@@ -91,6 +94,10 @@ hashSegment
|
||||
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
|
||||
;
|
||||
|
||||
partialName
|
||||
: PARTIAL_NAME { $$ = new yy.PartialNameNode($1); }
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments { $$ = new yy.IdNode($1); }
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user