hotcell 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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(
|