hotcell 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -46,15 +46,15 @@ prechigh
46
46
  left AND
47
47
  left OR
48
48
  nonassoc RANGE
49
- right TERNARY
49
+ right QUESTION
50
50
  right ASSIGN
51
51
  nonassoc COMMA COLON
52
52
  left SEMICOLON NEWLINE
53
53
  preclow
54
54
  start document
55
55
  rule
56
- document: document document_unit { pospoppush(2); val[0].children.push(val[1]) }
57
- | document_unit { result = build Joiner, :JOINER, val[0], position: pospoppush(1) }
56
+ document: document document_unit { pospoppush(2); val[0].push(val[1]) }
57
+ | document_unit { result = [val[0]] }
58
58
  document_unit: template | tag | block_tag | command_tag
59
59
 
60
60
  template: TEMPLATE { result = val[0] }
@@ -155,6 +155,7 @@ rule
155
155
  | expr EQUAL expr { result = build Expression, :EQUAL, val[0], val[2], position: pospoppush(3) }
156
156
  | expr INEQUAL expr { result = build Expression, :INEQUAL, val[0], val[2], position: pospoppush(3) }
157
157
  | NOT expr { result = build Expression, :NOT, val[1], position: pospoppush(2) }
158
+ | expr QUESTION expr COLON expr { result = build Expression, :TERNARY, val[0], val[2], val[4], position: pospoppush(5) }
158
159
  | IDENTIFER ASSIGN expr { result = build Assigner, val[0], val[2], position: pospoppush(3) }
159
160
  | expr PERIOD method { pospoppush(3); val[2].children[0] = val[0]; result = val[2] }
160
161
  | expr AOPEN arguments ACLOSE {
@@ -162,21 +163,37 @@ rule
162
163
  }
163
164
  | POPEN PCLOSE { pospoppush(2); result = nil }
164
165
  | POPEN sequence PCLOSE {
165
- position = pospoppush(3)
166
166
  result = case val[1].size
167
167
  when 1
168
168
  val[1][0]
169
169
  else
170
- build Sequencer, :SEQUENCE, *val[1].flatten, position: position
170
+ build Sequencer, :SEQUENCE, *val[1].flatten, position: pospoppush(3)
171
171
  end
172
172
  }
173
173
  | value
174
174
 
175
- value: const | number | string | range | array | hash | method
175
+ value: const | number | string | dstring | regexp | range | array | hash | method
176
176
 
177
177
  const: NIL | TRUE | FALSE
178
178
  number: INTEGER | FLOAT
179
- string: STRING | REGEXP
179
+ string: STRING
180
+ regexp: REGEXP
181
+
182
+ dstring: DOPEN dstring_content DCLOSE { result = build Expression, :DSTRING, *val[1], position: pospoppush(3) }
183
+ | DOPEN DCLOSE { pospoppush(2); result = '' }
184
+ dstring_content: STRING { result = [val[0]] }
185
+ | dstring_interpolation { result = [val[0]] }
186
+ | dstring_content STRING { pospoppush(2); val[0].push(val[1]); result = val[0] }
187
+ | dstring_content dstring_interpolation { pospoppush(2); val[0].push(val[1]); result = val[0] }
188
+ dstring_interpolation: IOPEN sequence ICLOSE {
189
+ result = case val[1].size
190
+ when 1
191
+ val[1][0]
192
+ else
193
+ build Sequencer, :SEQUENCE, *val[1].flatten, position: pospoppush(3)
194
+ end
195
+ }
196
+ | IOPEN ICLOSE { pospoppush(2); result = nil }
180
197
 
