handlebars 0.0.2 → 0.2.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 +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
|
+
|