hotcell 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 { result = build Tag, :TAG, mode: TAG_MODES[val[0]], position: pospoppush(2) }
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, *Array.wrap(val[1]).flatten, mode: TAG_MODES[val[0]], position: pospoppush(3)
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: TAG_MODES[val[0]], position: pospoppush(3)
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: TAG_MODES[val[0]], position: pospoppush(3)
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 Calculator, :MULTIPLY, val[0], val[2], position: pospoppush(3) }
134
- | expr POWER expr { result = build Calculator, :POWER, val[0], val[2], position: pospoppush(3) }
135
- | expr DIVIDE expr { result = build Calculator, :DIVIDE, val[0], val[2], position: pospoppush(3) }
136
- | expr PLUS expr { result = build Calculator, :PLUS, val[0], val[2], position: pospoppush(3) }
137
- | expr MINUS expr { result = build Calculator, :MINUS, val[0], val[2], position: pospoppush(3) }
138
- | expr MODULO expr { result = build Calculator, :MODULO, val[0], val[2], position: pospoppush(3) }
139
- | MINUS expr =UMINUS { result = build Calculator, :UMINUS, val[1], position: pospoppush(2) }
140
- | PLUS expr =UPLUS { result = build Calculator, :UPLUS, val[1], position: pospoppush(2) }
141
- | expr AND expr { result = build Calculator, :AND, val[0], val[2], position: pospoppush(3) }
142
- | expr OR expr { result = build Calculator, :OR, val[0], val[2], position: pospoppush(3) }
143
- | expr GT expr { result = build Calculator, :GT, val[0], val[2], position: pospoppush(3) }
144
- | expr GTE expr { result = build Calculator, :GTE, val[0], val[2], position: pospoppush(3) }
145
- | expr LT expr { result = build Calculator, :LT, val[0], val[2], position: pospoppush(3) }
146
- | expr LTE expr { result = build Calculator, :LTE, val[0], val[2], position: pospoppush(3) }
147
- | expr EQUAL expr { result = build Calculator, :EQUAL, val[0], val[2], position: pospoppush(3) }
148
- | expr INEQUAL expr { result = build Calculator, :INEQUAL, val[0], val[2], position: pospoppush(3) }
149
- | NOT expr { result = build Calculator, :NOT, val[1], position: pospoppush(2) }
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, 'manipulator_brackets', val[0], *val[2], position: pospoppush(4)
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 = { '{{' => :normal, '{{!' => :silence }
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
@@ -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 = {}
@@ -1,3 +1,3 @@
1
1
  module Hotcell
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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 == [[:PERIOD, '.'], [:FLOAT, 0.42]] }
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 types' do
315
- specify { scan('{{hello}}').should == [
316
- [:TOPEN, "{{"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
317
- ] }
318
- specify { scan('{{! hello}}').should == [
319
- [:TOPEN, "{{!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
320
- ] }
321
- specify { scan('{{ !hello}}').should == [
322
- [:TOPEN, "{{"], [:NOT, "!"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
323
- ] }
324
- specify { scan('{{/ hello}}').should == [
325
- [:TOPEN, "{{"], [:DIVIDE, "/"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
326
- ] }
327
- specify { scan('{{ /hello}}').should == [
328
- [:TOPEN, "{{"], [:DIVIDE, "/"], [:IDENTIFER, "hello"], [:TCLOSE, "}}"]
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('round').should be_nil }
17
- specify { subject.manipulator_invoke(:round).should be_nil }
18
- specify { subject.manipulator_invoke('round', 42, :arg).should be_nil }
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
- manipulator :split, :size
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.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 }
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::Calculator::HANDLERS[method] ?
6
- Hotcell::Calculator : Hotcell::Node
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::Calculator::HANDLERS[method] ?
7
- Hotcell::Calculator : Hotcell::Node
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('manipulator_brackets',
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('manipulator_brackets', ARRAY(2, 3), 42), mode: :normal)) }
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('manipulator_brackets', HASH(PAIR('hello', 'world')), 'hello'), mode: :normal))
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('manipulator_brackets', METHOD('hello'), 3), mode: :normal)) }
242
- specify { parse('{{ \'boom\'[3] }}').should be_equal_node_to JOINER(TAG(METHOD('manipulator_brackets', 'boom', 3), mode: :normal)) }
243
- specify { parse('{{ 7[3] }}').should be_equal_node_to JOINER(TAG(METHOD('manipulator_brackets', 7, 3), mode: :normal)) }
244
- specify { parse('{{ 3 + 5[7] }}').should be_equal_node_to JOINER(TAG(PLUS(3, METHOD('manipulator_brackets', 5, 7)), mode: :normal)) }
245
- specify { parse('{{ (3 + 5)[7] }}').should be_equal_node_to JOINER(TAG(METHOD('manipulator_brackets', 8, 7), mode: :normal)) }
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(:include_tag) { Class.new(Hotcell::Command) }
311
- let(:snippet_tag) { Class.new(Hotcell::Command) }
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: include_tag,
315
- snippet: snippet_tag
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(include_tag.build('include', 'some/partial'), mode: :normal)
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(include_tag.build('include'), mode: :normal)
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(include_tag.build('include', 'some/partial'), mode: :silence),
412
+ TAG(include_command.build('include', 'some/partial'), mode: :silence),
330
413
  "\n",
331
- TAG(snippet_tag.build('snippet', 'sidebar'), mode: :normal),
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', include_tag.build('include')), mode: :silence)
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', include_tag.build('include', 'some/partial')), mode: :normal)
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(:scoped_tag) { Class.new(Hotcell::Block) }
345
- let(:each_tag) { Class.new(Hotcell::Block) }
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: scoped_tag,
349
- each: each_tag
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(scoped_tag.build('scoped'), mode: :normal)
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(scoped_tag.build('scoped', HASH(PAIR('var', 'hello'))), mode: :normal)
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(each_tag.build('each',
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', each_tag.build('each',
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', scoped_tag.build('scoped',
472
+ TAG(ASSIGN('capture', scoped_block.build('scoped',
390
473
  subnodes: [JOINER(
391
474
  ' hello ',
392
- TAG(each_tag.build('each',
475
+ TAG(each_block.build('each',
393
476
  METHOD('post'),
394
477
  HASH(PAIR('in', METHOD('posts'))),
395
478
  subnodes: [JOINER(