hotcell 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +102 -84
- data/Rakefile +2 -2
- data/ext/lexerc/lexerc.c +350 -308
- data/ext/lexerc/lexerc.h +2 -7
- data/ext/lexerc/lexerc.rl +9 -90
- data/lib/hotcell/commands/for.rb +7 -2
- data/lib/hotcell/config.rb +2 -1
- data/lib/hotcell/extensions.rb +13 -1
- data/lib/hotcell/lexer.rb +5 -10
- data/lib/hotcell/lexer.rl +95 -0
- data/lib/hotcell/lexerr.rb +256 -215
- data/lib/hotcell/lexerr.rl +7 -91
- data/lib/hotcell/manipulator.rb +20 -2
- data/lib/hotcell/node/{calculator.rb → expression.rb} +4 -2
- data/lib/hotcell/node/tag.rb +5 -3
- data/lib/hotcell/node.rb +1 -1
- data/lib/hotcell/parser.rb +565 -514
- data/lib/hotcell/parser.y +46 -24
- data/lib/hotcell/template.rb +3 -2
- data/lib/hotcell/version.rb +1 -1
- data/lib/hotcell.rb +1 -1
- data/spec/lib/hotcell/commands/for_spec.rb +3 -0
- data/spec/lib/hotcell/config_spec.rb +6 -0
- data/spec/lib/hotcell/lexer_spec.rb +28 -17
- data/spec/lib/hotcell/manipulator_spec.rb +16 -11
- data/spec/lib/hotcell/node/block_spec.rb +2 -2
- data/spec/lib/hotcell/parser_spec.rb +113 -30
- data/spec/lib/hotcell/template_spec.rb +51 -1
- data/spec/lib/hotcell_spec.rb +1 -0
- metadata +4 -3
data/lib/hotcell/parser.y
CHANGED
@@ -45,6 +45,7 @@ prechigh
|
|
45
45
|
nonassoc EQUAL INEQUAL
|
46
46
|
left AND
|
47
47
|
left OR
|
48
|
+
nonassoc RANGE
|
48
49
|
right TERNARY
|
49
50
|
right ASSIGN
|
50
51
|
nonassoc COMMA COLON
|
@@ -57,9 +58,16 @@ rule
|
|
57
58
|
document_unit: template | tag | block_tag | command_tag
|
58
59
|
|
59
60
|
template: TEMPLATE { result = val[0] }
|
60
|
-
tag: TOPEN TCLOSE {
|
61
|
+
tag: TOPEN TCLOSE {
|
62
|
+
result = build Tag, :TAG,
|
63
|
+
mode: tag_modes(val[0], @escape_tags ? :escape : :normal),
|
64
|
+
position: pospoppush(2)
|
65
|
+
}
|
61
66
|
| TOPEN sequence TCLOSE {
|
62
|
-
result = build Tag, :TAG,
|
67
|
+
result = build Tag, :TAG,
|
68
|
+
*Array.wrap(val[1]).flatten,
|
69
|
+
mode: tag_modes(val[0], @escape_tags ? :escape : :normal),
|
70
|
+
position: pospoppush(3)
|
63
71
|
}
|
64
72
|
|
65
73
|
command_body: COMMAND { result = build @commands[val[0]] || Command, val[0], position: pospoppush(1) }
|
@@ -73,7 +81,7 @@ rule
|
|
73
81
|
command_tag: TOPEN command TCLOSE {
|
74
82
|
command = val[1].is_a?(Command) ? val[1] : val[1].children[0]
|
75
83
|
command.validate!
|
76
|
-
result = build Tag, :TAG, val[1], mode:
|
84
|
+
result = build Tag, :TAG, val[1], mode: tag_modes(val[0]), position: pospoppush(3)
|
77
85
|
}
|
78
86
|
|
79
87
|
subcommand: SUBCOMMAND { result = build @substack.last[val[0]], val[0], position: pospoppush(1) }
|
@@ -94,7 +102,7 @@ rule
|
|
94
102
|
| END BLOCK { pospoppush(2) }
|
95
103
|
| END
|
96
104
|
block_open_tag: TOPEN block_open TCLOSE {
|
97
|
-
result = build Tag, :TAG, val[1], mode:
|
105
|
+
result = build Tag, :TAG, val[1], mode: tag_modes(val[0]), position: pospoppush(3)
|
98
106
|
}
|
99
107
|
block_close_tag: TOPEN block_close TCLOSE { pospoppush(3) }
|
100
108
|
block_subnodes: block_subnodes document_unit {
|
@@ -130,27 +138,27 @@ rule
|
|
130
138
|
| NEWLINE { result = [] }
|
131
139
|
| expr { result = [val[0]] }
|
132
140
|
|
133
|
-
expr: expr MULTIPLY expr { result = build
|
134
|
-
| expr POWER expr { result = build
|
135
|
-
| expr DIVIDE expr { result = build
|
136
|
-
| expr PLUS expr { result = build
|
137
|
-
| expr MINUS expr { result = build
|
138
|
-
| expr MODULO expr { result = build
|
139
|
-
| MINUS expr =UMINUS { result = build
|
140
|
-
| PLUS expr =UPLUS { result = build
|
141
|
-
| expr AND expr { result = build
|
142
|
-
| expr OR expr { result = build
|
143
|
-
| expr GT expr { result = build
|
144
|
-
| expr GTE expr { result = build
|
145
|
-
| expr LT expr { result = build
|
146
|
-
| expr LTE expr { result = build
|
147
|
-
| expr EQUAL expr { result = build
|
148
|
-
| expr INEQUAL expr { result = build
|
149
|
-
| NOT expr { result = build
|
141
|
+
expr: expr MULTIPLY expr { result = build Expression, :MULTIPLY, val[0], val[2], position: pospoppush(3) }
|
142
|
+
| expr POWER expr { result = build Expression, :POWER, val[0], val[2], position: pospoppush(3) }
|
143
|
+
| expr DIVIDE expr { result = build Expression, :DIVIDE, val[0], val[2], position: pospoppush(3) }
|
144
|
+
| expr PLUS expr { result = build Expression, :PLUS, val[0], val[2], position: pospoppush(3) }
|
145
|
+
| expr MINUS expr { result = build Expression, :MINUS, val[0], val[2], position: pospoppush(3) }
|
146
|
+
| expr MODULO expr { result = build Expression, :MODULO, val[0], val[2], position: pospoppush(3) }
|
147
|
+
| MINUS expr =UMINUS { result = build Expression, :UMINUS, val[1], position: pospoppush(2) }
|
148
|
+
| PLUS expr =UPLUS { result = build Expression, :UPLUS, val[1], position: pospoppush(2) }
|
149
|
+
| expr AND expr { result = build Expression, :AND, val[0], val[2], position: pospoppush(3) }
|
150
|
+
| expr OR expr { result = build Expression, :OR, val[0], val[2], position: pospoppush(3) }
|
151
|
+
| expr GT expr { result = build Expression, :GT, val[0], val[2], position: pospoppush(3) }
|
152
|
+
| expr GTE expr { result = build Expression, :GTE, val[0], val[2], position: pospoppush(3) }
|
153
|
+
| expr LT expr { result = build Expression, :LT, val[0], val[2], position: pospoppush(3) }
|
154
|
+
| expr LTE expr { result = build Expression, :LTE, val[0], val[2], position: pospoppush(3) }
|
155
|
+
| expr EQUAL expr { result = build Expression, :EQUAL, val[0], val[2], position: pospoppush(3) }
|
156
|
+
| expr INEQUAL expr { result = build Expression, :INEQUAL, val[0], val[2], position: pospoppush(3) }
|
157
|
+
| NOT expr { result = build Expression, :NOT, val[1], position: pospoppush(2) }
|
150
158
|
| IDENTIFER ASSIGN expr { result = build Assigner, val[0], val[2], position: pospoppush(3) }
|
151
159
|
| expr PERIOD method { pospoppush(3); val[2].children[0] = val[0]; result = val[2] }
|
152
160
|
| expr AOPEN arguments ACLOSE {
|
153
|
-
result = build Summoner, '
|
161
|
+
result = build Summoner, '[]', val[0], *val[2], position: pospoppush(4)
|
154
162
|
}
|
155
163
|
| POPEN PCLOSE { pospoppush(2); result = nil }
|
156
164
|
| POPEN sequence PCLOSE {
|
@@ -164,12 +172,17 @@ rule
|
|
164
172
|
}
|
165
173
|
| value
|
166
174
|
|
167
|
-
value: const | number | string | array | hash | method
|
175
|
+
value: const | number | string | range | array | hash | method
|
168
176
|
|
169
177
|
const: NIL | TRUE | FALSE
|
170
178
|
number: INTEGER | FLOAT
|
171
179
|
string: STRING | REGEXP
|
172
180
|
|
181
|
+
range: expr RANGE expr {
|
182
|
+
result = build Expression, val[1] == '..' ? :RANGE : :ERANGE,
|
183
|
+
val[0], val[2], position: pospoppush(3)
|
184
|
+
}
|
185
|
+
|
173
186
|
array: AOPEN ACLOSE { result = build Arrayer, :ARRAY, position: pospoppush(2) }
|
174
187
|
| AOPEN params ACLOSE { result = build Arrayer, :ARRAY, *val[1], position: pospoppush(3) }
|
175
188
|
params: params COMMA expr { pospoppush(3); val[0].push(val[2]) }
|
@@ -207,7 +220,10 @@ rule
|
|
207
220
|
NEWLINE_PRED = Set.new(BOPEN.values + OPERATIONS.values)
|
208
221
|
NEWLINE_NEXT = Set.new(BCLOSE.values + [:NEWLINE])
|
209
222
|
|
210
|
-
TAG_MODES = {
|
223
|
+
TAG_MODES = {
|
224
|
+
'!' => :silence, '^' => :escape, 'e' => :escape,
|
225
|
+
'~' => :normal, 'r' => :normal
|
226
|
+
}
|
211
227
|
|
212
228
|
def initialize source, options = {}
|
213
229
|
@source = Source.wrap(source)
|
@@ -218,6 +234,7 @@ rule
|
|
218
234
|
@commands = options[:commands] || {}
|
219
235
|
@blocks = options[:blocks] || {}
|
220
236
|
@endblocks = Set.new(@blocks.keys.map { |identifer| "end#{identifer}" })
|
237
|
+
@escape_tags = !!options[:escape_tags]
|
221
238
|
|
222
239
|
@substack = []
|
223
240
|
@posstack = []
|
@@ -237,6 +254,11 @@ rule
|
|
237
254
|
reduced
|
238
255
|
end
|
239
256
|
|
257
|
+
def tag_modes tag, default = :normal
|
258
|
+
mode = tag.gsub(/^{{/, '').first
|
259
|
+
TAG_MODES[mode] || default
|
260
|
+
end
|
261
|
+
|
240
262
|
def parse
|
241
263
|
if @tokens.size == 0
|
242
264
|
build Joiner, :JOINER, position: 0
|
data/lib/hotcell/template.rb
CHANGED
@@ -5,7 +5,8 @@ module Hotcell
|
|
5
5
|
def self.parse source
|
6
6
|
new source,
|
7
7
|
commands: Hotcell.commands,
|
8
|
-
blocks: Hotcell.blocks
|
8
|
+
blocks: Hotcell.blocks,
|
9
|
+
escape_tags: Hotcell.escape_tags
|
9
10
|
end
|
10
11
|
|
11
12
|
def initialize source, options = {}
|
@@ -14,7 +15,7 @@ module Hotcell
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def syntax
|
17
|
-
@syntax ||= Parser.new(source, options.slice(:commands, :blocks)).parse
|
18
|
+
@syntax ||= Parser.new(source, options.slice(:commands, :blocks, :escape_tags)).parse
|
18
19
|
end
|
19
20
|
|
20
21
|
def render context = {}
|
data/lib/hotcell/version.rb
CHANGED
data/lib/hotcell.rb
CHANGED
@@ -8,7 +8,7 @@ module Hotcell
|
|
8
8
|
def self.config; Config.instance; end
|
9
9
|
|
10
10
|
singleton_class.delegate :commands, :blocks, :helpers, :register_command, :register_helpers,
|
11
|
-
:resolver, :resolver=, to: :config
|
11
|
+
:resolver, :resolver=, :escape_tags, :escape_tags=, to: :config
|
12
12
|
end
|
13
13
|
|
14
14
|
require 'hotcell/manipulator'
|
@@ -17,6 +17,9 @@ describe Hotcell::Commands::For do
|
|
17
17
|
describe '#render' do
|
18
18
|
specify { parse('{{ for item, in: [1, 2, 3] }}{{ end for }}').render.should == '' }
|
19
19
|
specify { parse('{{ for item, in: [1, 2, 3] }}{{ item }}{{ end for }}').render.should == '123' }
|
20
|
+
specify { parse('{{ for item, in: 1..3 }}{{ item }}{{ end for }}').render.should == '123' }
|
21
|
+
specify { parse('{{ for item, in: { a: 1, b: 2, c: 3 } }}{{ item[0] }}{{ end }}').render.should == 'abc' }
|
22
|
+
specify { parse('{{ for item, in: { a: 1, b: 2, c: 3 } }}{{ item[1] }}{{ end }}').render.should == '123' }
|
20
23
|
specify { parse(
|
21
24
|
'{{ for item, in: [1, 2, 3] }}{{ item }} * 3 = {{ item * 3 }}; {{ end for }}'
|
22
25
|
).render(reraise: true).should == '1 * 3 = 3; 2 * 3 = 6; 3 * 3 = 9; ' }
|
@@ -16,6 +16,7 @@ describe Hotcell::Config do
|
|
16
16
|
specify { subject.commands.should == {} }
|
17
17
|
specify { subject.helpers.should == [] }
|
18
18
|
specify { subject.resolver.should be_a Hotcell::Resolver }
|
19
|
+
specify { subject.escape_tags.should be_false }
|
19
20
|
|
20
21
|
describe '#resolver=' do
|
21
22
|
let(:resolver) { Hotcell::FileSystemResolver.new('/') }
|
@@ -23,6 +24,11 @@ describe Hotcell::Config do
|
|
23
24
|
its(:resolver) { should == resolver }
|
24
25
|
end
|
25
26
|
|
27
|
+
describe '#escape_tags=' do
|
28
|
+
before { subject.escape_tags = true }
|
29
|
+
its(:escape_tags) { should be_true }
|
30
|
+
end
|
31
|
+
|
26
32
|
describe '#register_command' do
|
27
33
|
context do
|
28
34
|
before { subject.register_command :for, command_class }
|
@@ -45,6 +45,8 @@ describe Hotcell::Lexer do
|
|
45
45
|
specify { expression('= =').should == [[:ASSIGN, '='], [:ASSIGN, '=']] }
|
46
46
|
specify { expression(',').should == [[:COMMA, ',']] }
|
47
47
|
specify { expression('.').should == [[:PERIOD, '.']] }
|
48
|
+
specify { expression('..').should == [[:RANGE, '..']] }
|
49
|
+
specify { expression('...').should == [[:RANGE, '...']] }
|
48
50
|
specify { expression(':').should == [[:COLON, ':']] }
|
49
51
|
specify { expression('?').should == [[:QUESTION, '?']] }
|
50
52
|
specify { expression('hello?').should == [[:IDENTIFER, 'hello?']] }
|
@@ -83,7 +85,7 @@ describe Hotcell::Lexer do
|
|
83
85
|
specify { expression('.42.').should == [[:FLOAT, 0.42], [:PERIOD, '.']] }
|
84
86
|
specify { expression('.42.foo').should == [[:FLOAT, 0.42], [:PERIOD, '.'], [:IDENTIFER, 'foo']] }
|
85
87
|
specify { expression('.42foo').should == [[:FLOAT, 0.42], [:IDENTIFER, 'foo']] }
|
86
|
-
specify { expression('..42').should == [[:
|
88
|
+
specify { expression('..42').should == [[:RANGE, '..'], [:INTEGER, 42]] }
|
87
89
|
end
|
88
90
|
end
|
89
91
|
|
@@ -311,22 +313,31 @@ describe Hotcell::Lexer do
|
|
311
313
|
[:OR, "||"], [:INTEGER, 3], [:TCLOSE, "}}"], [:TEMPLATE, " hello"]
|
312
314
|
] }
|
313
315
|
|
314
|
-
context 'tag
|
315
|
-
specify { scan('{{hello}}').should == [
|
316
|
-
|
317
|
-
] }
|
318
|
-
specify { scan('{{!
|
319
|
-
|
320
|
-
] }
|
321
|
-
|
322
|
-
|
323
|
-
] }
|
324
|
-
specify { scan('{{
|
325
|
-
|
326
|
-
|
327
|
-
specify { scan('{{
|
328
|
-
|
329
|
-
|
316
|
+
context 'tag modifers' do
|
317
|
+
specify { scan('{{hello}}').should == [[:TOPEN, "{{"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
318
|
+
|
319
|
+
specify { scan('{{! hello}}').should == [[:TOPEN, "{{!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
320
|
+
specify { scan('{{!hello}}').should == [[:TOPEN, "{{!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
321
|
+
specify { scan('{{ !hello}}').should == [[:TOPEN, "{{"], [:NOT, "!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
322
|
+
specify { scan('{{!}}').should == [[:TOPEN, "{{!"], [:TCLOSE, "}}"]] }
|
323
|
+
|
324
|
+
specify { scan('{{~ hello}}').should == [[:TOPEN, "{{~"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
325
|
+
specify { scan('{{~hello}}').should == [[:TOPEN, "{{~"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
326
|
+
specify { expect { scan('{{ ~hello}}') }.to raise_error Hotcell::UnexpectedSymbol }
|
327
|
+
specify { scan('{{~}}').should == [[:TOPEN, "{{~"], [:TCLOSE, "}}"]] }
|
328
|
+
|
329
|
+
specify { scan('{{^ hello}}').should == [[:TOPEN, "{{^"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
330
|
+
specify { scan('{{^hello}}').should == [[:TOPEN, "{{^"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
331
|
+
specify { expect { scan('{{ ^hello}}') }.to raise_error Hotcell::UnexpectedSymbol }
|
332
|
+
specify { scan('{{^}}').should == [[:TOPEN, "{{^"], [:TCLOSE, "}}"]] }
|
333
|
+
|
334
|
+
specify { scan('{{r hello}}').should == [[:TOPEN, "{{r "], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
335
|
+
specify { scan('{{ r hello}}').should == [[:TOPEN, "{{"], [:IDENTIFER, "r"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
336
|
+
specify { scan('{{rhello}}').should == [[:TOPEN, "{{"], [:IDENTIFER, "rhello"], [:TCLOSE, "}}"]] }
|
337
|
+
|
338
|
+
specify { scan('{{e hello}}').should == [[:TOPEN, "{{e "], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
339
|
+
specify { scan('{{ e hello}}').should == [[:TOPEN, "{{"], [:IDENTIFER, "e"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]] }
|
340
|
+
specify { scan('{{ehello}}').should == [[:TOPEN, "{{"], [:IDENTIFER, "ehello"], [:TCLOSE, "}}"]] }
|
330
341
|
end
|
331
342
|
end
|
332
343
|
|
@@ -6,6 +6,8 @@ describe Hotcell::Manipulator do
|
|
6
6
|
let(:klass) do
|
7
7
|
Class.new(Numeric) do
|
8
8
|
include Hotcell::Manipulator::Mixin
|
9
|
+
|
10
|
+
def foo; end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
subject { klass.new }
|
@@ -13,9 +15,9 @@ describe Hotcell::Manipulator do
|
|
13
15
|
its(:manipulator_methods) { should be_a Set }
|
14
16
|
its(:manipulator_methods) { should be_empty }
|
15
17
|
its(:to_manipulator) { should === subject }
|
16
|
-
specify { subject.manipulator_invoke('
|
17
|
-
specify { subject.manipulator_invoke(:
|
18
|
-
specify { subject.manipulator_invoke('
|
18
|
+
specify { subject.manipulator_invoke('foo').should be_nil }
|
19
|
+
specify { subject.manipulator_invoke(:foo).should be_nil }
|
20
|
+
specify { subject.manipulator_invoke('foo', 42, :arg).should be_nil }
|
19
21
|
end
|
20
22
|
|
21
23
|
context do
|
@@ -23,19 +25,22 @@ describe Hotcell::Manipulator do
|
|
23
25
|
Class.new(String) do
|
24
26
|
include Hotcell::Manipulator::Mixin
|
25
27
|
|
26
|
-
|
28
|
+
manipulate :foo, :bar
|
29
|
+
|
30
|
+
alias_method :foo, :split
|
31
|
+
alias_method :bar, :size
|
27
32
|
end
|
28
33
|
end
|
29
34
|
subject { klass.new('hello world') }
|
30
35
|
|
31
|
-
its('manipulator_methods.to_a') { should =~ %w(split size) }
|
32
36
|
its(:to_manipulator) { should === subject }
|
33
|
-
specify { subject.
|
34
|
-
specify { subject.manipulator_invoke('
|
35
|
-
specify { subject.manipulator_invoke(:
|
36
|
-
specify { subject.manipulator_invoke(
|
37
|
-
specify { subject.manipulator_invoke('
|
38
|
-
specify {
|
37
|
+
specify { (subject.manipulator_methods.to_a & %w(foo bar)).should =~ %w(foo bar) }
|
38
|
+
specify { subject.manipulator_invoke('baz').should be_nil }
|
39
|
+
specify { subject.manipulator_invoke('baz', 42, :arg).should be_nil }
|
40
|
+
specify { subject.manipulator_invoke(:foo).should be_nil }
|
41
|
+
specify { subject.manipulator_invoke('foo').should == %w(hello world) }
|
42
|
+
specify { subject.manipulator_invoke('bar').should == 11 }
|
43
|
+
specify { expect { subject.manipulator_invoke('bar', 42) }.to raise_error ArgumentError }
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Hotcell::Block do
|
4
4
|
def method_missing method, *args, &block
|
5
|
-
klass = Hotcell::
|
6
|
-
Hotcell::
|
5
|
+
klass = Hotcell::Expression::HANDLERS[method] ?
|
6
|
+
Hotcell::Expression : Hotcell::Node
|
7
7
|
|
8
8
|
instance = Hotcell::Assigner.new *args if method == :ASSIGN
|
9
9
|
instance = Hotcell::Summoner.new *args if method == :METHOD
|
@@ -3,8 +3,8 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe Hotcell::Parser do
|
5
5
|
def method_missing method, *args, &block
|
6
|
-
klass = Hotcell::
|
7
|
-
Hotcell::
|
6
|
+
klass = Hotcell::Expression::HANDLERS[method] ?
|
7
|
+
Hotcell::Expression : Hotcell::Node
|
8
8
|
|
9
9
|
instance = Hotcell::Assigner.new *args if method == :ASSIGN
|
10
10
|
instance = Hotcell::Summoner.new *args if method == :METHOD
|
@@ -157,6 +157,12 @@ describe Hotcell::Parser do
|
|
157
157
|
specify { parse('{{ (\'hello\') }}').should be_equal_node_to JOINER(TAG('hello', mode: :normal)) }
|
158
158
|
specify { parse('{{ () }}').should be_equal_node_to JOINER(TAG(nil, mode: :normal)) }
|
159
159
|
|
160
|
+
specify { parse('{{ \'a\'..\'z\' }}').should be_equal_node_to JOINER(TAG('a'..'z', mode: :normal)) }
|
161
|
+
specify { parse('{{ 0..10 }}').should be_equal_node_to JOINER(TAG(0..10, mode: :normal)) }
|
162
|
+
specify { parse('{{ 0...10 }}').should be_equal_node_to JOINER(TAG(0...10, mode: :normal)) }
|
163
|
+
specify { parse('{{ 3 + 0..10 + 5 }}').should be_equal_node_to JOINER(TAG(3..15, mode: :normal)) }
|
164
|
+
specify { parse('{{ 3 + (0..var) + 5 }}').should be_equal_node_to JOINER(TAG(PLUS(PLUS(3, RANGE(0, METHOD('var'))), 5), mode: :normal)) }
|
165
|
+
|
160
166
|
specify { parse('{{ bar > 2 }}').should be_equal_node_to JOINER(TAG(GT(METHOD('bar'), 2), mode: :normal)) }
|
161
167
|
specify { parse('{{ 2 < bar }}').should be_equal_node_to JOINER(TAG(LT(2, METHOD('bar')), mode: :normal)) }
|
162
168
|
specify { parse('{{ 2 >= tru }}').should be_equal_node_to JOINER(TAG(GTE(2, METHOD('tru')), mode: :normal)) }
|
@@ -202,7 +208,7 @@ describe Hotcell::Parser do
|
|
202
208
|
mode: :normal)) }
|
203
209
|
specify { parse('{{ foo(\'hello\').bar[2].baz(-42) }}').should be_equal_node_to JOINER(TAG(
|
204
210
|
METHOD('baz',
|
205
|
-
METHOD('
|
211
|
+
METHOD('[]',
|
206
212
|
METHOD('bar',
|
207
213
|
METHOD('foo', nil, 'hello')
|
208
214
|
), 2
|
@@ -216,7 +222,7 @@ describe Hotcell::Parser do
|
|
216
222
|
specify { parse('{{ [] }}').should be_equal_node_to JOINER(TAG(ARRAY(), mode: :normal)) }
|
217
223
|
specify { parse('{{ [ 2 ] }}').should be_equal_node_to JOINER(TAG(ARRAY(2), mode: :normal)) }
|
218
224
|
specify { parse('{{ [ 2, 3 ] }}').should be_equal_node_to JOINER(TAG(ARRAY(2, 3), mode: :normal)) }
|
219
|
-
specify { parse('{{ [2, 3][42] }}').should be_equal_node_to JOINER(TAG(METHOD('
|
225
|
+
specify { parse('{{ [2, 3][42] }}').should be_equal_node_to JOINER(TAG(METHOD('[]', ARRAY(2, 3), 42), mode: :normal)) }
|
220
226
|
specify { parse('{{ [2 + foo, (2 * bar)] }}').should be_equal_node_to JOINER(TAG(ARRAY(PLUS(2, METHOD('foo')), MULTIPLY(2, METHOD('bar'))), mode: :normal)) }
|
221
227
|
specify { parse('{{ [[2, 3], 42] }}').should be_equal_node_to JOINER(TAG(ARRAY(ARRAY(2, 3), 42), mode: :normal)) }
|
222
228
|
end
|
@@ -227,7 +233,7 @@ describe Hotcell::Parser do
|
|
227
233
|
JOINER(TAG(HASH(PAIR('hello', 'world')), mode: :normal))
|
228
234
|
) }
|
229
235
|
specify { parse('{{ {hello: \'world\'}[\'hello\'] }}').should be_equal_node_to(
|
230
|
-
JOINER(TAG(METHOD('
|
236
|
+
JOINER(TAG(METHOD('[]', HASH(PAIR('hello', 'world')), 'hello'), mode: :normal))
|
231
237
|
) }
|
232
238
|
specify { parse('{{ { hello: 3, world: 6 * foo } }}').should be_equal_node_to(
|
233
239
|
JOINER(TAG(HASH(
|
@@ -238,11 +244,11 @@ describe Hotcell::Parser do
|
|
238
244
|
end
|
239
245
|
|
240
246
|
context '[]' do
|
241
|
-
specify { parse('{{ hello[3] }}').should be_equal_node_to JOINER(TAG(METHOD('
|
242
|
-
specify { parse('{{ \'boom\'[3] }}').should be_equal_node_to JOINER(TAG(METHOD('
|
243
|
-
specify { parse('{{ 7[3] }}').should be_equal_node_to JOINER(TAG(METHOD('
|
244
|
-
specify { parse('{{ 3 + 5[7] }}').should be_equal_node_to JOINER(TAG(PLUS(3, METHOD('
|
245
|
-
specify { parse('{{ (3 + 5)[7] }}').should be_equal_node_to JOINER(TAG(METHOD('
|
247
|
+
specify { parse('{{ hello[3] }}').should be_equal_node_to JOINER(TAG(METHOD('[]', METHOD('hello'), 3), mode: :normal)) }
|
248
|
+
specify { parse('{{ \'boom\'[3] }}').should be_equal_node_to JOINER(TAG(METHOD('[]', 'boom', 3), mode: :normal)) }
|
249
|
+
specify { parse('{{ 7[3] }}').should be_equal_node_to JOINER(TAG(METHOD('[]', 7, 3), mode: :normal)) }
|
250
|
+
specify { parse('{{ 3 + 5[7] }}').should be_equal_node_to JOINER(TAG(PLUS(3, METHOD('[]', 5, 7)), mode: :normal)) }
|
251
|
+
specify { parse('{{ (3 + 5)[7] }}').should be_equal_node_to JOINER(TAG(METHOD('[]', 8, 7), mode: :normal)) }
|
246
252
|
end
|
247
253
|
|
248
254
|
context 'function arguments' do
|
@@ -306,62 +312,139 @@ describe Hotcell::Parser do
|
|
306
312
|
specify { parse("hello {{ world# foo}}").should be_equal_node_to JOINER('hello ', TAG(METHOD('world'), mode: :normal)) }
|
307
313
|
end
|
308
314
|
|
315
|
+
context 'tag modes' do
|
316
|
+
specify { parse('{{ }}').should be_equal_node_to JOINER(TAG(mode: :normal)) }
|
317
|
+
specify { parse('{{! }}').should be_equal_node_to JOINER(TAG(mode: :silence)) }
|
318
|
+
specify { parse('{{^ }}').should be_equal_node_to JOINER(TAG(mode: :escape)) }
|
319
|
+
specify { parse('{{e }}').should be_equal_node_to JOINER(TAG(mode: :escape)) }
|
320
|
+
specify { parse('{{~ }}').should be_equal_node_to JOINER(TAG(mode: :normal)) }
|
321
|
+
specify { parse('{{r }}').should be_equal_node_to JOINER(TAG(mode: :normal)) }
|
322
|
+
|
323
|
+
context 'escape_tags' do
|
324
|
+
specify { parse('{{ }}', escape_tags: true).should be_equal_node_to JOINER(TAG(mode: :escape)) }
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'commands' do
|
328
|
+
let(:snippet_command) { Class.new(Hotcell::Command) }
|
329
|
+
let(:commands) { { snippet: snippet_command }.stringify_keys }
|
330
|
+
|
331
|
+
specify { parse('{{ snippet }}', commands: commands).should be_equal_node_to JOINER(
|
332
|
+
TAG(snippet_command.build('snippet'), mode: :normal)
|
333
|
+
) }
|
334
|
+
specify { parse('{{! snippet }}', commands: commands).should be_equal_node_to JOINER(
|
335
|
+
TAG(snippet_command.build('snippet'), mode: :silence)
|
336
|
+
) }
|
337
|
+
specify { parse('{{^ snippet }}', commands: commands).should be_equal_node_to JOINER(
|
338
|
+
TAG(snippet_command.build('snippet'), mode: :escape)
|
339
|
+
) }
|
340
|
+
specify { parse('{{e snippet }}', commands: commands).should be_equal_node_to JOINER(
|
341
|
+
TAG(snippet_command.build('snippet'), mode: :escape)
|
342
|
+
) }
|
343
|
+
specify { parse('{{~ snippet }}', commands: commands).should be_equal_node_to JOINER(
|
344
|
+
TAG(snippet_command.build('snippet'), mode: :normal)
|
345
|
+
) }
|
346
|
+
specify { parse('{{r snippet }}', commands: commands).should be_equal_node_to JOINER(
|
347
|
+
TAG(snippet_command.build('snippet'), mode: :normal)
|
348
|
+
) }
|
349
|
+
|
350
|
+
context 'escape_tags' do
|
351
|
+
specify { parse('{{ snippet }}',
|
352
|
+
commands: commands, escape_tags: true
|
353
|
+
).should be_equal_node_to JOINER(
|
354
|
+
TAG(snippet_command.build('snippet'), mode: :normal)
|
355
|
+
) }
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
context 'blocks' do
|
360
|
+
let(:cycle_block) { Class.new(Hotcell::Block) }
|
361
|
+
let(:blocks) { { cycle: cycle_block }.stringify_keys }
|
362
|
+
|
363
|
+
specify { parse('{{ cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
364
|
+
TAG(cycle_block.build('cycle'), mode: :normal)
|
365
|
+
) }
|
366
|
+
specify { parse('{{! cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
367
|
+
TAG(cycle_block.build('cycle'), mode: :silence)
|
368
|
+
) }
|
369
|
+
specify { parse('{{^ cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
370
|
+
TAG(cycle_block.build('cycle'), mode: :escape)
|
371
|
+
) }
|
372
|
+
specify { parse('{{e cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
373
|
+
TAG(cycle_block.build('cycle'), mode: :escape)
|
374
|
+
) }
|
375
|
+
specify { parse('{{~ cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
376
|
+
TAG(cycle_block.build('cycle'), mode: :normal)
|
377
|
+
) }
|
378
|
+
specify { parse('{{r cycle }}{{ end }}', blocks: blocks).should be_equal_node_to JOINER(
|
379
|
+
TAG(cycle_block.build('cycle'), mode: :normal)
|
380
|
+
) }
|
381
|
+
|
382
|
+
context 'escape_tags' do
|
383
|
+
specify { parse('{{ cycle }}{{ end }}',
|
384
|
+
blocks: blocks, escape_tags: true
|
385
|
+
).should be_equal_node_to JOINER(
|
386
|
+
TAG(cycle_block.build('cycle'), mode: :normal)
|
387
|
+
) }
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
309
392
|
context 'commands' do
|
310
|
-
let(:
|
311
|
-
let(:
|
393
|
+
let(:include_command) { Class.new(Hotcell::Command) }
|
394
|
+
let(:snippet_command) { Class.new(Hotcell::Command) }
|
312
395
|
let(:commands) do
|
313
396
|
{
|
314
|
-
include:
|
315
|
-
snippet:
|
397
|
+
include: include_command,
|
398
|
+
snippet: snippet_command
|
316
399
|
}.stringify_keys
|
317
400
|
end
|
318
401
|
|
319
402
|
specify { parse("{{ include 'some/partial' }}",
|
320
403
|
commands: commands).should be_equal_node_to JOINER(
|
321
|
-
TAG(
|
404
|
+
TAG(include_command.build('include', 'some/partial'), mode: :normal)
|
322
405
|
) }
|
323
406
|
specify { parse("{{ include }}",
|
324
407
|
commands: commands).should be_equal_node_to JOINER(
|
325
|
-
TAG(
|
408
|
+
TAG(include_command.build('include'), mode: :normal)
|
326
409
|
) }
|
327
410
|
specify { parse("{{! include 'some/partial' }}\n{{ snippet 'sidebar' }}",
|
328
411
|
commands: commands).should be_equal_node_to JOINER(
|
329
|
-
TAG(
|
412
|
+
TAG(include_command.build('include', 'some/partial'), mode: :silence),
|
330
413
|
"\n",
|
331
|
-
TAG(
|
414
|
+
TAG(snippet_command.build('snippet', 'sidebar'), mode: :normal),
|
332
415
|
) }
|
333
416
|
specify { parse("{{! variable = include }}",
|
334
417
|
commands: commands).should be_equal_node_to JOINER(
|
335
|
-
TAG(ASSIGN('variable',
|
418
|
+
TAG(ASSIGN('variable', include_command.build('include')), mode: :silence)
|
336
419
|
) }
|
337
420
|
specify { parse("{{ variable = include 'some/partial' }}",
|
338
421
|
commands: commands).should be_equal_node_to JOINER(
|
339
|
-
TAG(ASSIGN('variable',
|
422
|
+
TAG(ASSIGN('variable', include_command.build('include', 'some/partial')), mode: :normal)
|
340
423
|
) }
|
341
424
|
end
|
342
425
|
|
343
426
|
context 'blocks' do
|
344
|
-
let(:
|
345
|
-
let(:
|
427
|
+
let(:scoped_block) { Class.new(Hotcell::Block) }
|
428
|
+
let(:each_block) { Class.new(Hotcell::Block) }
|
346
429
|
let(:blocks) do
|
347
430
|
{
|
348
|
-
scoped:
|
349
|
-
each:
|
431
|
+
scoped: scoped_block,
|
432
|
+
each: each_block
|
350
433
|
}.stringify_keys
|
351
434
|
end
|
352
435
|
|
353
436
|
specify { parse("{{ scoped }}{{ end scoped }}",
|
354
437
|
blocks: blocks).should be_equal_node_to JOINER(
|
355
|
-
TAG(
|
438
|
+
TAG(scoped_block.build('scoped'), mode: :normal)
|
356
439
|
) }
|
357
440
|
specify { parse("{{ scoped var: 'hello' }}{{ endscoped }}",
|
358
441
|
blocks: blocks).should be_equal_node_to JOINER(
|
359
|
-
TAG(
|
442
|
+
TAG(scoped_block.build('scoped', HASH(PAIR('var', 'hello'))), mode: :normal)
|
360
443
|
) }
|
361
444
|
specify { parse("<article>\n{{ each post, in: posts }}\n<h1>{{ post.title }}</h1>\n{{ end each }}\n</article>",
|
362
445
|
blocks: blocks).should be_equal_node_to JOINER(
|
363
446
|
"<article>\n",
|
364
|
-
TAG(
|
447
|
+
TAG(each_block.build('each',
|
365
448
|
METHOD('post'),
|
366
449
|
HASH(PAIR('in', METHOD('posts'))),
|
367
450
|
subnodes: [JOINER(
|
@@ -374,7 +457,7 @@ describe Hotcell::Parser do
|
|
374
457
|
) }
|
375
458
|
specify { parse("{{! iter = each post, in: posts }}\n<h1>{{ post.title }}</h1>\n{{ end each }}",
|
376
459
|
blocks: blocks).should be_equal_node_to JOINER(
|
377
|
-
TAG(ASSIGN('iter',
|
460
|
+
TAG(ASSIGN('iter', each_block.build('each',
|
378
461
|
METHOD('post'),
|
379
462
|
HASH(PAIR('in', METHOD('posts'))),
|
380
463
|
subnodes: [JOINER(
|
@@ -386,10 +469,10 @@ describe Hotcell::Parser do
|
|
386
469
|
) }
|
387
470
|
specify { parse("{{ capture = scoped }} hello {{ each post, in: posts }} {{ loop }} {{ end each }}{{ endscoped }}",
|
388
471
|
blocks: blocks).should be_equal_node_to JOINER(
|
389
|
-
TAG(ASSIGN('capture',
|
472
|
+
TAG(ASSIGN('capture', scoped_block.build('scoped',
|
390
473
|
subnodes: [JOINER(
|
391
474
|
' hello ',
|
392
|
-
TAG(
|
475
|
+
TAG(each_block.build('each',
|
393
476
|
METHOD('post'),
|
394
477
|
HASH(PAIR('in', METHOD('posts'))),
|
395
478
|
subnodes: [JOINER(
|