hbs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ var Handlebars = require("handlebars");
2
+
3
+ // BEGIN(BROWSER)
4
+ (function() {
5
+ var classes = ["Lexer", "PrintVisitor", "Context", "Runtime", "Exception"];
6
+ var prop;
7
+
8
+ for(var i=0, l=classes.length; i<l; i++) {
9
+ var className = classes[i], klass = Handlebars[className];
10
+ klass.displayName = "new Handlebars." + className;
11
+
12
+ for(prop in klass) {
13
+ if(klass.hasOwnProperty(prop)) {
14
+ klass[prop].displayName = "Handlebars." + className + "#" + prop;
15
+ }
16
+ }
17
+ }
18
+
19
+ for(prop in Handlebars.Utils) {
20
+ if(Handlebars.Utils.hasOwnProperty(prop)) {
21
+ Handlebars.Utils[prop].displayName = "Handlebars.Utils." + prop;
22
+ }
23
+ }
24
+
25
+ Handlebars.parse.displayName = "Handlebars.parse";
26
+ Handlebars.print.displayName = "Handlebars.print";
27
+ Handlebars.compile.displayName = "Handlebars.compile";
28
+ })();
29
+ // END(BROWSER)
@@ -0,0 +1,138 @@
1
+ var Handlebars = require("handlebars");
2
+ require("handlebars/visitor");
3
+
4
+ // BEGIN(BROWSER)
5
+ Handlebars.PrintVisitor = function() { this.padding = 0; };
6
+ Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
7
+
8
+ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
9
+ var out = "";
10
+
11
+ for(var i=0,l=this.padding; i<l; i++) {
12
+ out = out + " ";
13
+ }
14
+
15
+ out = out + string;
16
+
17
+ if(newline !== false) { out = out + "\n"; }
18
+ return out;
19
+ };
20
+
21
+ Handlebars.PrintVisitor.prototype.program = function(program) {
22
+ var out = this.pad("PROGRAM:"),
23
+ statements = program.statements,
24
+ inverse = program.inverse,
25
+ i, l;
26
+
27
+ this.padding++;
28
+
29
+ for(i=0, l=statements.length; i<l; i++) {
30
+ out = out + this.accept(statements[i]);
31
+ }
32
+
33
+ this.padding--;
34
+
35
+ if(inverse) {
36
+ out = out + this.pad("{{^}}");
37
+
38
+ this.padding++;
39
+
40
+ for(i=0, l=inverse.statements.length; i<l; i++) {
41
+ out = out + this.accept(inverse.statements[i]);
42
+ }
43
+ }
44
+
45
+ this.padding--;
46
+
47
+ return out;
48
+ };
49
+
50
+ Handlebars.PrintVisitor.prototype.block = function(block) {
51
+ var out = "";
52
+
53
+ out = out + this.pad("BLOCK:");
54
+ this.padding++;
55
+ out = out + this.accept(block.mustache);
56
+ out = out + this.accept(block.program);
57
+ this.padding--;
58
+
59
+ return out;
60
+ };
61
+
62
+ Handlebars.PrintVisitor.prototype.inverse = function(block) {
63
+ var out = "";
64
+
65
+ out = out + this.pad("INVERSE:");
66
+ this.padding++;
67
+ out = out + this.accept(block.mustache);
68
+ out = out + this.accept(block.program);
69
+ this.padding--;
70
+
71
+ return out;
72
+ };
73
+
74
+
75
+ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
76
+ var params = mustache.params, paramStrings = [], hash;
77
+
78
+ for(var i=0, l=params.length; i<l; i++) {
79
+ paramStrings.push(this.accept(params[i]));
80
+ }
81
+
82
+ params = "[" + paramStrings.join(", ") + "]";
83
+
84
+ hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
85
+
86
+ return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
87
+ };
88
+
89
+ Handlebars.PrintVisitor.prototype.partial = function(partial) {
90
+ var content = this.accept(partial.id);
91
+ if(partial.context) { content = content + " " + this.accept(partial.context); }
92
+ return this.pad("{{> " + content + " }}");
93
+ };
94
+
95
+ Handlebars.PrintVisitor.prototype.hash = function(hash) {
96
+ var pairs = hash.pairs;
97
+ var joinedPairs = [], left, right;
98
+
99
+ for(var i=0, l=pairs.length; i<l; i++) {
100
+ left = pairs[i][0];
101
+ right = this.accept(pairs[i][1]);
102
+ joinedPairs.push( left + "=" + right );
103
+ }
104
+
105
+ return "HASH{" + joinedPairs.join(", ") + "}";
106
+ };
107
+
108
+ Handlebars.PrintVisitor.prototype.STRING = function(string) {
109
+ return '"' + string.string + '"';
110
+ };
111
+
112
+ Handlebars.PrintVisitor.prototype.INTEGER = function(integer) {
113
+ return "INTEGER{" + integer.integer + "}";
114
+ };
115
+
116
+ Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) {
117
+ return "BOOLEAN{" + bool.bool + "}";
118
+ };
119
+
120
+ Handlebars.PrintVisitor.prototype.ID = function(id) {
121
+ var path = id.parts.join("/");
122
+ if(id.parts.length > 1) {
123
+ return "PATH:" + path;
124
+ } else {
125
+ return "ID:" + path;
126
+ }
127
+ };
128
+
129
+ Handlebars.PrintVisitor.prototype.content = function(content) {
130
+ return this.pad("CONTENT[ '" + content.string + "' ]");
131
+ };
132
+
133
+ Handlebars.PrintVisitor.prototype.comment = function(comment) {
134
+ return this.pad("{{! '" + comment.comment + "' }}");
135
+ };
136
+ // END(BROWSER)
137
+
138
+ exports.PrintVisitor = Handlebars.PrintVisitor;
@@ -0,0 +1,66 @@
1
+ var Handlebars = require("handlebars");
2
+
3
+ // BEGIN(BROWSER)
4
+ Handlebars.Exception = function(message) {
5
+ var tmp = Error.prototype.constructor.apply(this, arguments);
6
+
7
+ for (var p in tmp) {
8
+ if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
9
+ }
10
+ };
11
+ Handlebars.Exception.prototype = new Error;
12
+
13
+ // Build out our basic SafeString type
14
+ Handlebars.SafeString = function(string) {
15
+ this.string = string;
16
+ };
17
+ Handlebars.SafeString.prototype.toString = function() {
18
+ return this.string.toString();
19
+ };
20
+
21
+ (function() {
22
+ var escape = {
23
+ "<": "&lt;",
24
+ ">": "&gt;",
25
+ '"': "&quot;",
26
+ "'": "&#x27;",
27
+ "`": "&#x60;"
28
+ };
29
+
30
+ var badChars = /&(?!\w+;)|[<>"'`]/g;
31
+ var possible = /[&<>"'`]/;
32
+
33
+ var escapeChar = function(chr) {
34
+ return escape[chr] || "&amp;";
35
+ };
36
+
37
+ Handlebars.Utils = {
38
+ escapeExpression: function(string) {
39
+ // don't escape SafeStrings, since they're already safe
40
+ if (string instanceof Handlebars.SafeString) {
41
+ return string.toString();
42
+ } else if (string == null || string === false) {
43
+ return "";
44
+ }
45
+
46
+ if(!possible.test(string)) { return string; }
47
+ return string.replace(badChars, escapeChar);
48
+ },
49
+
50
+ isEmpty: function(value) {
51
+ if (typeof value === "undefined") {
52
+ return true;
53
+ } else if (value === null) {
54
+ return true;
55
+ } else if (value === false) {
56
+ return true;
57
+ } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
58
+ return true;
59
+ } else {
60
+ return false;
61
+ }
62
+ }
63
+ };
64
+ })();
65
+ // END(BROWSER)
66
+
@@ -0,0 +1,13 @@
1
+ var Handlebars = require("handlebars");
2
+
3
+ // BEGIN(BROWSER)
4
+
5
+ Handlebars.Visitor = function() {};
6
+
7
+ Handlebars.Visitor.prototype = {
8
+ accept: function(object) {
9
+ return this[object.type](object);
10
+ }
11
+ };
12
+ // END(BROWSER)
13
+
@@ -0,0 +1,100 @@
1
+ require "spec_helper"
2
+
3
+ class TestContext
4
+ class TestModule
5
+ attr_reader :name, :tests
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ @tests = []
10
+ end
11
+ end
12
+
13
+ attr_reader :modules
14
+
15
+ def initialize
16
+ @modules = []
17
+ end
18
+
19
+ def module(name)
20
+ @modules << TestModule.new(name)
21
+ end
22
+
23
+ def test(name, function)
24
+ @modules.last.tests << [name, function]
25
+ end
26
+ end
27
+
28
+ test_context = TestContext.new
29
+ js_context = Handlebars::Spec::CONTEXT
30
+
31
+ Module.new do
32
+ extend Test::Unit::Assertions
33
+
34
+ def self.js_backtrace(context)
35
+ begin
36
+ context.eval("throw")
37
+ rescue V8::JSError => e
38
+ return e.backtrace(:javascript)
39
+ end
40
+ end
41
+
42
+ js_context["p"] = proc do |str|
43
+ p str
44
+ end
45
+
46
+ js_context["ok"] = proc do |ok, message|
47
+ js_context["$$RSPEC1$$"] = ok
48
+
49
+ result = js_context.eval("!!$$RSPEC1$$")
50
+
51
+ message ||= "#{ok} was not truthy"
52
+
53
+ unless result
54
+ backtrace = js_backtrace(js_context)
55
+ message << "\n#{backtrace.join("\n")}"
56
+ end
57
+
58
+ assert result, message
59
+ end
60
+
61
+ js_context["equals"] = proc do |first, second, message|
62
+ js_context["$$RSPEC1$$"] = first
63
+ js_context["$$RSPEC2$$"] = second
64
+
65
+ result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
66
+
67
+ message ||= "#{first} did not == #{second}"
68
+
69
+ unless result
70
+ backtrace = js_backtrace(js_context)
71
+ message << "\n#{backtrace.join("\n")}"
72
+ end
73
+
74
+ assert result, message
75
+ end
76
+
77
+ js_context["equal"] = js_context["equals"]
78
+
79
+ js_context["module"] = proc do |name|
80
+ test_context.module(name)
81
+ end
82
+
83
+ js_context["test"] = proc do |name, function|
84
+ test_context.test(name, function)
85
+ end
86
+
87
+ local = Regexp.escape(File.expand_path(Dir.pwd))
88
+ qunit_spec = File.expand_path("../qunit_spec.js", __FILE__)
89
+ js_context.load(qunit_spec.sub(/^#{local}\//, ''))
90
+ end
91
+
92
+ test_context.modules.each do |mod|
93
+ describe mod.name do
94
+ mod.tests.each do |name, function|
95
+ it name do
96
+ function.call
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,259 @@
1
+ require "spec_helper"
2
+
3
+ describe "Parser" do
4
+ let(:handlebars) { @context["Handlebars"] }
5
+
6
+ def program(&block)
7
+ ASTBuilder.build do
8
+ program do
9
+ instance_eval(&block)
10
+ end
11
+ end
12
+ end
13
+
14
+ def ast_for(string)
15
+ ast = handlebars.parse(string)
16
+ handlebars.print(ast)
17
+ end
18
+
19
+ class ASTBuilder
20
+ def self.build(&block)
21
+ ret = new
22
+ ret.evaluate(&block)
23
+ ret.out
24
+ end
25
+
26
+ attr_reader :out
27
+
28
+ def initialize
29
+ @padding = 0
30
+ @out = ""
31
+ end
32
+
33
+ def evaluate(&block)
34
+ instance_eval(&block)
35
+ end
36
+
37
+ def pad(string)
38
+ @out << (" " * @padding) + string + "\n"
39
+ end
40
+
41
+ def with_padding
42
+ @padding += 1
43
+ ret = yield
44
+ @padding -= 1
45
+ ret
46
+ end
47
+
48
+ def program
49
+ pad("PROGRAM:")
50
+ with_padding { yield }
51
+ end
52
+
53
+ def inverse
54
+ pad("{{^}}")
55
+ with_padding { yield }
56
+ end
57
+
58
+ def block
59
+ pad("BLOCK:")
60
+ with_padding { yield }
61
+ end
62
+
63
+ def inverted_block
64
+ pad("INVERSE:")
65
+ with_padding { yield }
66
+ end
67
+
68
+ def mustache(id, params = [], hash = nil)
69
+ hash = " #{hash}" if hash
70
+ pad("{{ #{id} [#{params.join(", ")}]#{hash} }}")
71
+ end
72
+
73
+ def partial(id, context = nil)
74
+ content = id.dup
75
+ content << " #{context}" if context
76
+ pad("{{> #{content} }}")
77
+ end
78
+
79
+ def comment(comment)
80
+ pad("{{! '#{comment}' }}")
81
+ end
82
+
83
+ def multiline_comment(comment)
84
+ pad("{{! '\n#{comment}\n' }}")
85
+ end
86
+
87
+ def content(string)
88
+ pad("CONTENT[ '#{string}' ]")
89
+ end
90
+
91
+ def string(string)
92
+ string.inspect
93
+ end
94
+
95
+ def integer(string)
96
+ "INTEGER{#{string}}"
97
+ end
98
+
99
+ def boolean(string)
100
+ "BOOLEAN{#{string}}"
101
+ end
102
+
103
+ def hash(*pairs)
104
+ "HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}"
105
+ end
106
+
107
+ def id(id)
108
+ "ID:#{id}"
109
+ end
110
+
111
+ def path(*parts)
112
+ "PATH:#{parts.join("/")}"
113
+ end
114
+ end
115
+
116
+ it "parses simple mustaches" do
117
+ ast_for("{{foo}}").should == program { mustache id("foo") }
118
+ end
119
+
120
+ it "parses mustaches with paths" do
121
+ ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
122
+ end
123
+
124
+ it "parses mustaches with this/foo" do
125
+ ast_for("{{this/foo}}").should == program { mustache id("foo") }
126
+ end
127
+
128
+ it "parses mustaches with - in a path" do
129
+ ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
130
+ end
131
+
132
+ it "parses mustaches with parameters" do
133
+ ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
134
+ end
135
+
136
+ it "parses mustaches with hash arguments" do
137
+ ast_for("{{foo bar=baz}}").should == program do
138
+ mustache id("foo"), [], hash(["bar", id("baz")])
139
+ end
140
+
141
+ ast_for("{{foo bar=1}}").should == program do
142
+ mustache id("foo"), [], hash(["bar", integer("1")])
143
+ end
144
+
145
+ ast_for("{{foo bar=true}}").should == program do
146
+ mustache id("foo"), [], hash(["bar", boolean("true")])
147
+ end
148
+
149
+ ast_for("{{foo bar=false}}").should == program do
150
+ mustache id("foo"), [], hash(["bar", boolean("false")])
151
+ end
152
+
153
+ ast_for("{{foo bar=baz bat=bam}}").should == program do
154
+ mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
155
+ end
156
+
157
+ ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
158
+ mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
159
+ end
160
+
161
+ ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
162
+ mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
163
+ end
164
+
165
+ ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == program do
166
+ mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
167
+ end
168
+
169
+ ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == program do
170
+ mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
171
+ end
172
+
173
+ ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == program do
174
+ mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
175
+ end
176
+ end
177
+
178
+ it "parses mustaches with string parameters" do
179
+ ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
180
+ end
181
+
182
+ it "parses mustaches with INTEGER parameters" do
183
+ ast_for("{{foo 1}}").should == program { mustache id("foo"), [integer("1")] }
184
+ end
185
+
186
+ it "parses mustaches with BOOLEAN parameters" do
187
+ ast_for("{{foo true}}").should == program { mustache id("foo"), [boolean("true")] }
188
+ ast_for("{{foo false}}").should == program { mustache id("foo"), [boolean("false")] }
189
+ end
190
+
191
+ it "parses contents followed by a mustache" do
192
+ ast_for("foo bar {{baz}}").should == program do
193
+ content "foo bar "
194
+ mustache id("baz")
195
+ end
196
+ end
197
+
198
+ it "parses a partial" do
199
+ ast_for("{{> foo }}").should == program { partial id("foo") }
200
+ end
201
+
202
+ it "parses a partial with context" do
203
+ ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
204
+ end
205
+
206
+ it "parses a comment" do
207
+ ast_for("{{! this is a comment }}").should == program do
208
+ comment " this is a comment "
209
+ end
210
+ end
211
+
212
+ it "parses a multi-line comment" do
213
+ ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
214
+ multiline_comment "this is a multi-line comment"
215
+ end
216
+ end
217
+
218
+ it "parses an inverse section" do
219
+ ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
220
+ block do
221
+ mustache id("foo")
222
+
223
+ program do
224
+ content " bar "
225
+ end
226
+
227
+ inverse do
228
+ content " baz "
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ it "parses a standalone inverse section" do
235
+ ast_for("{{^foo}}bar{{/foo}}").should == program do
236
+ inverted_block do
237
+ mustache id("foo")
238
+
239
+ program do
240
+ content "bar"
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ it "raises if there's a Parse error" do
247
+ lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/)
248
+ lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/)
249
+ end
250
+
251
+ it "knows how to report the correct line number in errors" do
252
+ lambda { ast_for("hello\nmy\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 3/m)
253
+ lambda { ast_for("hello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 5/m)
254
+ end
255
+
256
+ it "knows how to report the correct line number in errors when the first character is a newline" do
257
+ lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
258
+ end
259
+ end