Skip to content

Commit f79af6b

Browse files
author
tomhuda
committed
Merge branch 'master' of github.com:wycats/handlebars.js
2 parents 92b6c14 + efb1e25 commit f79af6b

File tree

12 files changed

+182
-33
lines changed

12 files changed

+182
-33
lines changed

Gemfile.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ GEM
44
diff-lcs (1.1.3)
55
libv8 (3.3.10.4)
66
rake (0.9.2.2)
7-
rspec (2.7.0)
8-
rspec-core (~> 2.7.0)
9-
rspec-expectations (~> 2.7.0)
10-
rspec-mocks (~> 2.7.0)
11-
rspec-core (2.7.1)
12-
rspec-expectations (2.7.0)
13-
diff-lcs (~> 1.1.2)
14-
rspec-mocks (2.7.0)
15-
therubyracer (0.9.9)
7+
rspec (2.11.0)
8+
rspec-core (~> 2.11.0)
9+
rspec-expectations (~> 2.11.0)
10+
rspec-mocks (~> 2.11.0)
11+
rspec-core (2.11.0)
12+
rspec-expectations (2.11.1)
13+
diff-lcs (~> 1.1.3)
14+
rspec-mocks (2.11.1)
15+
therubyracer (0.10.1)
1616
libv8 (~> 3.3.10)
1717

1818
PLATFORMS

lib/handlebars/base.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,27 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
5656
}
5757
});
5858

59+
Handlebars.K = function() {};
60+
61+
Handlebars.createFrame = Object.create || function(object) {
62+
Handlebars.K.prototype = object;
63+
var obj = new Handlebars.K();
64+
Handlebars.K.prototype = null;
65+
return obj;
66+
};
67+
5968
Handlebars.registerHelper('each', function(context, options) {
6069
var fn = options.fn, inverse = options.inverse;
61-
var ret = "";
70+
var ret = "", data;
71+
72+
if (options.data) {
73+
data = Handlebars.createFrame(options.data);
74+
}
6275

6376
if(context && context.length > 0) {
6477
for(var i=0, j=context.length; i<j; i++) {
65-
ret = ret + fn(context[i]);
78+
if (data) { data.index = i; }
79+
ret = ret + fn(context[i], { data: data });
6680
}
6781
} else {
6882
ret = inverse(this);

lib/handlebars/compiler/ast.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ var Handlebars = require('./base');
9393
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
9494
};
9595

96+
Handlebars.AST.DataNode = function(id) {
97+
this.type = "DATA";
98+
this.id = id;
99+
};
100+
96101
Handlebars.AST.StringNode = function(string) {
97102
this.type = "STRING";
98103
this.string = string;

lib/handlebars/compiler/compiler.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -210,17 +210,17 @@ Handlebars.JavaScriptCompiler = function() {};
210210
simpleMustache: function(mustache, program, inverse) {
211211
var id = mustache.id;
212212

213-
this.addDepth(id.depth);
214-
this.opcode('getContext', id.depth);
215-
216-
if (id.parts.length) {
217-
this.opcode('lookupOnContext', id.parts[0]);
218-
for(var i=1, l=id.parts.length; i<l; i++) {
219-
this.opcode('lookup', id.parts[i]);
220-
}
213+
if (id.type === 'DATA') {
214+
this.DATA(id);
215+
} else if (id.parts.length) {
216+
this.ID(id);
221217
} else {
218+
// Simplified ID for `this`
219+
this.addDepth(id.depth);
220+
this.opcode('getContext', id.depth);
222221
this.opcode('pushContext');
223222
}
223+
224224
this.opcode('resolvePossibleLambda');
225225
},
226226

@@ -247,6 +247,11 @@ Handlebars.JavaScriptCompiler = function() {};
247247
}
248248
},
249249

250+
DATA: function(data) {
251+
this.options.data = true;
252+
this.opcode('lookupData', data.id);
253+
},
254+
250255
STRING: function(string) {
251256
this.opcode('pushString', string.string);
252257
},
@@ -271,6 +276,7 @@ Handlebars.JavaScriptCompiler = function() {};
271276
},
272277

273278
addDepth: function(depth) {
279+
if(isNaN(depth)) { throw new Error("EWOT"); }
274280
if(depth === 0) { return; }
275281

276282
if(!this.depths[depth]) {
@@ -437,7 +443,8 @@ Handlebars.JavaScriptCompiler = function() {};
437443
if (!this.isChild) {
438444
var namespace = this.namespace;
439445
var copies = "helpers = helpers || " + namespace + ".helpers;";
440-
if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
446+
if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
447+
if (this.options.data) { copies = copies + " data = data || {};"; }
441448
out.push(copies);
442449
} else {
443450
out.push('');
@@ -646,6 +653,16 @@ Handlebars.JavaScriptCompiler = function() {};
646653
});
647654
},
648655

656+
// [lookupData]
657+
//
658+
// On stack, before: ...
659+
// On stack, after: data[id], ...
660+
//
661+
// Push the result of looking up `id` on the current data
662+
lookupData: function(id) {
663+
this.pushStack(this.nameLookup('data', id, 'data'));
664+
},
665+
649666
// [pushStringParam]
650667
//
651668
// On stack, before: ...

lib/handlebars/compiler/printer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
111111
}
112112
};
113113