181
198
  range: expr RANGE expr {
182
199
  result = build Expression, val[1] == '..' ? :RANGE : :ERANGE,
@@ -260,11 +277,8 @@ rule
260
277
  end
261
278
 
262
279
  def parse
263
- if @tokens.size == 0
264
- build Joiner, :JOINER, position: 0
265
- else
266
- do_parse
267
- end
280
+ children = @tokens.size.zero? ? [] : do_parse
281
+ build Joiner, :JOINER, *children, position: 0
268
282
  end
269
283
 
270
284
  def next_token
@@ -0,0 +1,54 @@
1
+ module Hotcell
2
+ class Tong
3
+ module Mixin
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :tong_methods, instance_writter: false
8
+ self.tong_methods = Set.new
9
+
10
+ def self.manipulate *methods
11
+ self.tong_methods = Set.new(tong_methods.to_a + methods.flatten.map(&:to_s))
12
+ end
13
+ end
14
+
15
+ def to_tong
16
+ self
17
+ end
18
+
19
+ def tong_invoke method, *arguments
20
+ if method == '[]'
21
+ tong_invoke_brackets *arguments
22
+ elsif tong_invokable? method
23
+ send(method, *arguments)
24
+ else
25
+ tong_missing(method, *arguments)
26
+ end
27
+ end
28
+
29
+ def tong_missing method, *arguments
30
+ end
31
+
32
+ private
33
+
34
+ def tong_invokable? method
35
+ tong_methods.include? method
36
+ end
37
+
38
+ def tong_invoke_brackets *arguments
39
+ if respond_to? :[]
40
+ self[*arguments]
41
+ else
42
+ tong_invoke *arguments
43
+ end
44
+ end
45
+ end
46
+
47
+ include Mixin
48
+
49
+ def tong_methods
50
+ @tong_methods ||= Set.new((self.class.public_instance_methods -
51
+ Hotcell::Tong.public_instance_methods).map(&:to_s))
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module Hotcell
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
File without changes
@@ -27,7 +27,7 @@ describe Hotcell::Commands::Include do
27
27
 
28
28
  describe '#render' do
29
29
  specify { parse("{{ include 'template0' }}").render(render_options).should =~ /Template not found/ }
30
- specify { parse("{{ include 'template1' }}").render(render_options).should == 'Hello' }
30
+ specify { parse("{{ include 'template1' }}").render!(render_options).should == 'Hello' }
31
31
  specify { parse("{{ include 'template2' }}").render(render_options).should == 'Hello, ' }
32
32
  specify { parse(
33
33
  "{{ include 'template2', name: 'Pyrosha' }}"
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Hotcell::Context do
4
4
  its('scope.scope') { should == [{}] }
5
- its(:helpers) { should be_a Hotcell::Manipulator }
5
+ its(:helpers) { should be_a Hotcell::Tong }
6
6
 
7
7
  describe '#normalize_options' do
8
8
  def result options = {}
@@ -69,7 +69,7 @@ describe Hotcell::Context do
69
69
  end
70
70
  end
71
71
 
72
- describe '#manipulator_invoke' do
72
+ describe '#tong_invoke' do
73
73
  subject { described_class.new(
74
74
  variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' },
75
75
  helpers: Module.new do
@@ -82,12 +82,12 @@ describe Hotcell::Context do
82
82
  end
83
83
  end
84
84
  ) }
85
- specify { subject.manipulator_invoke('foo').should == 42 }
86
- specify { subject.manipulator_invoke('moo').should be_nil }
87
- specify { subject.manipulator_invoke('baz').should be_nil }
88
- specify { subject.manipulator_invoke('bar').should == 'baz' }
89
- specify { expect { subject.manipulator_invoke('bar', 42) }.to raise_error ArgumentError }
90
- specify { expect { subject.manipulator_invoke('strip') }.to raise_error ArgumentError }
91
- specify { subject.manipulator_invoke('strip', ' hello ').should == 'hello' }
85
+ specify { subject.tong_invoke('foo').should == 42 }
86
+ specify { subject.tong_invoke('moo').should be_nil }
87
+ specify { subject.tong_invoke('baz').should be_nil }
88
+ specify { subject.tong_invoke('bar').should == 'baz' }
89
+ specify { expect { subject.tong_invoke('bar', 42) }.to raise_error ArgumentError }
90
+ specify { expect { subject.tong_invoke('strip') }.to raise_error ArgumentError }
91
+ specify { subject.tong_invoke('strip', ' hello ').should == 'hello' }
92
92
  end
93
93
  end
@@ -105,13 +105,13 @@ describe Hotcell::Lexer do
105
105
 
106
106
  context 'constants' do
107
107
  specify { expression('nil').should == [[:NIL, nil]] }
108
- specify { expression('"nil"').should == [[:STRING, 'nil']] }
108
+ specify { expression('"nil"').should == [[:DOPEN, '"'], [:STRING, 'nil'], [:DCLOSE, '"']] }
109
109
  specify { expression('null').should == [[:NIL, nil]] }
110
- specify { expression('"null"').should == [[:STRING, 'null']] }
110
+ specify { expression('"null"').should == [[:DOPEN, '"'], [:STRING, 'null'], [:DCLOSE, '"']] }
111
111
  specify { expression('false').should == [[:FALSE, false]] }
112
- specify { expression('"false"').should == [[:STRING, 'false']] }
112
+ specify { expression('"false"').should == [[:DOPEN, '"'], [:STRING, 'false'], [:DCLOSE, '"']] }
113
113
  specify { expression('true').should == [[:TRUE, true]] }
114
- specify { expression('"true"').should == [[:STRING, 'true']] }
114
+ specify { expression('"true"').should == [[:DOPEN, '"'], [:STRING, 'true'], [:DCLOSE, '"']] }
115
115
  end
116
116
  end
117
117
 
@@ -130,7 +130,7 @@ describe Hotcell::Lexer do
130
130
  specify { expression(%q{'при\вет'}).should == [[:STRING, 'при\вет']] }
131
131
 
132
132
  context do
133
- let(:strings) { data 'sstrings' }
133
+ let(:strings) { data 'strings' }
134
134
 
135
135
  specify { expression(strings).delete_if { |token| token.first == :NEWLINE }.should == [
136
136
  [:STRING, 'fo\'o'], [:STRING, 'fo\o'], [:STRING, 'fo\\o'],
@@ -141,28 +141,64 @@ describe Hotcell::Lexer do
141
141
 
142
142
  context 'double quoted' do
143
143
  specify { expression(%q{""}).should == [[:STRING, ""]] }
144
- specify { expression(%q{"foo"}).should == [[:STRING, "foo"]] }
145
- specify { expression(%q{"fo'o"}).should == [[:STRING, "fo'o"]] }
146
- specify { expression(%q{"fo\o"}).should == [[:STRING, "fo\o"]] }
147
- specify { expression(%q{"fo\"o"}).should == [[:STRING, "fo\"o"]] }
148
- specify { expression(%q{"fo\'o"}).should == [[:STRING, "fo\'o"]] }
149
- specify { expression(%q{"fo\no"}).should == [[:STRING, "fo\no"]] }
150
- specify { expression(%q{"fo\mo"}).should == [[:STRING, "fo\mo"]] }
151
- specify { expression(%q{"foo42"}).should == [[:STRING, "foo42"]] }
152
- specify { expression(%q{"привет"}).should == [[:STRING, "привет"]] }
144
+ specify { expression(%q{"foo"}).should == [[:DOPEN, '"'], [:STRING, "foo"], [:DCLOSE, '"']] }
145
+ specify { expression(%q{"fo'o"}).should == [[:DOPEN, '"'], [:STRING, "fo'o"], [:DCLOSE, '"']] }
146
+ specify { expression(%q{"fo\o"}).should == [[:DOPEN, '"'], [:STRING, "fo\o"], [:DCLOSE, '"']] }
147
+ specify { expression(%q{"fo\"o"}).should == [[:DOPEN, '"'], [:STRING, "fo\"o"], [:DCLOSE, '"']] }
148
+ specify { expression(%q{"fo\'o"}).should == [[:DOPEN, '"'], [:STRING, "fo\'o"], [:DCLOSE, '"']] }
149
+ specify { expression(%q{"fo\no"}).should == [[:DOPEN, '"'], [:STRING, "fo\no"], [:DCLOSE, '"']] }
150
+ specify { expression(%q{"fo\mo"}).should == [[:DOPEN, '"'], [:STRING, "fo\mo"], [:DCLOSE, '"']] }
151
+ specify { expression(%q{"foo42"}).should == [[:DOPEN, '"'], [:STRING, "foo42"], [:DCLOSE, '"']] }
152
+ specify { expression(%q{"привет"}).should == [[:DOPEN, '"'], [:STRING, "привет"], [:DCLOSE, '"']] }
153
153
  # RBX can not handle this
154
154
  # specify { expression(%q{"при\вет"}).should == [[:STRING, "при\вет"]] }
155
155
 
156
156
  context do
157
157
  let(:strings) { data 'dstrings' }
158
158
 
159
- specify { expression(strings).delete_if { |token| token.first == :NEWLINE }.should == [
159
+ specify { expression(strings).delete_if { |token|
160
+ [:NEWLINE, :DOPEN, :DCLOSE].include?(token.first)
161
+ }.should == [
160
162
  [:STRING, "fo\"o"], [:STRING, "fo\o"], [:STRING, "fo\\o"],
161
163
  [:STRING, "fo\no"], [:STRING, "fo\mo"], [:STRING, "fo\to"],
162
164
  [:STRING, "foo\nbar"]
163
165
  ] }
164
166
  end
165
167
  end
168
+
169
+ context 'interpolation' do
170
+ specify { expression('"{ }"').should == [[:DOPEN, '"'], [:STRING, '{ }'], [:DCLOSE, '"']] }
171
+ specify { expression('"#"').should == [[:DOPEN, '"'], [:STRING, '#'], [:DCLOSE, '"']] }
172
+ specify { expression('"# "').should == [[:DOPEN, '"'], [:STRING, '# '], [:DCLOSE, '"']] }
173
+ specify { expression('" #"').should == [[:DOPEN, '"'], [:STRING, ' #'], [:DCLOSE, '"']] }
174
+ specify { expression('"#{ var }"').should == [[:DOPEN, '"'], [:IOPEN, "\#{"], [:IDENTIFER, "var"],
175
+ [:ICLOSE, "}"], [:DCLOSE, '"']] }
176
+ specify { expression('"#{ } world"').should == [[:DOPEN, '"'], [:IOPEN, "\#{"], [:ICLOSE, "}"],
177
+ [:STRING, " world"], [:DCLOSE, '"']] }
178
+ specify { expression('"hello #{ }"').should == [[:DOPEN, '"'], [:STRING, "hello "], [:IOPEN, "\#{"],
179
+ [:ICLOSE, "}"], [:DCLOSE, '"']] }
180
+ specify { expression('"hello#{ } world"').should == [[:DOPEN, '"'], [:STRING, "hello"], [:IOPEN, "\#{"],
181
+ [:ICLOSE, "}"], [:STRING, " world"], [:DCLOSE, '"']] }
182
+ specify { expression('"hello #{ 2 + 3 }world"').should == [[:DOPEN, '"'], [:STRING, "hello "],
183
+ [:IOPEN, "\#{"], [:INTEGER, 2], [:PLUS, "+"], [:INTEGER, 3],
184
+ [:ICLOSE, "}"], [:STRING, "world"], [:DCLOSE, '"']] }
185
+ specify { expression('"\#{ var }"').should == [[:DOPEN, '"'], [:STRING, '#{ var }'], [:DCLOSE, '"']] }
186
+ specify { expression('"#{ } }"').should == [[:DOPEN, '"'], [:IOPEN, "\#{"], [:ICLOSE, "}"],
187
+ [:STRING, ' }'], [:DCLOSE, '"']] }
188
+ specify { expression('"#{ { } }"').should == [[:DOPEN, '"'], [:IOPEN, "\#{"], [:HOPEN, "{"],
189
+ [:HCLOSE, "}"], [:ICLOSE, "}"], [:DCLOSE, '"']] }
190
+ specify { expression('"#{ [ ] }"').should == [[:DOPEN, '"'], [:IOPEN, "\#{"], [:AOPEN, "["],
191
+ [:ACLOSE, "]"], [:ICLOSE, "}"], [:DCLOSE, '"']] }
192
+ specify { expression('"#\{ }"').should == [[:DOPEN, '"'], [:STRING, '#{ }'], [:DCLOSE, '"']] }
193
+ specify { expression('"##{ }"').should == [[:DOPEN, '"'], [:STRING, '#'], [:IOPEN, "\#{"], [:ICLOSE, "}"], [:DCLOSE, '"']] }
194
+ specify { expression('"###{ } #{ }"').should == [[:DOPEN, '"'], [:STRING, '##'], [:IOPEN, "\#{"],
195
+ [:ICLOSE, "}"], [:STRING, ' '], [:IOPEN, "\#{"], [:ICLOSE, "}"], [:DCLOSE, '"']] }
196
+
197
+ context 'exceptions' do
198
+ specify { expect { expression('"#{"') }.to raise_error Hotcell::UnterminatedString, /`" }}`.*1:7/ }
199
+ specify { expect { expression('"#{ { }"') }.to raise_error Hotcell::UnterminatedString, /`" }}`.*1:11/ }
200
+ end
201
+ end
166
202
  end
167
203
 
168
204
  context 'regexp' do
@@ -204,7 +240,8 @@ describe Hotcell::Lexer do
204
240
  [:INTEGER, 42], [:SEMICOLON, ";"], [:REGEXP, /regexp/]
205
241
  ] }
206
242
  specify { expression('"hello" /regexp/').should == [
207
- [:STRING, "hello"], [:DIVIDE, "/"], [:IDENTIFER, "regexp"], [:DIVIDE, "/"]
243
+ [:DOPEN, '"'], [:STRING, "hello"], [:DCLOSE, '"'],
244
+ [:DIVIDE, "/"], [:IDENTIFER, "regexp"], [:DIVIDE, "/"]
208
245
  ] }
209
246
  end
210
247
  end
@@ -274,11 +311,13 @@ describe Hotcell::Lexer do
274
311
  specify { expression("foo(36.6);\n a = \"привет\"").should == [
275
312
  [:IDENTIFER, "foo"], [:POPEN, "("], [:FLOAT, 36.6],
276
313
  [:PCLOSE, ")"], [:SEMICOLON, ";"], [:NEWLINE, "\n"],
277
- [:IDENTIFER, "a"], [:ASSIGN, "="], [:STRING, "привет"]
314
+ [:IDENTIFER, "a"], [:ASSIGN, "="], [:DOPEN, '"'],
315
+ [:STRING, "привет"], [:DCLOSE, '"']
278
316
  ] }
279
317
  specify { expression("'foo'.match(\"^foo$\")").should == [
280
318
  [:STRING, "foo"], [:PERIOD, "."], [:IDENTIFER, "match"],
281
- [:POPEN, "("], [:STRING, "^foo$"], [:PCLOSE, ")"]
319
+ [:POPEN, "("], [:DOPEN, '"'], [:STRING, "^foo$"],
320
+ [:DCLOSE, '"'], [:PCLOSE, ")"]
282
321
  ] }
283
322
  end
284
323
 
@@ -218,6 +218,54 @@ describe Hotcell::Parser do
218
218
  end
219
219
  end
220
220
 
221
+ context 'ternary operator' do
222
+ specify { parse('{{ true ? 42 : 43 }}').should be_equal_node_to JOINER(TAG(42, mode: :normal)) }
223
+ specify { parse('{{ false ? 42 : 43 }}').should be_equal_node_to JOINER(TAG(43, mode: :normal)) }
224
+ specify { parse('{{ cond ? branch1 : branch2 }}').should be_equal_node_to JOINER(
225
+ TAG(TERNARY(METHOD('cond'), METHOD('branch1'), METHOD('branch2')), mode: :normal)
226
+ ) }
227
+ specify { parse('{{ cond1 ? branch1 : cond2 ? branch2 : branch3 }}').should be_equal_node_to JOINER(
228
+ TAG(TERNARY(
229
+ METHOD('cond1'), METHOD('branch1'),
230
+ TERNARY(METHOD('cond2'), METHOD('branch2'), METHOD('branch3'))
231
+ ), mode: :normal)
232
+ ) }
233
+ specify { parse('{{ (cond1 ? branch1 : cond2) ? branch2 : branch3 }}').should be_equal_node_to JOINER(
234
+ TAG(TERNARY(
235
+ TERNARY(METHOD('cond1'), METHOD('branch1'), METHOD('cond2')),
236
+ METHOD('branch2'), METHOD('branch3')
237
+ ), mode: :normal)
238
+ ) }
239
+ specify { parse('{{ cond1 ? cond2 ? branch1 : branch2 : branch3 }}').should be_equal_node_to JOINER(
240
+ TAG(TERNARY(
241
+ METHOD('cond1'),
242
+ TERNARY(METHOD('cond2'), METHOD('branch1'), METHOD('branch2')),
243
+ METHOD('branch3')
244
+ ), mode: :normal)
245
+ ) }
246
+ specify { parse('{{ res = cond ? branch1 + 3 : branch2 && true }}').should be_equal_node_to JOINER(
247
+ TAG(ASSIGN('res',
248
+ TERNARY(METHOD('cond'), PLUS(METHOD('branch1'), 3), AND(METHOD('branch2'), true)
249
+ )), mode: :normal)
250
+ ) }
251
+ specify { expect { parse('{{ (1 ? 2) : 3 }}') }.to raise_error Hotcell::UnexpectedLexem, "Unexpected PCLOSE `)` at 1:10"}
252
+ end
253
+
254
+ context 'interpolation' do
255
+ specify { parse('{{ "hello" }}').should be_equal_node_to JOINER(TAG('hello', mode: :normal)) }
256
+ specify { parse('{{ \'#{}\' }}').should be_equal_node_to JOINER(TAG('#{}', mode: :normal)) }
257
+ specify { parse('{{ "#{}" }}').should be_equal_node_to JOINER(TAG('', mode: :normal)) }
258
+ specify { parse('{{ "#{ 3 }" }}').should be_equal_node_to JOINER(TAG('3', mode: :normal)) }
259
+ specify { parse('{{ "#{ var }" }}').should be_equal_node_to JOINER(TAG(DSTRING(METHOD('var')), mode: :normal)) }
260
+ specify { parse('{{ "#{ var }world" }}').should be_equal_node_to JOINER(TAG(DSTRING(METHOD('var'), 'world'), mode: :normal)) }
261
+ specify { parse('{{ "hello#{}" }}').should be_equal_node_to JOINER(TAG('hello', mode: :normal)) }
262
+ specify { parse('{{ "hello#{}world" }}').should be_equal_node_to JOINER(TAG('helloworld', mode: :normal)) }
263
+ specify { parse('{{ "hello#{ 6 * 7 }world" }}').should be_equal_node_to JOINER(TAG('hello42world', mode: :normal)) }
264
+ specify { parse('{{ "hello#{ var = 6 * 7; var + 1 }world" }}').should be_equal_node_to JOINER(
265
+ TAG(DSTRING('hello', SEQUENCE(ASSIGN('var', 42), PLUS(METHOD('var'), 1)), 'world'), mode: :normal)
266
+ ) }
267
+ end
268
+
221
269
  context 'arrays' do
222
270
  specify { parse('{{ [] }}').should be_equal_node_to JOINER(TAG(ARRAY(), mode: :normal)) }
223
271
  specify { parse('{{ [ 2 ] }}').should be_equal_node_to JOINER(TAG(ARRAY(2), mode: :normal)) }
@@ -67,11 +67,11 @@ describe Hotcell::Template do
67
67
  specify { described_class.parse(<<-SOURCE
68
68
  {{ for i, in: [1, 2, 3, 4] }}
69
69
  {{ if i % 2 == 1 }}
70
- {{ i }}
70
+ {{ "number\#{i}" }}
71
71
  {{ end if }}
72
72
  {{ end for }}
73
73
  SOURCE
74
- ).render.gsub(/[\s\n]+/, ' ').strip.should == '1 3' }
74
+ ).render.gsub(/[\s\n]+/, ' ').strip.should == 'number1 number3' }
75
75
 
76
76
  specify { described_class.parse(<<-SOURCE
77
77
  {{ for i, in: [1, 2, 3, 4] }}
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Tong do
4
+ context 'mixed in' do
5
+ context do
6
+ let(:klass) do
7
+ Class.new(Numeric) do
8
+ include Hotcell::Tong::Mixin
9
+
10
+ def foo; end
11
+ end
12
+ end
13
+ subject { klass.new }
14
+
15
+ its(:tong_methods) { should be_a Set }
16
+ its(:tong_methods) { should be_empty }
17
+ its(:to_tong) { should === subject }
18
+ specify { subject.tong_invoke('foo').should be_nil }
19
+ specify { subject.tong_invoke(:foo).should be_nil }
20
+ specify { subject.tong_invoke('foo', 42, :arg).should be_nil }
21
+ end
22
+
23
+ context do
24
+ let(:klass) do
25
+ Class.new(String) do
26
+ include Hotcell::Tong::Mixin
27
+
28
+ manipulate :foo, :bar
29
+
30
+ alias_method :foo, :split
31
+ alias_method :bar, :size
32
+ end
33
+ end
34
+ subject { klass.new('hello world') }
35
+
36
+ its(:to_tong) { should === subject }
37
+ specify { (subject.tong_methods.to_a & %w(foo bar)).should =~ %w(foo bar) }
38
+ specify { subject.tong_invoke('baz').should be_nil }
39
+ specify { subject.tong_invoke('baz', 42, :arg).should be_nil }
40
+ specify { subject.tong_invoke(:foo).should be_nil }
41
+ specify { subject.tong_invoke('foo').should == %w(hello world) }
42
+ specify { subject.tong_invoke('bar').should == 11 }
43
+ specify { expect { subject.tong_invoke('bar', 42) }.to raise_error ArgumentError }
44
+ end
45
+ end
46
+
47
+ context 'inherited' do
48
+ let(:klass) do
49
+ Class.new(described_class) do
50
+ def foo
51
+ 'foo'
52
+ end
53
+
54
+ def bar arg1, arg2
55
+ arg1 + arg2
56
+ end
57
+ end
58
+ end
59
+ subject { klass.new }
60
+
61
+ its('tong_methods.to_a') { should =~ %w(foo bar) }
62
+ its(:to_tong) { should === subject }
63
+ specify { subject.tong_invoke('send').should be_nil }
64
+ specify { subject.tong_invoke(:bar).should be_nil }
65
+ specify { subject.tong_invoke('foo').should == 'foo' }
66
+ specify { subject.tong_invoke('bar', 5, 8).should == 13 }
67
+ specify { expect { subject.tong_invoke('bar') }.to raise_error ArgumentError }
68
+ end
69
+
70
+ describe '#tong_missing' do
71
+ let(:klass) do
72
+ Class.new(described_class) do
73
+ def tong_missing method, *args
74
+ "missing #{method} with #{args}"
75
+ end
76
+ end
77
+ end
78
+ subject { klass.new }
79
+
80
+ specify { subject.tong_invoke('foo').should == 'missing foo with []' }
81
+ specify { subject.tong_invoke('bar', 42).should == 'missing bar with [42]' }
82
+ end
83
+ end