hbs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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