handlebars 0.0.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +1 -1
- data/README.mdown +44 -0
- data/Rakefile +3 -0
- data/handlebars.gemspec +19 -13
- data/lib/handlebars.rb +4 -3
- data/lib/handlebars/context.rb +37 -0
- data/lib/handlebars/version.rb +1 -1
- data/spec/handlebars_spec.rb +40 -0
- data/spike.rb +17 -0
- data/vendor/handlebars/.gitignore +6 -0
- data/vendor/handlebars/.jshintrc +50 -0
- data/vendor/handlebars/.npmignore +11 -0
- data/vendor/handlebars/Gemfile +5 -0
- data/vendor/handlebars/LICENSE +20 -0
- data/vendor/handlebars/README.markdown +315 -0
- data/vendor/handlebars/Rakefile +116 -0
- data/vendor/handlebars/bench/benchwarmer.js +149 -0
- data/vendor/handlebars/bench/handlebars.js +163 -0
- data/vendor/handlebars/bin/handlebars +139 -0
- data/vendor/handlebars/lib/handlebars.js +14 -0
- data/vendor/handlebars/lib/handlebars/base.js +101 -0
- data/vendor/handlebars/lib/handlebars/compiler/ast.js +103 -0
- data/vendor/handlebars/lib/handlebars/compiler/base.js +27 -0
- data/vendor/handlebars/lib/handlebars/compiler/compiler.js +808 -0
- data/vendor/handlebars/lib/handlebars/compiler/index.js +7 -0
- data/vendor/handlebars/lib/handlebars/compiler/printer.js +137 -0
- data/vendor/handlebars/lib/handlebars/compiler/visitor.js +13 -0
- data/vendor/handlebars/lib/handlebars/runtime.js +68 -0
- data/vendor/handlebars/lib/handlebars/utils.js +68 -0
- data/vendor/handlebars/package.json +25 -0
- data/vendor/handlebars/spec/acceptance_spec.rb +101 -0
- data/vendor/handlebars/spec/parser_spec.rb +264 -0
- data/vendor/handlebars/spec/qunit_spec.js +1067 -0
- data/vendor/handlebars/spec/spec_helper.rb +157 -0
- data/vendor/handlebars/spec/tokenizer_spec.rb +254 -0
- data/vendor/handlebars/src/handlebars.l +42 -0
- data/vendor/handlebars/src/handlebars.yy +99 -0
- metadata +93 -77
- data/README.md +0 -39
- data/lib/handlebars/generator.rb +0 -4
- data/lib/handlebars/parser.rb +0 -240
- data/spec/generator_spec.rb +0 -5
- data/spec/parser_spec.rb +0 -163
- data/spec/spec_helper.rb +0 -17
@@ -0,0 +1,157 @@
|
|
1
|
+
require "v8"
|
2
|
+
|
3
|
+
# Monkey patches due to bugs in RubyRacer
|
4
|
+
class V8::JSError
|
5
|
+
def initialize(try, to)
|
6
|
+
@to = to
|
7
|
+
begin
|
8
|
+
super(initialize_unsafe(try))
|
9
|
+
rescue Exception => e
|
10
|
+
# Original code does not make an Array here
|
11
|
+
@boundaries = [Boundary.new(:rbframes => e.backtrace)]
|
12
|
+
@value = e
|
13
|
+
super("BUG! please report. JSError#initialize failed!: #{e.message}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_js_frames(try)
|
18
|
+
raw = @to.rb(try.StackTrace())
|
19
|
+
if raw && !raw.empty?
|
20
|
+
raw.split("\n")[1..-1].tap do |frames|
|
21
|
+
# Original code uses strip!, and the frames are not guaranteed to be strippable
|
22
|
+
frames.each {|frame| frame.strip.chomp!(",")}
|
23
|
+
end
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Handlebars
|
31
|
+
module Spec
|
32
|
+
def self.js_backtrace(context)
|
33
|
+
begin
|
34
|
+
context.eval("throw")
|
35
|
+
rescue V8::JSError => e
|
36
|
+
return e.backtrace(:javascript)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.remove_exports(string)
|
41
|
+
match = string.match(%r{\A(.*?)^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)(.*?)\Z}m)
|
42
|
+
prelines = match ? match[1].count("\n") + 1 : 0
|
43
|
+
ret = match ? match[2] : string
|
44
|
+
("\n" * prelines) + ret
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.load_helpers(context)
|
48
|
+
context["exports"] = nil
|
49
|
+
|
50
|
+
context["p"] = proc do |val|
|
51
|
+
p val if ENV["DEBUG_JS"]
|
52
|
+
end
|
53
|
+
|
54
|
+
context["puts"] = proc do |val|
|
55
|
+
puts val if ENV["DEBUG_JS"]
|
56
|
+
end
|
57
|
+
|
58
|
+
context["puts_node"] = proc do |val|
|
59
|
+
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
60
|
+
puts
|
61
|
+
end
|
62
|
+
|
63
|
+
context["puts_caller"] = proc do
|
64
|
+
puts "BACKTRACE:"
|
65
|
+
puts Handlebars::Spec.js_backtrace(context)
|
66
|
+
puts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.js_load(context, file)
|
71
|
+
str = File.read(file)
|
72
|
+
context.eval(remove_exports(str), file)
|
73
|
+
end
|
74
|
+
|
75
|
+
CONTEXT = V8::Context.new
|
76
|
+
CONTEXT.instance_eval do |context|
|
77
|
+
Handlebars::Spec.load_helpers(context);
|
78
|
+
|
79
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
80
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
81
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
|
82
|
+
|
83
|
+
context["CompilerContext"] = {}
|
84
|
+
CompilerContext = context["CompilerContext"]
|
85
|
+
CompilerContext["compile"] = proc do |*args|
|
86
|
+
template, options = args[0], args[1] || nil
|
87
|
+
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
|
88
|
+
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
|
89
|
+
end
|
90
|
+
CompilerContext["compileWithPartial"] = proc do |*args|
|
91
|
+
template, options = args[0], args[1] || nil
|
92
|
+
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
COMPILE_CONTEXT = V8::Context.new
|
97
|
+
COMPILE_CONTEXT.instance_eval do |context|
|
98
|
+
Handlebars::Spec.load_helpers(context);
|
99
|
+
|
100
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
101
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
102
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
103
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
|
104
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
|
105
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
106
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
107
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
|
108
|
+
|
109
|
+
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
110
|
+
|
111
|
+
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
112
|
+
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
113
|
+
|
114
|
+
if logger_level <= level
|
115
|
+
puts str
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
FULL_CONTEXT = V8::Context.new
|
121
|
+
FULL_CONTEXT.instance_eval do |context|
|
122
|
+
Handlebars::Spec.load_helpers(context);
|
123
|
+
|
124
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
125
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
126
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
127
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
|
128
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
|
129
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
130
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
131
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
|
132
|
+
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
|
133
|
+
|
134
|
+
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
135
|
+
|
136
|
+
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
137
|
+
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
138
|
+
|
139
|
+
if logger_level <= level
|
140
|
+
puts str
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
require "test/unit/assertions"
|
149
|
+
|
150
|
+
RSpec.configure do |config|
|
151
|
+
config.include Test::Unit::Assertions
|
152
|
+
|
153
|
+
# Each is required to allow classes to mark themselves as compiler tests
|
154
|
+
config.before(:each) do
|
155
|
+
@context = @compiles ? Handlebars::Spec::COMPILE_CONTEXT : Handlebars::Spec::CONTEXT
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "timeout"
|
3
|
+
|
4
|
+
describe "Tokenizer" do
|
5
|
+
let(:parser) { @context["handlebars"] }
|
6
|
+
let(:lexer) { @context["handlebars"]["lexer"] }
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
@compiles = true
|
10
|
+
end
|
11
|
+
Token = Struct.new(:name, :text)
|
12
|
+
|
13
|
+
def tokenize(string)
|
14
|
+
lexer.setInput(string)
|
15
|
+
out = []
|
16
|
+
|
17
|
+
while token = lexer.lex
|
18
|
+
# p token
|
19
|
+
result = parser.terminals_[token] || token
|
20
|
+
# p result
|
21
|
+
break if !result || result == "EOF" || result == "INVALID"
|
22
|
+
out << Token.new(result, lexer.yytext)
|
23
|
+
end
|
24
|
+
|
25
|
+
out
|
26
|
+
end
|
27
|
+
|
28
|
+
RSpec::Matchers.define :match_tokens do |tokens|
|
29
|
+
match do |result|
|
30
|
+
result.map(&:name).should == tokens
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Matchers.define :be_token do |name, string|
|
35
|
+
match do |token|
|
36
|
+
token.name.should == name
|
37
|
+
token.text.should == string
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "tokenizes a simple mustache as 'OPEN ID CLOSE'" do
|
42
|
+
result = tokenize("{{foo}}")
|
43
|
+
result.should match_tokens(%w(OPEN ID CLOSE))
|
44
|
+
result[1].should be_token("ID", "foo")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "supports escaping delimiters" do
|
48
|
+
result = tokenize("{{foo}} \\{{bar}} {{baz}}")
|
49
|
+
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
50
|
+
|
51
|
+
result[4].should be_token("CONTENT", "{{bar}} ")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "supports escaping a triple stash" do
|
55
|
+
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
56
|
+
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
57
|
+
|
58
|
+
result[4].should be_token("CONTENT", "{{{bar}}} ")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "tokenizes a simple path" do
|
62
|
+
result = tokenize("{{foo/bar}}")
|
63
|
+
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
64
|
+
end
|
65
|
+
|
66
|
+
it "allows dot notation" do
|
67
|
+
result = tokenize("{{foo.bar}}")
|
68
|
+
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
69
|
+
|
70
|
+
tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
71
|
+
end
|
72
|
+
|
73
|
+
it "allows path literals with []" do
|
74
|
+
result = tokenize("{{foo.[bar]}}")
|
75
|
+
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
76
|
+
end
|
77
|
+
|
78
|
+
it "allows multiple path literals on a line with []" do
|
79
|
+
result = tokenize("{{foo.[bar]}}{{foo.[baz]}}")
|
80
|
+
result.should match_tokens(%w(OPEN ID SEP ID CLOSE OPEN ID SEP ID CLOSE))
|
81
|
+
end
|
82
|
+
|
83
|
+
it "tokenizes {{.}} as OPEN ID CLOSE" do
|
84
|
+
result = tokenize("{{.}}")
|
85
|
+
result.should match_tokens(%w(OPEN ID CLOSE))
|
86
|
+
end
|
87
|
+
|
88
|
+
it "tokenizes a path as 'OPEN (ID SEP)* ID CLOSE'" do
|
89
|
+
result = tokenize("{{../foo/bar}}")
|
90
|
+
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
91
|
+
result[1].should be_token("ID", "..")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "tokenizes a path with .. as a parent path" do
|
95
|
+
result = tokenize("{{../foo.bar}}")
|
96
|
+
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
97
|
+
result[1].should be_token("ID", "..")
|
98
|
+
end
|
99
|
+
|
100
|
+
it "tokenizes a path with this/foo as OPEN ID SEP ID CLOSE" do
|
101
|
+
result = tokenize("{{this/foo}}")
|
102
|
+
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
103
|
+
result[1].should be_token("ID", "this")
|
104
|
+
result[3].should be_token("ID", "foo")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "tokenizes a simple mustache with spaces as 'OPEN ID CLOSE'" do
|
108
|
+
result = tokenize("{{ foo }}")
|
109
|
+
result.should match_tokens(%w(OPEN ID CLOSE))
|
110
|
+
result[1].should be_token("ID", "foo")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "tokenizes a simple mustache with line breaks as 'OPEN ID ID CLOSE'" do
|
114
|
+
result = tokenize("{{ foo \n bar }}")
|
115
|
+
result.should match_tokens(%w(OPEN ID ID CLOSE))
|
116
|
+
result[1].should be_token("ID", "foo")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "tokenizes raw content as 'CONTENT'" do
|
120
|
+
result = tokenize("foo {{ bar }} baz")
|
121
|
+
result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT))
|
122
|
+
result[0].should be_token("CONTENT", "foo ")
|
123
|
+
result[4].should be_token("CONTENT", " baz")
|
124
|
+
end
|
125
|
+
|
126
|
+
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
|
127
|
+
result = tokenize("{{> foo}}")
|
128
|
+
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
129
|
+
end
|
130
|
+
|
131
|
+
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
|
132
|
+
result = tokenize("{{> foo bar }}")
|
133
|
+
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
|
134
|
+
end
|
135
|
+
|
136
|
+
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
137
|
+
result = tokenize("{{>foo}}")
|
138
|
+
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
139
|
+
end
|
140
|
+
|
141
|
+
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
142
|
+
result = tokenize("{{>foo }}")
|
143
|
+
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
144
|
+
end
|
145
|
+
|
146
|
+
it "tokenizes a comment as 'COMMENT'" do
|
147
|
+
result = tokenize("foo {{! this is a comment }} bar {{ baz }}")
|
148
|
+
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
149
|
+
result[1].should be_token("COMMENT", " this is a comment ")
|
150
|
+
end
|
151
|
+
|
152
|
+
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
153
|
+
result = tokenize("{{#foo}}content{{/foo}}")
|
154
|
+
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
155
|
+
end
|
156
|
+
|
157
|
+
it "tokenizes inverse sections as 'OPEN_INVERSE CLOSE'" do
|
158
|
+
tokenize("{{^}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
159
|
+
tokenize("{{else}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
160
|
+
tokenize("{{ else }}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
161
|
+
end
|
162
|
+
|
163
|
+
it "tokenizes inverse sections with ID as 'OPEN_INVERSE ID CLOSE'" do
|
164
|
+
result = tokenize("{{^foo}}")
|
165
|
+
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
|
166
|
+
result[1].should be_token("ID", "foo")
|
167
|
+
end
|
168
|
+
|
169
|
+
it "tokenizes inverse sections with ID and spaces as 'OPEN_INVERSE ID CLOSE'" do
|
170
|
+
result = tokenize("{{^ foo }}")
|
171
|
+
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
|
172
|
+
result[1].should be_token("ID", "foo")
|
173
|
+
end
|
174
|
+
|
175
|
+
it "tokenizes mustaches with params as 'OPEN ID ID ID CLOSE'" do
|
176
|
+
result = tokenize("{{ foo bar baz }}")
|
177
|
+
result.should match_tokens(%w(OPEN ID ID ID CLOSE))
|
178
|
+
result[1].should be_token("ID", "foo")
|
179
|
+
result[2].should be_token("ID", "bar")
|
180
|
+
result[3].should be_token("ID", "baz")
|
181
|
+
end
|
182
|
+
|
183
|
+
it "tokenizes mustaches with String params as 'OPEN ID ID STRING CLOSE'" do
|
184
|
+
result = tokenize("{{ foo bar \"baz\" }}")
|
185
|
+
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
186
|
+
result[3].should be_token("STRING", "baz")
|
187
|
+
end
|
188
|
+
|
189
|
+
it "tokenizes String params with spaces inside as 'STRING'" do
|
190
|
+
result = tokenize("{{ foo bar \"baz bat\" }}")
|
191
|
+
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
192
|
+
result[3].should be_token("STRING", "baz bat")
|
193
|
+
end
|
194
|
+
|
195
|
+
it "tokenizes String params with escapes quotes as 'STRING'" do
|
196
|
+
result = tokenize(%|{{ foo "bar\\"baz" }}|)
|
197
|
+
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
198
|
+
result[2].should be_token("STRING", %{bar"baz})
|
199
|
+
end
|
200
|
+
|
201
|
+
it "tokenizes numbers" do
|
202
|
+
result = tokenize(%|{{ foo 1 }}|)
|
203
|
+
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
204
|
+
result[2].should be_token("INTEGER", "1")
|
205
|
+
end
|
206
|
+
|
207
|
+
it "tokenizes booleans" do
|
208
|
+
result = tokenize(%|{{ foo true }}|)
|
209
|
+
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
210
|
+
result[2].should be_token("BOOLEAN", "true")
|
211
|
+
|
212
|
+
result = tokenize(%|{{ foo false }}|)
|
213
|
+
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
214
|
+
result[2].should be_token("BOOLEAN", "false")
|
215
|
+
end
|
216
|
+
|
217
|
+
it "tokenizes hash arguments" do
|
218
|
+
result = tokenize("{{ foo bar=baz }}")
|
219
|
+
result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE)
|
220
|
+
|
221
|
+
result = tokenize("{{ foo bar baz=bat }}")
|
222
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
223
|
+
|
224
|
+
result = tokenize("{{ foo bar baz=1 }}")
|
225
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE)
|
226
|
+
|
227
|
+
result = tokenize("{{ foo bar baz=true }}")
|
228
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
229
|
+
|
230
|
+
result = tokenize("{{ foo bar baz=false }}")
|
231
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
232
|
+
|
233
|
+
result = tokenize("{{ foo bar\n baz=bat }}")
|
234
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
235
|
+
|
236
|
+
result = tokenize("{{ foo bar baz=\"bat\" }}")
|
237
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE)
|
238
|
+
|
239
|
+
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}")
|
240
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING ID EQUALS ID CLOSE)
|
241
|
+
|
242
|
+
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}")
|
243
|
+
result.should match_tokens %w(OPEN ID ID ID EQUALS ID ID EQUALS STRING CLOSE)
|
244
|
+
result[2].should be_token("ID", "omg")
|
245
|
+
end
|
246
|
+
|
247
|
+
it "does not time out in a mustache with a single } followed by EOF" do
|
248
|
+
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
|
249
|
+
end
|
250
|
+
|
251
|
+
it "does not time out in a mustache when invalid ID characters are used" do
|
252
|
+
Timeout.timeout(1) { tokenize("{{foo & }}").should match_tokens(%w(OPEN ID)) }
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
%x mu emu
|
3
|
+
|
4
|
+
%%
|
5
|
+
|
6
|
+
[^\x00]*?/("{{") {
|
7
|
+
if(yytext.slice(-1) !== "\\") this.begin("mu");
|
8
|
+
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
|
9
|
+
if(yytext) return 'CONTENT';
|
10
|
+
}
|
11
|
+
|
12
|
+
[^\x00]+ { return 'CONTENT'; }
|
13
|
+
|
14
|
+
<emu>[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; }
|
15
|
+
|
16
|
+
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
17
|
+
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
18
|
+
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
19
|
+
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
20
|
+
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
21
|
+
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
22
|
+
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
23
|
+
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
24
|
+
<mu>"{{" { return 'OPEN'; }
|
25
|
+
|
26
|
+
<mu>"=" { return 'EQUALS'; }
|
27
|
+
<mu>"."/[} ] { return 'ID'; }
|
28
|
+
<mu>".." { return 'ID'; }
|
29
|
+
<mu>[\/.] { return 'SEP'; }
|
30
|
+
<mu>\s+ { /*ignore whitespace*/ }
|
31
|
+
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
32
|
+
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
33
|
+
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
34
|
+
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
35
|
+
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
36
|
+
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
|
37
|
+
<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
|
38
|
+
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
39
|
+
<mu>. { return 'INVALID'; }
|
40
|
+
|
41
|
+
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
42
|
+
|