114+
Handlebars.PrintVisitor.prototype.DATA = function(data) {
115+
return "@" + data.id;
116+
};
117+
114118
Handlebars.PrintVisitor.prototype.content = function(content) {
115119
return this.pad("CONTENT[ '" + content.string + "' ]");
116120
};

spec/acceptance_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ def self.js_backtrace(context)
3939
end
4040
end
4141

42-
js_context["p"] = proc do |str|
42+
js_context["p"] = proc do |this, str|
4343
p str
4444
end
4545

46-
js_context["ok"] = proc do |ok, message|
46+
js_context["ok"] = proc do |this, ok, message|
4747
js_context["$$RSPEC1$$"] = ok
4848

4949
result = js_context.eval("!!$$RSPEC1$$")
@@ -58,7 +58,7 @@ def self.js_backtrace(context)
5858
assert result, message
5959
end
6060

61-
js_context["equals"] = proc do |first, second, message|
61+
js_context["equals"] = proc do |this, first, second, message|
6262
js_context["$$RSPEC1$$"] = first
6363
js_context["$$RSPEC2$$"] = second
6464

@@ -77,11 +77,11 @@ def self.js_backtrace(context)
7777

7878
js_context["equal"] = js_context["equals"]
7979

80-
js_context["module"] = proc do |name|
80+
js_context["module"] = proc do |this, name|
8181
test_context.module(name)
8282
end
8383

84-
js_context["test"] = proc do |name, function|
84+
js_context["test"] = proc do |this, name, function|
8585
test_context.test(name, function)
8686
end
8787

spec/parser_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ def id(id)
110110
"ID:#{id}"
111111
end
112112

113+
def data(id)
114+
"@#{id}"
115+
end
116+
113117
def path(*parts)
114118
"PATH:#{parts.join("/")}"
115119
end
@@ -119,6 +123,10 @@ def path(*parts)
119123
ast_for("{{foo}}").should == root { mustache id("foo") }
120124
end
121125

126+
it "parses simple mustaches with data" do
127+
ast_for("{{@foo}}").should == root { mustache data("foo") }
128+
end
129+
122130
it "parses mustaches with paths" do
123131
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
124132
end
@@ -152,6 +160,10 @@ def path(*parts)
152160
mustache id("foo"), [], hash(["bar", boolean("false")])
153161
end
154162

163+
ast_for("{{foo bar=@baz}}").should == root do
164+
mustache id("foo"), [], hash(["bar", data("baz")])
165+
end
166+
155167
ast_for("{{foo bar=baz bat=bam}}").should == root do
156168
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
157169
end
@@ -190,6 +202,10 @@ def path(*parts)
190202
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
191203
end
192204

205+
it "parses mutaches with DATA parameters" do
206+
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
207+
end
208+
193209
it "parses contents followed by a mustache" do
194210
ast_for("foo bar {{baz}}").should == root do
195211
content "foo bar "

spec/qunit_spec.js

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,14 +616,24 @@ test("if with function argument", function() {
616616
});
617617

618618
test("each", function() {
619-
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"
619+
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
620620
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
621621
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
622622
"each with array argument iterates over the contents when not empty");
623623
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
624624
"each with array argument ignores the contents when empty");
625625
});
626626

