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.
- data/.gitignore +4 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.mdown +20 -0
- data/Rakefile +5 -0
- data/hbs.gemspec +28 -0
- data/js/.gitignore +5 -0
- data/js/Gemfile +4 -0
- data/js/LICENSE +20 -0
- data/js/README.markdown +224 -0
- data/js/Rakefile +107 -0
- data/js/bench/benchwarmer.js +147 -0
- data/js/bench/handlebars.js +166 -0
- data/js/lib/handlebars.js +15 -0
- data/js/lib/handlebars/ast.js +103 -0
- data/js/lib/handlebars/base.js +114 -0
- data/js/lib/handlebars/compiler.js +739 -0
- data/js/lib/handlebars/debug.js +29 -0
- data/js/lib/handlebars/printer.js +138 -0
- data/js/lib/handlebars/utils.js +66 -0
- data/js/lib/handlebars/visitor.js +13 -0
- data/js/spec/acceptance_spec.rb +100 -0
- data/js/spec/parser_spec.rb +259 -0
- data/js/spec/qunit_spec.js +836 -0
- data/js/spec/spec_helper.rb +106 -0
- data/js/spec/tokenizer_spec.rb +227 -0
- data/js/src/handlebars.l +34 -0
- data/js/src/handlebars.yy +99 -0
- data/lib/handlebars.rb +23 -0
- data/lib/handlebars/loader.rb +29 -0
- data/lib/handlebars/version.rb +3 -0
- data/spec/handlebars_spec.rb +32 -0
- metadata +112 -0
@@ -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
|
+
"<": "<",
|
24
|
+
">": ">",
|
25
|
+
'"': """,
|
26
|
+
"'": "'",
|
27
|
+
"`": "`"
|
28
|
+
};
|
29
|
+
|
30
|
+
var badChars = /&(?!\w+;)|[<>"'`]/g;
|
31
|
+
var possible = /[&<>"'`]/;
|
32
|
+
|
33
|
+
var escapeChar = function(chr) {
|
34
|
+
return escape[chr] || "&";
|
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,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
|