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.
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(