627+
test("each with @index", function() {
628+
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
629+
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
630+
631+
var template = CompilerContext.compile(string);
632+
var result = template(hash);
633+
634+
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
635+
});
636+
627637
test("log", function() {
628638
var string = "{{log blah}}"
629639
var hash = { blah: "whee" };
@@ -655,6 +665,71 @@ test("passing in data to a compiled function that expects data - works with help
655665
equals("happy cat", result, "Data output by helper");
656666
});
657667

668+
test("data can be looked up via @foo", function() {
669+
var template = CompilerContext.compile("{{@hello}}");
670+
var result = template({}, { data: { hello: "hello" } });
671+
equals("hello", result, "@foo retrieves template data");
672+
});
673+
674+
var objectCreate = Handlebars.createFrame;
675+
676+
test("deep @foo triggers automatic top-level data", function() {
677+
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
678+
679+
var helpers = objectCreate(Handlebars.helpers);
680+
681+
helpers.let = function(options) {
682+
var frame = Handlebars.createFrame(options.data);
683+
684+
for (var prop in options.hash) {
685+
frame[prop] = options.hash[prop];
686+
}
687+
return options.fn(this, { data: frame });
688+
};
689+
690+
var result = template({ foo: true }, { helpers: helpers });
691+
equals("Hello world", result, "Automatic data was triggered");
692+
});
693+
694+
test("parameter data can be looked up via @foo", function() {
695+
var template = CompilerContext.compile("{{hello @world}}");
696+
var helpers = {
697+
hello: function(noun) {
698+
return "Hello " + noun;
699+
}
700+
};
701+
702+
var result = template({}, { helpers: helpers, data: { world: "world" } });
703+
equals("Hello world", result, "@foo as a parameter retrieves template data");
704+
});
705+
706+
test("hash values can be looked up via @foo", function() {
707+
var template = CompilerContext.compile("{{hello noun=@world}}");
708+
var helpers = {
709+
hello: function(options) {
710+
return "Hello " + options.hash.noun;
711+
}
712+
};
713+
714+
var result = template({}, { helpers: helpers, data: { world: "world" } });
715+
equals("Hello world", result, "@foo as a parameter retrieves template data");
716+
});
717+
718+
test("data is inherited downstream", function() {
719+
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
720+
var helpers = {
721+
let: function(options) {
722+
for (var prop in options.hash) {
723+
options.data[prop] = options.hash[prop];
724+
}
725+
return options.fn(this);
726+
}
727+
};
728+
729+
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
730+
equals("hello world", result, "data variables are inherited downstream");
731+
});
732+
658733
test("passing in data to a compiled function that expects data - works with helpers in partials", function() {
659734
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
660735

spec/spec_helper.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ def self.remove_exports(string)
4747
def self.load_helpers(context)
4848
context["exports"] = nil
4949

50-
context["p"] = proc do |val|
50+
context["p"] = proc do |this, val|
5151
p val if ENV["DEBUG_JS"]
5252
end
5353

54-
context["puts"] = proc do |val|
54+
context["puts"] = proc do |this, val|
5555
puts val if ENV["DEBUG_JS"]
5656
end
5757

58-
context["puts_node"] = proc do |val|
58+
context["puts_node"] = proc do |this, val|
5959
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
6060
puts
6161
end
@@ -82,12 +82,12 @@ def self.js_load(context, file)
8282

8383
context["CompilerContext"] = {}
8484
CompilerContext = context["CompilerContext"]
85-
CompilerContext["compile"] = proc do |*args|
85+
CompilerContext["compile"] = proc do |this, *args|
8686
template, options = args[0], args[1] || nil
8787
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
8888
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
8989
end
90-
CompilerContext["compileWithPartial"] = proc do |*args|
90+
CompilerContext["compileWithPartial"] = proc do |this, *args|
9191
template, options = args[0], args[1] || nil
9292
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
9393
end
@@ -108,7 +108,7 @@ def self.js_load(context, file)
108108

109109
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
110110

111-
context["Handlebars"]["logger"]["log"] = proc do |level, str|
111+
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
112112
logger_level = context["Handlebars"]["logger"]["level"].to_i
113113

114114
if logger_level <= level
@@ -133,7 +133,7 @@ def self.js_load(context, file)
133133

134134
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
135135

136-
context["Handlebars"]["logger"]["log"] = proc do |level, str|
136+
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
137137
logger_level = context["Handlebars"]["logger"]["level"].to_i
138138

139139
if logger_level <= level

0 commit comments

Comments
 (0)