hotcell 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +15 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +17 -0
  9. data/hotcell.gemspec +22 -0
  10. data/lib/hotcell/.DS_Store +0 -0
  11. data/lib/hotcell/config.rb +31 -0
  12. data/lib/hotcell/context.rb +36 -0
  13. data/lib/hotcell/errors.rb +43 -0
  14. data/lib/hotcell/extensions.rb +42 -0
  15. data/lib/hotcell/lexer.rb +783 -0
  16. data/lib/hotcell/lexer.rl +299 -0
  17. data/lib/hotcell/manipulator.rb +31 -0
  18. data/lib/hotcell/node/arrayer.rb +7 -0
  19. data/lib/hotcell/node/assigner.rb +11 -0
  20. data/lib/hotcell/node/block.rb +58 -0
  21. data/lib/hotcell/node/calculator.rb +35 -0
  22. data/lib/hotcell/node/command.rb +41 -0
  23. data/lib/hotcell/node/hasher.rb +7 -0
  24. data/lib/hotcell/node/joiner.rb +7 -0
  25. data/lib/hotcell/node/sequencer.rb +7 -0
  26. data/lib/hotcell/node/summoner.rb +11 -0
  27. data/lib/hotcell/node/tag.rb +26 -0
  28. data/lib/hotcell/node.rb +55 -0
  29. data/lib/hotcell/parser.rb +1186 -0
  30. data/lib/hotcell/parser.y +231 -0
  31. data/lib/hotcell/scope.rb +57 -0
  32. data/lib/hotcell/template.rb +29 -0
  33. data/lib/hotcell/version.rb +3 -0
  34. data/lib/hotcell.rb +19 -0
  35. data/misc/rage.rl +1999 -0
  36. data/misc/unicode2ragel.rb +305 -0
  37. data/spec/data/dstrings +8 -0
  38. data/spec/data/sstrings +6 -0
  39. data/spec/lib/hotcell/config_spec.rb +57 -0
  40. data/spec/lib/hotcell/context_spec.rb +53 -0
  41. data/spec/lib/hotcell/lexer_spec.rb +340 -0
  42. data/spec/lib/hotcell/manipulator_spec.rb +64 -0
  43. data/spec/lib/hotcell/node/block_spec.rb +188 -0
  44. data/spec/lib/hotcell/node/command_spec.rb +71 -0
  45. data/spec/lib/hotcell/parser_spec.rb +382 -0
  46. data/spec/lib/hotcell/scope_spec.rb +160 -0
  47. data/spec/lib/hotcell/template_spec.rb +41 -0
  48. data/spec/lib/hotcell_spec.rb +8 -0
  49. data/spec/spec_helper.rb +44 -0
  50. 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