hotcell 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +15 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +17 -0
- data/hotcell.gemspec +22 -0
- data/lib/hotcell/.DS_Store +0 -0
- data/lib/hotcell/config.rb +31 -0
- data/lib/hotcell/context.rb +36 -0
- data/lib/hotcell/errors.rb +43 -0
- data/lib/hotcell/extensions.rb +42 -0
- data/lib/hotcell/lexer.rb +783 -0
- data/lib/hotcell/lexer.rl +299 -0
- data/lib/hotcell/manipulator.rb +31 -0
- data/lib/hotcell/node/arrayer.rb +7 -0
- data/lib/hotcell/node/assigner.rb +11 -0
- data/lib/hotcell/node/block.rb +58 -0
- data/lib/hotcell/node/calculator.rb +35 -0
- data/lib/hotcell/node/command.rb +41 -0
- data/lib/hotcell/node/hasher.rb +7 -0
- data/lib/hotcell/node/joiner.rb +7 -0
- data/lib/hotcell/node/sequencer.rb +7 -0
- data/lib/hotcell/node/summoner.rb +11 -0
- data/lib/hotcell/node/tag.rb +26 -0
- data/lib/hotcell/node.rb +55 -0
- data/lib/hotcell/parser.rb +1186 -0
- data/lib/hotcell/parser.y +231 -0
- data/lib/hotcell/scope.rb +57 -0
- data/lib/hotcell/template.rb +29 -0
- data/lib/hotcell/version.rb +3 -0
- data/lib/hotcell.rb +19 -0
- data/misc/rage.rl +1999 -0
- data/misc/unicode2ragel.rb +305 -0
- data/spec/data/dstrings +8 -0
- data/spec/data/sstrings +6 -0
- data/spec/lib/hotcell/config_spec.rb +57 -0
- data/spec/lib/hotcell/context_spec.rb +53 -0
- data/spec/lib/hotcell/lexer_spec.rb +340 -0
- data/spec/lib/hotcell/manipulator_spec.rb +64 -0
- data/spec/lib/hotcell/node/block_spec.rb +188 -0
- data/spec/lib/hotcell/node/command_spec.rb +71 -0
- data/spec/lib/hotcell/parser_spec.rb +382 -0
- data/spec/lib/hotcell/scope_spec.rb +160 -0
- data/spec/lib/hotcell/template_spec.rb +41 -0
- data/spec/lib/hotcell_spec.rb +8 -0
- data/spec/spec_helper.rb +44 -0
- metadata +139 -0
@@ -0,0 +1,340 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hotcell::Lexer do
|
5
|
+
def scan template
|
6
|
+
described_class.new(template).tokens
|
7
|
+
end
|
8
|
+
|
9
|
+
def expression template
|
10
|
+
scan("{{ #{template} }}")[1..-2]
|
11
|
+
end
|
12
|
+
|
13
|
+
specify { scan('').should == [] }
|
14
|
+
specify { scan('{{ }}').should == [[:TOPEN, "{{"], [:TCLOSE, "}}"]] }
|
15
|
+
specify { expression('').should == [] }
|
16
|
+
|
17
|
+
context 'arithmetic' do
|
18
|
+
specify { expression('+').should == [[:PLUS, '+']] }
|
19
|
+
specify { expression('-').should == [[:MINUS, '-']] }
|
20
|
+
specify { expression('*').should == [[:MULTIPLY, '*']] }
|
21
|
+
specify { expression('* *').should == [[:MULTIPLY, '*'], [:MULTIPLY, '*']] }
|
22
|
+
specify { expression('**').should == [[:POWER, '**']] }
|
23
|
+
specify { expression('/').should == [[:DIVIDE, '/']] }
|
24
|
+
specify { expression('%').should == [[:MODULO, '%']] }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'logic' do
|
28
|
+
specify { expression('&&').should == [[:AND, '&&']] }
|
29
|
+
specify { expression('||').should == [[:OR, '||']] }
|
30
|
+
specify { expression('!').should == [[:NOT, '!']] }
|
31
|
+
specify { expression('!!').should == [[:NOT, '!'], [:NOT, '!']] }
|
32
|
+
specify { expression('==').should == [[:EQUAL, '==']] }
|
33
|
+
specify { expression('!=').should == [[:INEQUAL, '!=']] }
|
34
|
+
specify { expression('! =').should == [[:NOT, '!'], [:ASSIGN, '=']] }
|
35
|
+
specify { expression('>').should == [[:GT, '>']] }
|
36
|
+
specify { expression('>=').should == [[:GTE, '>=']] }
|
37
|
+
specify { expression('> =').should == [[:GT, '>'], [:ASSIGN, '=']] }
|
38
|
+
specify { expression('<').should == [[:LT, '<']] }
|
39
|
+
specify { expression('<=').should == [[:LTE, '<=']] }
|
40
|
+
specify { expression('< =').should == [[:LT, '<'], [:ASSIGN, '=']] }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'flow' do
|
44
|
+
specify { expression('=').should == [[:ASSIGN, '=']] }
|
45
|
+
specify { expression('= =').should == [[:ASSIGN, '='], [:ASSIGN, '=']] }
|
46
|
+
specify { expression(',').should == [[:COMMA, ',']] }
|
47
|
+
specify { expression('.').should == [[:PERIOD, '.']] }
|
48
|
+
specify { expression(':').should == [[:COLON, ':']] }
|
49
|
+
specify { expression('?').should == [[:QUESTION, '?']] }
|
50
|
+
specify { expression('hello?').should == [[:IDENTIFER, 'hello?']] }
|
51
|
+
specify { expression('hello ?').should == [[:IDENTIFER, 'hello'], [:QUESTION, '?']] }
|
52
|
+
specify { expression(';').should == [[:SEMICOLON, ';']] }
|
53
|
+
specify { expression("\n").should == [[:NEWLINE, "\n"]] }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'structure' do
|
57
|
+
specify { expression('[').should == [[:AOPEN, '[']] }
|
58
|
+
specify { expression(']').should == [[:ACLOSE, ']']] }
|
59
|
+
specify { expression('{').should == [[:HOPEN, '{']] }
|
60
|
+
specify { expression('}').should == [[:HCLOSE, '}']] }
|
61
|
+
specify { expression('(').should == [[:POPEN, '(']] }
|
62
|
+
specify { expression(')').should == [[:PCLOSE, ')']] }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'numeric' do
|
66
|
+
context 'integer' do
|
67
|
+
specify { expression('42').should == [[:INTEGER, 42]] }
|
68
|
+
specify { expression('-42').should == [[:MINUS, '-'], [:INTEGER, 42]] }
|
69
|
+
specify { expression('!42').should == [[:NOT, '!'], [:INTEGER, 42]] }
|
70
|
+
specify { expression('42.').should == [[:INTEGER, 42], [:PERIOD, '.']] }
|
71
|
+
specify { expression('42.foo').should == [[:INTEGER, 42], [:PERIOD, '.'], [:IDENTIFER, 'foo']] }
|
72
|
+
specify { expression('42foo').should == [[:INTEGER, 42], [:IDENTIFER, 'foo']] }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'float' do
|
76
|
+
specify { expression('42.42').should == [[:FLOAT, 42.42]] }
|
77
|
+
specify { expression('-42.42').should == [[:MINUS, '-'], [:FLOAT, 42.42]] }
|
78
|
+
specify { expression('!42.42').should == [[:NOT, '!'], [:FLOAT, 42.42]] }
|
79
|
+
specify { expression('0.42').should == [[:FLOAT, 0.42]] }
|
80
|
+
specify { expression('.42').should == [[:FLOAT, 0.42]] }
|
81
|
+
specify { expression('.42.').should == [[:FLOAT, 0.42], [:PERIOD, '.']] }
|
82
|
+
specify { expression('.42.foo').should == [[:FLOAT, 0.42], [:PERIOD, '.'], [:IDENTIFER, 'foo']] }
|
83
|
+
specify { expression('.42foo').should == [[:FLOAT, 0.42], [:IDENTIFER, 'foo']] }
|
84
|
+
specify { expression('..42').should == [[:PERIOD, '.'], [:FLOAT, 0.42]] }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'identifer' do
|
89
|
+
specify { expression('foo').should == [[:IDENTIFER, 'foo']] }
|
90
|
+
specify { expression('foo42').should == [[:IDENTIFER, 'foo42']] }
|
91
|
+
specify { expression('f42').should == [[:IDENTIFER, 'f42']] }
|
92
|
+
specify { expression('foo42bar').should == [[:IDENTIFER, 'foo42bar']] }
|
93
|
+
specify { expression('42foo42').should == [[:INTEGER, 42], [:IDENTIFER, 'foo42']] }
|
94
|
+
specify { expression('foo?').should == [[:IDENTIFER, 'foo?']] }
|
95
|
+
specify { expression('foo?bar').should == [[:IDENTIFER, 'foo?'], [:IDENTIFER, 'bar']] }
|
96
|
+
specify { expression('foo!').should == [[:IDENTIFER, 'foo!']] }
|
97
|
+
specify { expression('foo!bar').should == [[:IDENTIFER, 'foo!'], [:IDENTIFER, 'bar']] }
|
98
|
+
specify { expression('foo.bar').should == [[:IDENTIFER, 'foo'], [:PERIOD, '.'], [:IDENTIFER, 'bar']] }
|
99
|
+
specify { expression('!foo').should == [[:NOT, '!'], [:IDENTIFER, 'foo']] }
|
100
|
+
specify { expression('-foo').should == [[:MINUS, '-'], [:IDENTIFER, 'foo']] }
|
101
|
+
|
102
|
+
context 'constants' do
|
103
|
+
specify { expression('nil').should == [[:NIL, nil]] }
|
104
|
+
specify { expression('"nil"').should == [[:STRING, 'nil']] }
|
105
|
+
specify { expression('null').should == [[:NIL, nil]] }
|
106
|
+
specify { expression('"null"').should == [[:STRING, 'null']] }
|
107
|
+
specify { expression('false').should == [[:FALSE, false]] }
|
108
|
+
specify { expression('"false"').should == [[:STRING, 'false']] }
|
109
|
+
specify { expression('true').should == [[:TRUE, true]] }
|
110
|
+
specify { expression('"true"').should == [[:STRING, 'true']] }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'string' do
|
115
|
+
context 'single quoted' do
|
116
|
+
specify { expression(%q{''}).should == [[:STRING, '']] }
|
117
|
+
specify { expression(%q{'foo'}).should == [[:STRING, 'foo']] }
|
118
|
+
specify { expression(%q{'fo"o'}).should == [[:STRING, 'fo"o']] }
|
119
|
+
specify { expression(%q{'fo\o'}).should == [[:STRING, 'fo\o']] }
|
120
|
+
specify { expression(%q{'fo\'o'}).should == [[:STRING, 'fo\'o']] }
|
121
|
+
specify { expression(%q{'fo\"o'}).should == [[:STRING, 'fo\"o']] }
|
122
|
+
specify { expression(%q{'fo\no'}).should == [[:STRING, 'fo\no']] }
|
123
|
+
specify { expression(%q{'fo\mo'}).should == [[:STRING, 'fo\mo']] }
|
124
|
+
specify { expression(%q{'foo42'}).should == [[:STRING, 'foo42']] }
|
125
|
+
specify { expression(%q{'привет'}).should == [[:STRING, 'привет']] }
|
126
|
+
specify { expression(%q{'при\вет'}).should == [[:STRING, 'при\вет']] }
|
127
|
+
|
128
|
+
context do
|
129
|
+
let(:strings) { data 'sstrings' }
|
130
|
+
|
131
|
+
specify { expression(strings).delete_if { |token| token.first == :NEWLINE }.should == [
|
132
|
+
[:STRING, 'fo\'o'], [:STRING, 'fo\o'], [:STRING, 'fo\\o'],
|
133
|
+
[:STRING, 'fo\no'], [:STRING, "foo\nbar"]
|
134
|
+
] }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'double quoted' do
|
139
|
+
specify { expression(%q{""}).should == [[:STRING, ""]] }
|
140
|
+
specify { expression(%q{"foo"}).should == [[:STRING, "foo"]] }
|
141
|
+
specify { expression(%q{"fo'o"}).should == [[:STRING, "fo'o"]] }
|
142
|
+
specify { expression(%q{"fo\o"}).should == [[:STRING, "fo\o"]] }
|
143
|
+
specify { expression(%q{"fo\"o"}).should == [[:STRING, "fo\"o"]] }
|
144
|
+
specify { expression(%q{"fo\'o"}).should == [[:STRING, "fo\'o"]] }
|
145
|
+
specify { expression(%q{"fo\no"}).should == [[:STRING, "fo\no"]] }
|
146
|
+
specify { expression(%q{"fo\mo"}).should == [[:STRING, "fo\mo"]] }
|
147
|
+
specify { expression(%q{"foo42"}).should == [[:STRING, "foo42"]] }
|
148
|
+
specify { expression(%q{"привет"}).should == [[:STRING, "привет"]] }
|
149
|
+
specify { expression(%q{"при\вет"}).should == [[:STRING, "при\вет"]] }
|
150
|
+
|
151
|
+
context do
|
152
|
+
let(:strings) { data 'dstrings' }
|
153
|
+
|
154
|
+
specify { expression(strings).delete_if { |token| token.first == :NEWLINE }.should == [
|
155
|
+
[:STRING, "fo\"o"], [:STRING, "fo\o"], [:STRING, "fo\\o"],
|
156
|
+
[:STRING, "fo\no"], [:STRING, "fo\mo"], [:STRING, "fo\to"],
|
157
|
+
[:STRING, "foo\nbar"]
|
158
|
+
] }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'regexp' do
|
164
|
+
specify { expression('//').should == [[:REGEXP, //]] }
|
165
|
+
specify { expression('/regexp/').should == [[:REGEXP, /regexp/]] }
|
166
|
+
specify { expression('/regexp/i').should == [[:REGEXP, /regexp/i]] }
|
167
|
+
specify { expression('/regexp/m').should == [[:REGEXP, /regexp/m]] }
|
168
|
+
specify { expression('/regexp/x').should == [[:REGEXP, /regexp/x]] }
|
169
|
+
specify { expression('/regexp/sdmfri').should == [[:REGEXP, /regexp/im]] }
|
170
|
+
specify { expression('/\.*/').should == [[:REGEXP, /\.*/]] }
|
171
|
+
specify { expression('/\//').should == [[:REGEXP, /\//]] }
|
172
|
+
specify { expression('/\//ix').should == [[:REGEXP, /\//ix]] }
|
173
|
+
specify { expression('/регексп/').should == [[:REGEXP, /регексп/]] }
|
174
|
+
specify { expression('/регексп/m').should == [[:REGEXP, /регексп/m]] }
|
175
|
+
|
176
|
+
context 'ambiguity' do
|
177
|
+
specify { expression('hello /regexp/').should == [
|
178
|
+
[:IDENTIFER, "hello"], [:DIVIDE, "/"], [:IDENTIFER, "regexp"], [:DIVIDE, "/"]
|
179
|
+
] }
|
180
|
+
specify { expression('hello(/regexp/)').should == [
|
181
|
+
[:IDENTIFER, "hello"], [:POPEN, "("], [:REGEXP, /regexp/], [:PCLOSE, ")"]
|
182
|
+
] }
|
183
|
+
specify { expression('[/regexp/]').should == [
|
184
|
+
[:AOPEN, "["], [:REGEXP, /regexp/], [:ACLOSE, "]"]
|
185
|
+
] }
|
186
|
+
specify { expression('[42, /regexp/]').should == [
|
187
|
+
[:AOPEN, "["], [:INTEGER, 42], [:COMMA, ","], [:REGEXP, /regexp/], [:ACLOSE, "]"]
|
188
|
+
] }
|
189
|
+
specify { expression('{/regexp/: 42}').should == [
|
190
|
+
[:HOPEN, "{"], [:REGEXP, /regexp/], [:COLON, ":"], [:INTEGER, 42], [:HCLOSE, "}"]
|
191
|
+
] }
|
192
|
+
specify { expression('42 : /regexp/').should == [
|
193
|
+
[:INTEGER, 42], [:COLON, ":"], [:REGEXP, /regexp/]
|
194
|
+
] }
|
195
|
+
specify { expression('42; /regexp/').should == [
|
196
|
+
[:INTEGER, 42], [:SEMICOLON, ";"], [:REGEXP, /regexp/]
|
197
|
+
] }
|
198
|
+
specify { expression('"hello" /regexp/').should == [
|
199
|
+
[:STRING, "hello"], [:DIVIDE, "/"], [:IDENTIFER, "regexp"], [:DIVIDE, "/"]
|
200
|
+
] }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'expression comments' do
|
205
|
+
specify { scan('{{ # }}').should == [[:TOPEN, '{{'], [:COMMENT, '# '], [:TCLOSE, '}}']] }
|
206
|
+
specify { scan('{{ #}}').should == [[:TOPEN, '{{'], [:COMMENT, '#'], [:TCLOSE, '}}']] }
|
207
|
+
specify { scan('{{ #hello }}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello '], [:TCLOSE, '}}']] }
|
208
|
+
specify { scan('{{ #hello}}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello'], [:TCLOSE, '}}']] }
|
209
|
+
specify { scan('{{ #hello}}}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello'], [:TCLOSE, '}}'], [:TEMPLATE, '}']] }
|
210
|
+
specify { scan('{{ #hello} }}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello} '], [:TCLOSE, '}}']] }
|
211
|
+
specify { scan('{{ #hello# }}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello# '], [:TCLOSE, '}}']] }
|
212
|
+
specify { scan('{{ #hello#}}').should == [[:TOPEN, '{{'], [:COMMENT, '#hello#'], [:TCLOSE, '}}']] }
|
213
|
+
specify { scan('{{ #hel}lo#}}').should == [[:TOPEN, '{{'], [:COMMENT, '#hel}lo#'], [:TCLOSE, '}}']] }
|
214
|
+
specify { scan('{{ #hel{lo#}}').should == [[:TOPEN, '{{'], [:COMMENT, '#hel{lo#'], [:TCLOSE, '}}']] }
|
215
|
+
specify { scan("{{ #hel{\nlo#}}").should == [[:TOPEN, "{{"], [:COMMENT, "#hel{"], [:NEWLINE, "\n"],
|
216
|
+
[:IDENTIFER, "lo"], [:COMMENT, "#"], [:TCLOSE, "}}"]] }
|
217
|
+
specify { scan("{{ #hel{#\n#lo#}}").should == [[:TOPEN, "{{"], [:COMMENT, "#hel{#"], [:NEWLINE, "\n"],
|
218
|
+
[:COMMENT, "#lo#"], [:TCLOSE, "}}"]] }
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'errors' do
|
222
|
+
describe Hotcell::Errors::UnexpectedSymbol do
|
223
|
+
let(:error) { Hotcell::Errors::UnexpectedSymbol }
|
224
|
+
|
225
|
+
specify { expect { expression("hello @world") }.to raise_error(error, /1:10/) }
|
226
|
+
specify { expect { expression("@hello world") }.to raise_error(error, /1:4/) }
|
227
|
+
specify { expect { expression("hello world@") }.to raise_error(error, /1:15/) }
|
228
|
+
specify { expect { expression("hello\n@ world") }.to raise_error(error, /2:1/) }
|
229
|
+
specify { expect { expression("hello\n @world") }.to raise_error(error, /2:2/) }
|
230
|
+
specify { expect { expression("hello\n world@") }.to raise_error(error, /2:7/) }
|
231
|
+
specify { expect { expression("hello@\n world") }.to raise_error(error, /1:9/) }
|
232
|
+
specify { expect { expression("@hello\n world") }.to raise_error(error, /1:4/) }
|
233
|
+
specify { expect { expression("'привет' @ 'мир'") }.to raise_error(error, /1:13/) }
|
234
|
+
end
|
235
|
+
|
236
|
+
describe Hotcell::Errors::UnterminatedString do
|
237
|
+
let(:error) { Hotcell::Errors::UnterminatedString }
|
238
|
+
|
239
|
+
specify { expect { expression("hello 'world") }.to raise_error(error, /1:10/) }
|
240
|
+
specify { expect { expression("hello\nwor'ld") }.to raise_error(error, /2:4/) }
|
241
|
+
specify { expect { expression("hello 'world\\'") }.to raise_error(error, /1:10/) }
|
242
|
+
specify { expect { expression("hello 'wor\\'ld") }.to raise_error(error, /1:10/) }
|
243
|
+
specify { expect { expression("\"hello world") }.to raise_error(error, /1:4/) }
|
244
|
+
specify { expect { expression("he\"llo\\\" world") }.to raise_error(error, /1:6/) }
|
245
|
+
specify { expect { expression("he\"llo\\\" \nworld") }.to raise_error(error, /1:6/) }
|
246
|
+
specify { expect { expression("\"hello\\\"\n world") }.to raise_error(error, /1:4/) }
|
247
|
+
specify { expect { expression("'привет' 'мир") }.to raise_error(error, /1:13/) }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'complex expressions' do
|
252
|
+
specify { expression('3+2 * 9').should == [
|
253
|
+
[:INTEGER, 3], [:PLUS, "+"], [:INTEGER, 2],
|
254
|
+
[:MULTIPLY, "*"], [:INTEGER, 9]
|
255
|
+
] }
|
256
|
+
specify { expression('3 / 2 / 9').should == [
|
257
|
+
[:INTEGER, 3], [:DIVIDE, "/"], [:INTEGER, 2],
|
258
|
+
[:DIVIDE, "/"], [:INTEGER, 9]
|
259
|
+
] }
|
260
|
+
specify { expression("foo.bar.baz('hello', 16)").should == [
|
261
|
+
[:IDENTIFER, "foo"], [:PERIOD, "."], [:IDENTIFER, "bar"],
|
262
|
+
[:PERIOD, "."], [:IDENTIFER, "baz"], [:POPEN, "("],
|
263
|
+
[:STRING, "hello"], [:COMMA, ","], [:INTEGER, 16],
|
264
|
+
[:PCLOSE, ")"]
|
265
|
+
] }
|
266
|
+
specify { expression("foo(36.6);\n a = \"привет\"").should == [
|
267
|
+
[:IDENTIFER, "foo"], [:POPEN, "("], [:FLOAT, 36.6],
|
268
|
+
[:PCLOSE, ")"], [:SEMICOLON, ";"], [:NEWLINE, "\n"],
|
269
|
+
[:IDENTIFER, "a"], [:ASSIGN, "="], [:STRING, "привет"]
|
270
|
+
] }
|
271
|
+
specify { expression("'foo'.match(\"^foo$\")").should == [
|
272
|
+
[:STRING, "foo"], [:PERIOD, "."], [:IDENTIFER, "match"],
|
273
|
+
[:POPEN, "("], [:STRING, "^foo$"], [:PCLOSE, ")"]
|
274
|
+
] }
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'templates' do
|
278
|
+
specify { scan(' ').should == [[:TEMPLATE, " "]] }
|
279
|
+
specify { scan('{').should == [[:TEMPLATE, "{"]] }
|
280
|
+
specify { scan('}').should == [[:TEMPLATE, "}"]] }
|
281
|
+
specify { scan('{{').should == [[:TOPEN, "{{"]] }
|
282
|
+
specify { scan('}}').should == [[:TEMPLATE, "}}"]] }
|
283
|
+
specify { scan('hello').should == [[:TEMPLATE, "hello"]] }
|
284
|
+
specify { scan('hel{lo').should == [[:TEMPLATE, "hel{lo"]] }
|
285
|
+
specify { scan('hel{{lo').should == [
|
286
|
+
[:TEMPLATE, "hel"], [:TOPEN, "{{"], [:IDENTIFER, "lo"]
|
287
|
+
] }
|
288
|
+
specify { scan('hel}lo').should == [[:TEMPLATE, "hel}lo"]] }
|
289
|
+
specify { scan('hel}}lo').should == [[:TEMPLATE, "hel}}lo"]] }
|
290
|
+
specify { scan('}}hello').should == [[:TEMPLATE, "}}hello"]] }
|
291
|
+
specify { scan('hello{{').should == [[:TEMPLATE, "hello"], [:TOPEN, "{{"]] }
|
292
|
+
specify { scan('2 + 3').should == [[:TEMPLATE, "2 + 3"]] }
|
293
|
+
specify { scan('hel}}lo{{}}').should == [
|
294
|
+
[:TEMPLATE, "hel}}lo"], [:TOPEN, "{{"], [:TCLOSE, "}}"]
|
295
|
+
] }
|
296
|
+
specify { scan('hel}}lo{{').should == [[:TEMPLATE, "hel}}lo"], [:TOPEN, "{{"]] }
|
297
|
+
specify { scan('hel}}lo{{ !empty').should == [
|
298
|
+
[:TEMPLATE, "hel}}lo"], [:TOPEN, "{{"], [:NOT, "!"], [:IDENTIFER, "empty"]
|
299
|
+
] }
|
300
|
+
specify { scan(' {{ }} ').should == [
|
301
|
+
[:TEMPLATE, " "], [:TOPEN, "{{"], [:TCLOSE, "}}"], [:TEMPLATE, " "]
|
302
|
+
] }
|
303
|
+
specify { scan('2 + 3 {{ 2 || 3 }} hello').should == [
|
304
|
+
[:TEMPLATE, "2 + 3 "], [:TOPEN, "{{"], [:INTEGER, 2],
|
305
|
+
[:OR, "||"], [:INTEGER, 3], [:TCLOSE, "}}"], [:TEMPLATE, " hello"]
|
306
|
+
] }
|
307
|
+
|
308
|
+
context 'tag types' do
|
309
|
+
specify { scan('{{hello}}').should == [
|
310
|
+
[:TOPEN, "{{"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
|
311
|
+
] }
|
312
|
+
specify { scan('{{! hello}}').should == [
|
313
|
+
[:TOPEN, "{{!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
|
314
|
+
] }
|
315
|
+
specify { scan('{{ !hello}}').should == [
|
316
|
+
[:TOPEN, "{{"], [:NOT, "!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
|
317
|
+
] }
|
318
|
+
specify { scan('{{/ hello}}').should == [
|
319
|
+
[:TOPEN, "{{"], [:DIVIDE, "/"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
|
320
|
+
] }
|
321
|
+
specify { scan('{{ /hello}}').should == [
|
322
|
+
[:TOPEN, "{{"], [:DIVIDE, "/"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
|
323
|
+
] }
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'template comments' do
|
328
|
+
specify { scan('{{#').should == [[:COMMENT, "{{#"]] }
|
329
|
+
specify { scan('{{# }}').should == [[:COMMENT, "{{# }}"]] }
|
330
|
+
specify { scan('{{##}}').should == [[:COMMENT, "{{##}}"]] }
|
331
|
+
specify { scan('{{###}}').should == [[:COMMENT, "{{###}}"]] }
|
332
|
+
specify { scan('{{# {{# blabla').should == [[:COMMENT, "{{# {{# blabla"]] }
|
333
|
+
specify { scan('{{# {{# }} blabla').should == [[:COMMENT, "{{# {{# }} blabla"]] }
|
334
|
+
specify { scan('{{# {{ #}} blabla').should == [[:COMMENT, "{{# {{ #}}"], [:TEMPLATE, " blabla"]] }
|
335
|
+
specify { scan('{# {{}}{{# {{# blabla').should == [[:TEMPLATE, "{# "], [:TOPEN, "{{"], [:TCLOSE, "}}"],
|
336
|
+
[:COMMENT, "{{# {{# blabla"]] }
|
337
|
+
specify { scan('{{ } {{# blabla').should == [[:TOPEN, "{{"], [:HCLOSE, "}"], [:HOPEN, "{"],
|
338
|
+
[:HOPEN, "{"], [:COMMENT, "# blabla"]] }
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hotcell::Manipulator do
|
4
|
+
context 'mixed in' do
|
5
|
+
context do
|
6
|
+
let(:klass) do
|
7
|
+
Class.new(Numeric) do
|
8
|
+
include Hotcell::Manipulator::Mixin
|
9
|
+
end
|
10
|
+
end
|
11
|
+
subject { klass.new }
|
12
|
+
|
13
|
+
its(:manipulator_methods) { should be_a Set }
|
14
|
+
its(:manipulator_methods) { should be_empty }
|
15
|
+
its(:to_manipulator) { should === subject }
|
16
|
+
specify { subject.manipulator_invoke('round').should be_nil }
|
17
|
+
specify { subject.manipulator_invoke(:round).should be_nil }
|
18
|
+
specify { subject.manipulator_invoke('round', 42, :arg).should be_nil }
|
19
|
+
end
|
20
|
+
|
21
|
+
context do
|
22
|
+
let(:klass) do
|
23
|
+
Class.new(String) do
|
24
|
+
include Hotcell::Manipulator::Mixin
|
25
|
+
|
26
|
+
manipulator :split, :size
|
27
|
+
end
|
28
|
+
end
|
29
|
+
subject { klass.new('hello world') }
|
30
|
+
|
31
|
+
its('manipulator_methods.to_a') { should =~ %w(split size) }
|
32
|
+
its(:to_manipulator) { should === subject }
|
33
|
+
specify { subject.manipulator_invoke('length').should be_nil }
|
34
|
+
specify { subject.manipulator_invoke('length', 42, :arg).should be_nil }
|
35
|
+
specify { subject.manipulator_invoke(:split).should be_nil }
|
36
|
+
specify { subject.manipulator_invoke('split').should == %w(hello world) }
|
37
|
+
specify { subject.manipulator_invoke('size').should == 11 }
|
38
|
+
specify { expect { subject.manipulator_invoke('size', 42) }.to raise_error ArgumentError }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'inherited' do
|
43
|
+
let(:klass) do
|
44
|
+
Class.new(described_class) do
|
45
|
+
def foo
|
46
|
+
'foo'
|
47
|
+
end
|
48
|
+
|
49
|
+
def bar arg1, arg2
|
50
|
+
arg1 + arg2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
subject { klass.new }
|
55
|
+
|
56
|
+
its('manipulator_methods.to_a') { should =~ %w(foo bar) }
|
57
|
+
its(:to_manipulator) { should === subject }
|
58
|
+
specify { subject.manipulator_invoke('send').should be_nil }
|
59
|
+
specify { subject.manipulator_invoke(:bar).should be_nil }
|
60
|
+
specify { subject.manipulator_invoke('foo').should == 'foo' }
|
61
|
+
specify { subject.manipulator_invoke('bar', 5, 8).should == 13 }
|
62
|
+
specify { expect { subject.manipulator_invoke('bar') }.to raise_error ArgumentError }
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hotcell::Block do
|
4
|
+
def method_missing method, *args, &block
|
5
|
+
klass = Hotcell::Calculator::HANDLERS[method] ?
|
6
|
+
Hotcell::Calculator : Hotcell::Node
|
7
|
+
|
8
|
+
instance = Hotcell::Assigner.new *args if method == :ASSIGN
|
9
|
+
instance = Hotcell::Summoner.new *args if method == :METHOD
|
10
|
+
klass = Hotcell::Arrayer if [:ARRAY, :PAIR].include?(method)
|
11
|
+
klass = Hotcell::Hasher if method == :HASH
|
12
|
+
klass = Hotcell::Sequencer if method == :SEQUENCE
|
13
|
+
|
14
|
+
klass = Hotcell::Tag if method == :TAG
|
15
|
+
instance = Hotcell::Command.new *args if method == :COMMAND
|
16
|
+
instance = Hotcell::Block.new *args if method == :BLOCK
|
17
|
+
klass = Hotcell::Joiner if method == :JOINER
|
18
|
+
|
19
|
+
instance || klass.new(method, *args)
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:context) { Hotcell::Context.new }
|
23
|
+
|
24
|
+
describe 'complex parsing and rendering' do
|
25
|
+
def parse source
|
26
|
+
Hotcell::Template.parse(source)
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:if_tag) do
|
30
|
+
Class.new(described_class) do
|
31
|
+
subcommands :else, :elsif
|
32
|
+
|
33
|
+
def validate!
|
34
|
+
names = subcommands.map { |subcommand| subcommand[:name] }
|
35
|
+
valid = names.empty? || (
|
36
|
+
names.any? && names.last.in?('elsif', 'else') &&
|
37
|
+
names[0..-2].uniq.in?(['elsif'], [])
|
38
|
+
)
|
39
|
+
raise Hotcell::Errors::BlockError.new 'Invalid if syntax' unless valid
|
40
|
+
end
|
41
|
+
|
42
|
+
def process context, subnodes, condition
|
43
|
+
conditions = [[condition]]
|
44
|
+
subnodes.each do |subnode|
|
45
|
+
if subnode.is_a?(Hash)
|
46
|
+
conditions.last[1] = '' if conditions.last[1] == nil
|
47
|
+
conditions << (subnode[:name] == 'elsif' ? [subnode[:args].first] : [true])
|
48
|
+
else
|
49
|
+
conditions.last[1] = subnode
|
50
|
+
end
|
51
|
+
end
|
52
|
+
condition = conditions.detect { |condition| !!condition[0] }
|
53
|
+
condition ? condition[1] : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
before { Hotcell.stub(:commands) { {} } }
|
59
|
+
before { Hotcell.stub(:blocks) { { 'if' => if_tag } } }
|
60
|
+
before { Hotcell.stub(:subcommands) { { 'elsif' => if_tag, 'else' => if_tag } } }
|
61
|
+
|
62
|
+
context do
|
63
|
+
subject(:template) { parse(<<-SOURCE
|
64
|
+
{{ if value == 'hello' }}
|
65
|
+
hello
|
66
|
+
{{ elsif value == 'world' }}
|
67
|
+
world
|
68
|
+
{{ else }}
|
69
|
+
foobar
|
70
|
+
{{ end if }}
|
71
|
+
SOURCE
|
72
|
+
) }
|
73
|
+
specify { subject.render(value: 'hello').strip.should == 'hello' }
|
74
|
+
specify { subject.render(value: 'world').strip.should == 'world' }
|
75
|
+
specify { subject.render(value: 'foo').strip.should == 'foobar' }
|
76
|
+
end
|
77
|
+
|
78
|
+
context do
|
79
|
+
subject(:template) { parse("{{ if value == 'hello' }}{{ elsif value == 'world' }}world{{ end if }}") }
|
80
|
+
specify { subject.render(value: 'hello').should == '' }
|
81
|
+
specify { subject.render(value: 'world').should == 'world' }
|
82
|
+
specify { subject.render(value: 'foo').should == '' }
|
83
|
+
end
|
84
|
+
|
85
|
+
context do
|
86
|
+
subject(:template) { parse("{{ if value == 'hello' }}{{ else }}world{{ end if }}") }
|
87
|
+
specify { subject.render(value: 'hello').should == '' }
|
88
|
+
specify { subject.render(value: 'foo').should == 'world' }
|
89
|
+
end
|
90
|
+
|
91
|
+
context do
|
92
|
+
specify { expect {
|
93
|
+
parse("{{ if value == 'hello' }}{{ else }}world{{ elsif }}{{ end if }}").syntax
|
94
|
+
}.to raise_error Hotcell::Errors::BlockError }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.subcommands' do
|
99
|
+
subject { Class.new(described_class) }
|
100
|
+
|
101
|
+
before { subject.subcommands :elsif, :else }
|
102
|
+
its(:subcommands) { should == %w(elsif else) }
|
103
|
+
|
104
|
+
context do
|
105
|
+
before { subject.subcommands :when }
|
106
|
+
its(:subcommands) { should == %w(elsif else when) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#subcommands' do
|
111
|
+
specify { described_class.new('if',
|
112
|
+
subnodes: [{name: 'elsif'}, JOINER(), {name: 'else'}, JOINER()]).subcommands.should == [
|
113
|
+
{name: 'elsif'}, {name: 'else'}] }
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#render_subnodes' do
|
117
|
+
specify { described_class.new('if',
|
118
|
+
subnodes: [{name: 'elsif', args: ARRAY(3, 5)}, JOINER(42), {name: 'else'}, JOINER(43)]
|
119
|
+
).render_subnodes(context).should == [
|
120
|
+
{name: 'elsif', args: [3, 5]}, '42', {name: 'else'}, '43'
|
121
|
+
] }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#render' do
|
125
|
+
let(:block) do
|
126
|
+
Class.new(described_class) do
|
127
|
+
def process context, subnodes, condition
|
128
|
+
"condition #{condition}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
specify { block.new('if').render(context).should =~ /ArgumentError/ }
|
134
|
+
specify { block.new('if', mode: :silence).render(context).should =~ /ArgumentError/ }
|
135
|
+
specify { block.new('if', true).render(context).should == "condition true" }
|
136
|
+
specify { block.new('if', true, mode: :silence).render(context).should == '' }
|
137
|
+
|
138
|
+
context 'assigning' do
|
139
|
+
before { subject.render(context) }
|
140
|
+
|
141
|
+
context do
|
142
|
+
subject { block.new('if', assign: 'result') }
|
143
|
+
specify { context.key?('result').should be_false }
|
144
|
+
end
|
145
|
+
|
146
|
+
context do
|
147
|
+
subject { block.new('if', mode: :silence, assign: 'result') }
|
148
|
+
specify { context.key?('result').should be_false }
|
149
|
+
end
|
150
|
+
|
151
|
+
context do
|
152
|
+
subject { block.new('if', true, assign: 'result') }
|
153
|
+
specify { context['result'].should == "condition true" }
|
154
|
+
end
|
155
|
+
|
156
|
+
context do
|
157
|
+
subject { block.new('if', 42, mode: :silence, assign: 'result') }
|
158
|
+
specify { context['result'].should == "condition 42" }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context '#validate!' do
|
164
|
+
let(:block) do
|
165
|
+
Class.new(described_class) do
|
166
|
+
subcommands :else
|
167
|
+
def process context, subnodes, condition
|
168
|
+
"condition #{condition}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context do
|
174
|
+
subject { block.new('if', true, subnodes: [TEMPLATE('hello')] ) }
|
175
|
+
specify { expect { subject.validate! }.not_to raise_error }
|
176
|
+
end
|
177
|
+
|
178
|
+
context do
|
179
|
+
subject { block.new('if', true, subnodes: [{ name: 'else' }] ) }
|
180
|
+
specify { expect { subject.validate! }.not_to raise_error }
|
181
|
+
end
|
182
|
+
|
183
|
+
context do
|
184
|
+
subject { block.new('if', true, subnodes: [{ name: 'case' }] ) }
|
185
|
+
specify { expect { subject.validate! }.to raise_error }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hotcell::Command do
|
4
|
+
let(:context) { Hotcell::Context.new }
|
5
|
+
|
6
|
+
describe 'complex parsing and rendering' do
|
7
|
+
def parse source
|
8
|
+
Hotcell::Template.parse(source)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:include_tag) do
|
12
|
+
Class.new(described_class) do
|
13
|
+
def validate!
|
14
|
+
raise Hotcell::Errors::ArgumentError.new('Template path is required') if children.count != 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def process context, path
|
18
|
+
"included #{path}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before { Hotcell.stub(:commands) { { 'include' => include_tag } } }
|
24
|
+
before { Hotcell.stub(:blocks) { {} } }
|
25
|
+
before { Hotcell.stub(:subcommands) { {} } }
|
26
|
+
|
27
|
+
specify { parse("{{ include 'template/path' }}").render(context).should == 'included template/path' }
|
28
|
+
specify { expect {
|
29
|
+
parse("{{ include }}").syntax
|
30
|
+
}.to raise_error Hotcell::Errors::ArgumentError }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#render' do
|
34
|
+
let(:command) do
|
35
|
+
Class.new(described_class) do
|
36
|
+
def process context, path
|
37
|
+
"rendered #{path}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
specify { command.new('include').render(context).should =~ /ArgumentError/ }
|
43
|
+
specify { command.new('include', mode: :silence).render(context).should =~ /ArgumentError/ }
|
44
|
+
specify { command.new('include', 'path').render(context).should == "rendered path" }
|
45
|
+
specify { command.new('include', 'path', mode: :silence).render(context).should == '' }
|
46
|
+
|
47
|
+
context 'assigning' do
|
48
|
+
before { subject.render(context) }
|
49
|
+
|
50
|
+
context do
|
51
|
+
subject { command.new('include', assign: 'inclusion') }
|
52
|
+
specify { context.key?('inclusion').should be_false }
|
53
|
+
end
|
54
|
+
|
55
|
+
context do
|
56
|
+
subject { command.new('include', mode: :silence, assign: 'inclusion') }
|
57
|
+
specify { context.key?('inclusion').should be_false }
|
58
|
+
end
|
59
|
+
|
60
|
+
context do
|
61
|
+
subject { command.new('include', 'path', assign: 'inclusion') }
|
62
|
+
specify { context['inclusion'].should == "rendered path" }
|
63
|
+
end
|
64
|
+
|
65
|
+
context do
|
66
|
+
subject { command.new('include', 'path', mode: :silence, assign: 'inclusion') }
|
67
|
+
specify { context['inclusion'].should == "rendered path" }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|