punchblock 0.8.1 → 0.8.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # develop
2
2
 
3
+ # v0.8.2 - 2012-01-10
4
+ * Feature: Support outbound dial on Asterisk
5
+ * Bugfix: Asterisk hangup causes should map to correct Rayo End reason
6
+
3
7
  # v0.8.1 - 2012-01-09
4
8
  * Feature: Support DTMF Input components on Asterisk
5
9
 
@@ -53,7 +53,7 @@ module Punchblock
53
53
  end
54
54
 
55
55
  def inspect_attributes # :nodoc:
56
- [:name] + super
56
+ [:name, :params] + super
57
57
  end
58
58
 
59
59
  class Param < RayoNode
@@ -30,7 +30,9 @@ module Punchblock
30
30
  end
31
31
 
32
32
  def call_for_channel(channel)
33
- call_with_id @channel_to_call_id[channel]
33
+ call = call_with_id @channel_to_call_id[channel]
34
+ pb_logger.trace "Looking up call for channel #{channel} from #{@channel_to_call_id}. Found: #{call || 'none'}"
35
+ call
34
36
  end
35
37
 
36
38
  def register_component(component)
@@ -53,13 +55,22 @@ module Punchblock
53
55
  end
54
56
  return
55
57
  end
56
- if event.name.downcase == "asyncagi" && event['SubEvent'] == "Start"
57
- handle_async_agi_start_event event
58
+
59
+ if event.name == 'VarSet' && event['Variable'] == 'punchblock_call_id' && (call = call_with_id event['Value'])
60
+ pb_logger.trace "Received a VarSet event indicating the full channel for call #{call}"
61
+ @channel_to_call_id.delete call.channel
62
+ pb_logger.trace "Removed call with old channel from channel map: #{@channel_to_call_id}"
63
+ call.channel = event['Channel']
64
+ register_call call
58
65
  end
66
+
59
67
  if call = call_for_channel(event['Channel'])
60
68
  pb_logger.trace "Found call by channel matching this event. Sending to call #{call.id}"
61
69
  call.process_ami_event! event
70
+ elsif event.name.downcase == "asyncagi" && event['SubEvent'] == "Start"
71
+ handle_async_agi_start_event event
62
72
  end
73
+
63
74
  handle_pb_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers)
64
75
  end
65
76
 
@@ -92,9 +103,16 @@ module Punchblock
92
103
  end
93
104
 
94
105
  def execute_global_command(command)
95
- component = Component::Asterisk::AMIAction.new command, current_actor
96
- register_component component
97
- component.execute!
106
+ case command
107
+ when Punchblock::Component::Asterisk::AMI::Action
108
+ component = Component::Asterisk::AMIAction.new command, current_actor
109
+ register_component component
110
+ component.execute!
111
+ when Punchblock::Command::Dial
112
+ call = Call.new command.to, current_actor
113
+ register_call call
114
+ call.dial! command
115
+ end
98
116
  end
99
117
 
100
118
  def send_ami_action(name, headers = {}, &block)
@@ -7,7 +7,16 @@ module Punchblock
7
7
  include HasGuardedHandlers
8
8
  include Celluloid
9
9
 
10
- attr_reader :id, :channel, :translator, :agi_env
10
+ attr_reader :id, :channel, :translator, :agi_env, :direction
11
+
12
+ HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
13
+ HANGUP_CAUSE_TO_END_REASON[16] = :hangup
14
+ HANGUP_CAUSE_TO_END_REASON[17] = :busy
15
+ HANGUP_CAUSE_TO_END_REASON[18] = :timeout
16
+ HANGUP_CAUSE_TO_END_REASON[19] = :reject
17
+ HANGUP_CAUSE_TO_END_REASON[21] = :reject
18
+ HANGUP_CAUSE_TO_END_REASON[22] = :reject
19
+ HANGUP_CAUSE_TO_END_REASON[102] = :timeout
11
20
 
12
21
  def initialize(channel, translator, agi_env = '')
13
22
  @channel, @translator = channel, translator
@@ -25,15 +34,49 @@ module Punchblock
25
34
  end
26
35
 
27
36
  def send_offer
37
+ @direction = :inbound
28
38
  send_pb_event offer_event
29
39
  end
30
40
 
41
+ def to_s
42
+ "#<#{self.class}:#{id} Channel: #{channel.inspect}>"
43
+ end
44
+
45
+ def dial(dial_command)
46
+ @direction = :outbound
47
+ originate_action = Punchblock::Component::Asterisk::AMI::Action.new :name => 'Originate',
48
+ :params => {
49
+ :async => true,
50
+ :application => 'AGI',
51
+ :data => 'agi:async',
52
+ :channel => dial_command.to,
53
+ :callerid => dial_command.from,
54
+ :variable => "punchblock_call_id=#{id}"
55
+ }
56
+ originate_action.request!
57
+ translator.execute_global_command! originate_action
58
+ dial_command.response = Ref.new :id => id
59
+ end
60
+
61
+ def outbound?
62
+ direction == :outbound
63
+ end
64
+
65
+ def inbound?
66
+ direction == :inbound
67
+ end
68
+
69
+ def channel=(other)
70
+ pb_logger.info "Channel is changing from #{channel} to #{other}."
71
+ @channel = other
72
+ end
73
+
31
74
  def process_ami_event(ami_event)
32
75
  pb_logger.trace "Processing AMI event #{ami_event.inspect}"
33
76
  case ami_event.name
34
77
  when 'Hangup'
35
78
  pb_logger.debug "Received a Hangup AMI event. Sending End event."
36
- send_pb_event Event::End.new(:reason => :hangup)
79
+ send_pb_event Event::End.new(:reason => HANGUP_CAUSE_TO_END_REASON[ami_event['Cause'].to_i])
37
80
  when 'AsyncAGI'
38
81
  pb_logger.debug "Received an AsyncAGI event. Looking for matching AGICommand component."
39
82
  if component = component_with_id(ami_event['CommandID'])
@@ -42,6 +85,14 @@ module Punchblock
42
85
  else
43
86
  pb_logger.debug "Could not find component for AMI event: #{ami_event}"
44
87
  end
88
+ when 'Newstate'
89
+ pb_logger.debug "Received a Newstate AMI event with state #{ami_event['ChannelState']}: #{ami_event['ChannelStateDesc']}"
90
+ case ami_event['ChannelState']
91
+ when '5'
92
+ send_pb_event Event::Ringing.new
93
+ when '6'
94
+ send_pb_event Event::Answered.new
95
+ end
45
96
  end
46
97
  trigger_handler :ami, ami_event
47
98
  end
@@ -53,8 +104,14 @@ module Punchblock
53
104
  end
54
105
  case command
55
106
  when Command::Accept
56
- send_agi_action 'EXEC RINGING' do |response|
107
+ if outbound?
108
+ pb_logger.trace "Attempting to accept an outbound call. Skipping RINGING."
57
109
  command.response = true
110
+ else
111
+ pb_logger.trace "Attempting to accept an inbound call. Executing RINGING."
112
+ send_agi_action 'EXEC RINGING' do |response|
113
+ command.response = true
114
+ end
58
115
  end
59
116
  when Command::Answer
60
117
  send_agi_action 'EXEC ANSWER' do |response|
@@ -1,3 +1,3 @@
1
1
  module Punchblock
2
- VERSION = "0.8.1"
2
+ VERSION = "0.8.2"
3
3
  end
@@ -52,6 +52,26 @@ module Punchblock
52
52
  :async => '1'} }
53
53
  end
54
54
 
55
+ describe "testing equality" do
56
+ context "with the same name and params" do
57
+ it "should be equal" do
58
+ Action.new(:name => 'Originate', :params => { :channel => 'SIP/101test' }).should == Action.new(:name => 'Originate', :params => { :channel => 'SIP/101test' })
59
+ end
60
+ end
61
+
62
+ context "with the same name and different params" do
63
+ it "should be equal" do
64
+ Action.new(:name => 'Originate', :params => { :channel => 'SIP/101' }).should_not == Action.new(:name => 'Originate', :params => { :channel => 'SIP/101test' })
65
+ end
66
+ end
67
+
68
+ context "with a different name and the same params" do
69
+ it "should be equal" do
70
+ Action.new(:name => 'Hangup', :params => { :channel => 'SIP/101test' }).should_not == Action.new(:name => 'Originate', :params => { :channel => 'SIP/101test' })
71
+ end
72
+ end
73
+ end
74
+
55
75
  describe "when setting options in initializer" do
56
76
  subject do
57
77
  Action.new :name => 'Originate',
@@ -82,6 +82,58 @@ module Punchblock
82
82
  translator.expects(:handle_pb_event!).with expected_offer
83
83
  subject.send_offer
84
84
  end
85
+
86
+ it 'should make the call identify as inbound' do
87
+ subject.send_offer
88
+ subject.direction.should == :inbound
89
+ subject.inbound?.should be true
90
+ subject.outbound?.should be false
91
+ end
92
+ end
93
+
94
+ describe '#dial' do
95
+ let :dial_command do
96
+ Punchblock::Command::Dial.new :to => 'SIP/1234', :from => 'sip:foo@bar.com'
97
+ end
98
+
99
+ before { dial_command.request! }
100
+
101
+ it 'sends an Originate AMI action' do
102
+ expected_action = Punchblock::Component::Asterisk::AMI::Action.new :name => 'Originate',
103
+ :params => {
104
+ :async => true,
105
+ :application => 'AGI',
106
+ :data => 'agi:async',
107
+ :channel => 'SIP/1234',
108
+ :callerid => 'sip:foo@bar.com',
109
+ :variable => "punchblock_call_id=#{subject.id}"
110
+ }
111
+
112
+ translator.expects(:execute_global_command!).once.with expected_action
113
+ subject.dial dial_command
114
+ end
115
+
116
+ it 'sends the call ID as a response to the Dial' do
117
+ subject.dial dial_command
118
+ dial_command.response
119
+ dial_command.call_id.should == subject.id
120
+ end
121
+
122
+ it 'should make the call identify as outbound' do
123
+ subject.dial dial_command
124
+ subject.direction.should == :outbound
125
+ subject.outbound?.should be true
126
+ subject.inbound?.should be false
127
+ end
128
+
129
+ it 'causes accepting the call to be a null operation' do
130
+ subject.dial dial_command
131
+ accept_command = Command::Accept.new
132
+ accept_command.request!
133
+ subject.wrapped_object.expects(:send_agi_action).never
134
+ subject.execute_command accept_command
135
+ accept_command.response(0.5).should be true
136
+ end
85
137
  end
86
138
 
87
139
  describe '#process_ami_event' do
@@ -91,17 +143,121 @@ module Punchblock
91
143
  e['Uniqueid'] = "1320842458.8"
92
144
  e['Calleridnum'] = "5678"
93
145
  e['Calleridname'] = "Jane Smith"
94
- e['Cause'] = "0"
95
- e['Cause-txt'] = "Unknown"
146
+ e['Cause'] = cause
147
+ e['Cause-txt'] = cause_txt
96
148
  e['Channel'] = "SIP/1234-00000000"
97
149
  end
98
150
  end
99
151
 
100
- it 'should send an end event to the translator' do
101
- expected_end_event = Punchblock::Event::End.new :reason => :hangup,
102
- :call_id => subject.id
103
- translator.expects(:handle_pb_event!).with expected_end_event
104
- subject.process_ami_event ami_event
152
+ context "with a normal clearing cause" do
153
+ let(:cause) { '16' }
154
+ let(:cause_txt) { 'Normal Clearing' }
155
+
156
+ it 'should send an end (hangup) event to the translator' do
157
+ expected_end_event = Punchblock::Event::End.new :reason => :hangup,
158
+ :call_id => subject.id
159
+ translator.expects(:handle_pb_event!).with expected_end_event
160
+ subject.process_ami_event ami_event
161
+ end
162
+ end
163
+
164
+ context "with a user busy cause" do
165
+ let(:cause) { '17' }
166
+ let(:cause_txt) { 'User Busy' }
167
+
168
+ it 'should send an end (busy) event to the translator' do
169
+ expected_end_event = Punchblock::Event::End.new :reason => :busy,
170
+ :call_id => subject.id
171
+ translator.expects(:handle_pb_event!).with expected_end_event
172
+ subject.process_ami_event ami_event
173
+ end
174
+ end
175
+
176
+ {
177
+ 18 => 'No user response',
178
+ 102 => 'Recovery on timer expire'
179
+ }.each_pair do |cause, cause_txt|
180
+ context "with a #{cause_txt} cause" do
181
+ let(:cause) { cause.to_s }
182
+ let(:cause_txt) { cause_txt }
183
+
184
+ it 'should send an end (timeout) event to the translator' do
185
+ expected_end_event = Punchblock::Event::End.new :reason => :timeout,
186
+ :call_id => subject.id
187
+ translator.expects(:handle_pb_event!).with expected_end_event
188
+ subject.process_ami_event ami_event
189
+ end
190
+ end
191
+ end
192
+
193
+ {
194
+ 19 => 'No Answer',
195
+ 21 => 'Call Rejected',
196
+ 22 => 'Number Changed'
197
+ }.each_pair do |cause, cause_txt|
198
+ context "with a #{cause_txt} cause" do
199
+ let(:cause) { cause.to_s }
200
+ let(:cause_txt) { cause_txt }
201
+
202
+ it 'should send an end (reject) event to the translator' do
203
+ expected_end_event = Punchblock::Event::End.new :reason => :reject,
204
+ :call_id => subject.id
205
+ translator.expects(:handle_pb_event!).with expected_end_event
206
+ subject.process_ami_event ami_event
207
+ end
208
+ end
209
+ end
210
+
211
+ {
212
+ 1 => 'AST_CAUSE_UNALLOCATED',
213
+ 2 => 'NO_ROUTE_TRANSIT_NET',
214
+ 3 => 'NO_ROUTE_DESTINATION',
215
+ 6 => 'CHANNEL_UNACCEPTABLE',
216
+ 7 => 'CALL_AWARDED_DELIVERED',
217
+ 27 => 'DESTINATION_OUT_OF_ORDER',
218
+ 28 => 'INVALID_NUMBER_FORMAT',
219
+ 29 => 'FACILITY_REJECTED',
220
+ 30 => 'RESPONSE_TO_STATUS_ENQUIRY',
221
+ 31 => 'NORMAL_UNSPECIFIED',
222
+ 34 => 'NORMAL_CIRCUIT_CONGESTION',
223
+ 38 => 'NETWORK_OUT_OF_ORDER',
224
+ 41 => 'NORMAL_TEMPORARY_FAILURE',
225
+ 42 => 'SWITCH_CONGESTION',
226
+ 43 => 'ACCESS_INFO_DISCARDED',
227
+ 44 => 'REQUESTED_CHAN_UNAVAIL',
228
+ 45 => 'PRE_EMPTED',
229
+ 50 => 'FACILITY_NOT_SUBSCRIBED',
230
+ 52 => 'OUTGOING_CALL_BARRED',
231
+ 54 => 'INCOMING_CALL_BARRED',
232
+ 57 => 'BEARERCAPABILITY_NOTAUTH',
233
+ 58 => 'BEARERCAPABILITY_NOTAVAIL',
234
+ 65 => 'BEARERCAPABILITY_NOTIMPL',
235
+ 66 => 'CHAN_NOT_IMPLEMENTED',
236
+ 69 => 'FACILITY_NOT_IMPLEMENTED',
237
+ 81 => 'INVALID_CALL_REFERENCE',
238
+ 88 => 'INCOMPATIBLE_DESTINATION',
239
+ 95 => 'INVALID_MSG_UNSPECIFIED',
240
+ 96 => 'MANDATORY_IE_MISSING',
241
+ 97 => 'MESSAGE_TYPE_NONEXIST',
242
+ 98 => 'WRONG_MESSAGE',
243
+ 99 => 'IE_NONEXIST',
244
+ 100 => 'INVALID_IE_CONTENTS',
245
+ 101 => 'WRONG_CALL_STATE',
246
+ 103 => 'MANDATORY_IE_LENGTH_ERROR',
247
+ 111 => 'PROTOCOL_ERROR',
248
+ 127 => 'INTERWORKING'
249
+ }.each_pair do |cause, cause_txt|
250
+ context "with a #{cause_txt} cause" do
251
+ let(:cause) { cause.to_s }
252
+ let(:cause_txt) { cause_txt }
253
+
254
+ it 'should send an end (error) event to the translator' do
255
+ expected_end_event = Punchblock::Event::End.new :reason => :error,
256
+ :call_id => subject.id
257
+ translator.expects(:handle_pb_event!).with expected_end_event
258
+ subject.process_ami_event ami_event
259
+ end
260
+ end
105
261
  end
106
262
  end
107
263
 
@@ -131,6 +287,46 @@ module Punchblock
131
287
  end
132
288
  end
133
289
 
290
+ context 'with a Newstate event' do
291
+ let :ami_event do
292
+ RubyAMI::Event.new('Newstate').tap do |e|
293
+ e['Privilege'] = 'call,all'
294
+ e['Channel'] = 'SIP/1234-00000000'
295
+ e['ChannelState'] = channel_state
296
+ e['ChannelStateDesc'] = channel_state_desc
297
+ e['CallerIDNum'] = ''
298
+ e['CallerIDName'] = ''
299
+ e['ConnectedLineNum'] = ''
300
+ e['ConnectedLineName'] = ''
301
+ e['Uniqueid'] = '1326194671.0'
302
+ end
303
+ end
304
+
305
+ context 'ringing' do
306
+ let(:channel_state) { '5' }
307
+ let(:channel_state_desc) { 'Ringing' }
308
+
309
+ it 'should send a ringing event' do
310
+ expected_ringing = Punchblock::Event::Ringing.new
311
+ expected_ringing.call_id = subject.id
312
+ translator.expects(:handle_pb_event!).with expected_ringing
313
+ subject.process_ami_event ami_event
314
+ end
315
+ end
316
+
317
+ context 'up' do
318
+ let(:channel_state) { '6' }
319
+ let(:channel_state_desc) { 'Up' }
320
+
321
+ it 'should send a ringing event' do
322
+ expected_answered = Punchblock::Event::Answered.new
323
+ expected_answered.call_id = subject.id
324
+ translator.expects(:handle_pb_event!).with expected_answered
325
+ subject.process_ami_event ami_event
326
+ end
327
+ end
328
+ end
329
+
134
330
  context 'with a handler registered for a matching event' do
135
331
  let :ami_event do
136
332
  RubyAMI::Event.new('DTMF').tap do |e|
@@ -126,7 +126,26 @@ module Punchblock
126
126
 
127
127
  describe '#execute_global_command' do
128
128
  context 'with a Dial' do
129
- pending
129
+ let :command do
130
+ Command::Dial.new :to => 'SIP/1234', :from => 'abc123'
131
+ end
132
+
133
+ before { command.request! }
134
+
135
+ let(:mock_action) { stub_everything 'Asterisk::Component::Asterisk::AMIAction' }
136
+
137
+ it 'should be able to look up the call by channel ID' do
138
+ subject.execute_global_command command
139
+ call_actor = subject.call_for_channel('SIP/1234')
140
+ call_actor.wrapped_object.should be_a Asterisk::Call
141
+ end
142
+
143
+ it 'should instruct the call to send a dial' do
144
+ mock_call = stub_everything 'Asterisk::Call'
145
+ Asterisk::Call.expects(:new).once.returns mock_call
146
+ mock_call.expects(:dial!).once.with command
147
+ subject.execute_global_command command
148
+ end
130
149
  end
131
150
 
132
151
  context 'with an AMI action' do
@@ -232,6 +251,72 @@ module Punchblock
232
251
  mock_call.expects(:send_offer!).once
233
252
  subject.handle_ami_event ami_event
234
253
  end
254
+
255
+ context 'if a call already exists for a matching channel' do
256
+ let(:call) { Asterisk::Call.new "SIP/1234-00000000", subject }
257
+
258
+ before do
259
+ subject.register_call call
260
+ end
261
+
262
+ it "should not create a new call" do
263
+ Asterisk::Call.expects(:new).never
264
+ subject.handle_ami_event ami_event
265
+ end
266
+ end
267
+ end
268
+
269
+ describe 'with a VarSet event including a punchblock_call_id' do
270
+ let :ami_event do
271
+ RubyAMI::Event.new('VarSet').tap do |e|
272
+ e["Privilege"] = "dialplan,all"
273
+ e["Channel"] = "SIP/1234-00000000"
274
+ e["Variable"] = "punchblock_call_id"
275
+ e["Value"] = call_id
276
+ e["Uniqueid"] = "1326210224.0"
277
+ end
278
+ end
279
+
280
+ before do
281
+ ami_client.stub_everything
282
+ subject.wrapped_object.stubs :handle_pb_event
283
+ end
284
+
285
+ context "matching a call that was created by a Dial command" do
286
+ let(:dial_command) { Punchblock::Command::Dial.new :to => 'SIP/1234', :from => 'abc123' }
287
+
288
+ before do
289
+ dial_command.request!
290
+ subject.execute_global_command dial_command
291
+ call
292
+ end
293
+
294
+ let(:call) { subject.call_for_channel 'SIP/1234' }
295
+ let(:call_id) { call.id }
296
+
297
+ it "should set the correct channel on the call" do
298
+ subject.handle_ami_event ami_event
299
+ call.channel.should == 'SIP/1234-00000000'
300
+ end
301
+
302
+ it "should make it possible to look up the call by the full channel name" do
303
+ subject.handle_ami_event ami_event
304
+ subject.call_for_channel("SIP/1234-00000000").should be call
305
+ end
306
+
307
+ it "should make looking up the channel by the requested channel name impossible" do
308
+ subject.handle_ami_event ami_event
309
+ subject.call_for_channel('SIP/1234').should be_nil
310
+ end
311
+ end
312
+
313
+ context "for a call that doesn't exist" do
314
+ let(:call_id) { 'foobarbaz' }
315
+
316
+ it "should not raise" do
317
+ lambda { subject.handle_ami_event ami_event }.should_not raise_error
318
+ end
319
+ end
235
320
  end
236
321
 
237
322
  describe 'with an AMI event for a known channel' do
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
3
3
  require 'punchblock'
4
4
  require 'mocha'
5
5
  require 'countdownlatch'
6
+ require 'logger'
6
7
 
7
8
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
8
9
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: punchblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-01-09 00:00:00.000000000 Z
14
+ date: 2012-01-10 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: niceogiri
18
- requirement: &2156232960 !ruby/object:Gem::Requirement
18
+ requirement: &2168470140 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ! '>='
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: 0.0.4
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *2156232960
26
+ version_requirements: *2168470140
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: blather
29
- requirement: &2156230140 !ruby/object:Gem::Requirement
29
+ requirement: &2168468720 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: 0.5.12
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *2156230140
37
+ version_requirements: *2168468720
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: activesupport
40
- requirement: &2156227040 !ruby/object:Gem::Requirement
40
+ requirement: &2168467360 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: 2.1.0
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *2156227040
48
+ version_requirements: *2168467360
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: state_machine
51
- requirement: &2156223280 !ruby/object:Gem::Requirement
51
+ requirement: &2168466140 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: 1.0.1
57
57
  type: :runtime
58
58
  prerelease: false
59
- version_requirements: *2156223280
59
+ version_requirements: *2168466140
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: future-resource
62
- requirement: &2156218340 !ruby/object:Gem::Requirement
62
+ requirement: &2168464520 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: 0.0.2
68
68
  type: :runtime
69
69
  prerelease: false
70
- version_requirements: *2156218340
70
+ version_requirements: *2168464520
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: has-guarded-handlers
73
- requirement: &2156212420 !ruby/object:Gem::Requirement
73
+ requirement: &2168508400 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: 0.1.0
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *2156212420
81
+ version_requirements: *2168508400
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: celluloid
84
- requirement: &2156209840 !ruby/object:Gem::Requirement
84
+ requirement: &2168506560 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,10 +89,10 @@ dependencies:
89
89
  version: 0.6.0
90
90
  type: :runtime
91
91
  prerelease: false
92
- version_requirements: *2156209840
92
+ version_requirements: *2168506560
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: ruby_ami
95
- requirement: &2156188760 !ruby/object:Gem::Requirement
95
+ requirement: &2168504680 !ruby/object:Gem::Requirement
96
96
  none: false
97
97
  requirements:
98
98
  - - ! '>='
@@ -100,10 +100,10 @@ dependencies:
100
100
  version: 0.1.3
101
101
  type: :runtime
102
102
  prerelease: false
103
- version_requirements: *2156188760
103
+ version_requirements: *2168504680
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: ruby_speech
106
- requirement: &2156167500 !ruby/object:Gem::Requirement
106
+ requirement: &2168503400 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ! '>='
@@ -111,10 +111,10 @@ dependencies:
111
111
  version: 0.5.1
112
112
  type: :runtime
113
113
  prerelease: false
114
- version_requirements: *2156167500
114
+ version_requirements: *2168503400
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: bundler
117
- requirement: &2156166720 !ruby/object:Gem::Requirement
117
+ requirement: &2168500560 !ruby/object:Gem::Requirement
118
118
  none: false
119
119
  requirements:
120
120
  - - ~>
@@ -122,10 +122,10 @@ dependencies:
122
122
  version: 1.0.0
123
123
  type: :development
124
124
  prerelease: false
125
- version_requirements: *2156166720
125
+ version_requirements: *2168500560
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rspec
128
- requirement: &2156165960 !ruby/object:Gem::Requirement
128
+ requirement: &2168497720 !ruby/object:Gem::Requirement
129
129
  none: false
130
130
  requirements:
131
131
  - - ~>
@@ -133,10 +133,10 @@ dependencies:
133
133
  version: 2.7.0
134
134
  type: :development
135
135
  prerelease: false
136
- version_requirements: *2156165960
136
+ version_requirements: *2168497720
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: ci_reporter
139
- requirement: &2156164980 !ruby/object:Gem::Requirement
139
+ requirement: &2152104200 !ruby/object:Gem::Requirement
140
140
  none: false
141
141
  requirements:
142
142
  - - ! '>='
@@ -144,10 +144,10 @@ dependencies:
144
144
  version: 1.6.3
145
145
  type: :development
146
146
  prerelease: false
147
- version_requirements: *2156164980
147
+ version_requirements: *2152104200
148
148
  - !ruby/object:Gem::Dependency
149
149
  name: yard
150
- requirement: &2156163300 !ruby/object:Gem::Requirement
150
+ requirement: &2152102500 !ruby/object:Gem::Requirement
151
151
  none: false
152
152
  requirements:
153
153
  - - ~>
@@ -155,10 +155,10 @@ dependencies:
155
155
  version: 0.6.0
156
156
  type: :development
157
157
  prerelease: false
158
- version_requirements: *2156163300
158
+ version_requirements: *2152102500
159
159
  - !ruby/object:Gem::Dependency
160
160
  name: rcov
161
- requirement: &2156162340 !ruby/object:Gem::Requirement
161
+ requirement: &2152101300 !ruby/object:Gem::Requirement
162
162
  none: false
163
163
  requirements:
164
164
  - - ! '>='
@@ -166,10 +166,10 @@ dependencies:
166
166
  version: '0'
167
167
  type: :development
168
168
  prerelease: false
169
- version_requirements: *2156162340
169
+ version_requirements: *2152101300
170
170
  - !ruby/object:Gem::Dependency
171
171
  name: rake
172
- requirement: &2156161440 !ruby/object:Gem::Requirement
172
+ requirement: &2152100160 !ruby/object:Gem::Requirement
173
173
  none: false
174
174
  requirements:
175
175
  - - ! '>='
@@ -177,10 +177,10 @@ dependencies:
177
177
  version: '0'
178
178
  type: :development
179
179
  prerelease: false
180
- version_requirements: *2156161440
180
+ version_requirements: *2152100160
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: mocha
183
- requirement: &2156160560 !ruby/object:Gem::Requirement
183
+ requirement: &2152097280 !ruby/object:Gem::Requirement
184
184
  none: false
185
185
  requirements:
186
186
  - - ! '>='
@@ -188,10 +188,10 @@ dependencies:
188
188
  version: '0'
189
189
  type: :development
190
190
  prerelease: false
191
- version_requirements: *2156160560
191
+ version_requirements: *2152097280
192
192
  - !ruby/object:Gem::Dependency
193
193
  name: i18n
194
- requirement: &2156135140 !ruby/object:Gem::Requirement
194
+ requirement: &2152084420 !ruby/object:Gem::Requirement
195
195
  none: false
196
196
  requirements:
197
197
  - - ! '>='
@@ -199,10 +199,10 @@ dependencies:
199
199
  version: '0'
200
200
  type: :development
201
201
  prerelease: false
202
- version_requirements: *2156135140
202
+ version_requirements: *2152084420
203
203
  - !ruby/object:Gem::Dependency
204
204
  name: countdownlatch
205
- requirement: &2156134060 !ruby/object:Gem::Requirement
205
+ requirement: &2152079640 !ruby/object:Gem::Requirement
206
206
  none: false
207
207
  requirements:
208
208
  - - ! '>='
@@ -210,10 +210,10 @@ dependencies:
210
210
  version: '0'
211
211
  type: :development
212
212
  prerelease: false
213
- version_requirements: *2156134060
213
+ version_requirements: *2152079640
214
214
  - !ruby/object:Gem::Dependency
215
215
  name: guard-rspec
216
- requirement: &2156132700 !ruby/object:Gem::Requirement
216
+ requirement: &2152075260 !ruby/object:Gem::Requirement
217
217
  none: false
218
218
  requirements:
219
219
  - - ! '>='
@@ -221,7 +221,7 @@ dependencies:
221
221
  version: '0'
222
222
  type: :development
223
223
  prerelease: false
224
- version_requirements: *2156132700
224
+ version_requirements: *2152075260
225
225
  description: Like Rack is to Rails and Sinatra, Punchblock provides a consistent API
226
226
  on top of several underlying third-party call control protocols.
227
227
  email: punchblock@adhearsion.com
@@ -363,7 +363,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
363
363
  version: '0'
364
364
  segments:
365
365
  - 0
366
- hash: -3178520051176557915
366
+ hash: -2044158183945900193
367
367
  required_rubygems_version: !ruby/object:Gem::Requirement
368
368
  none: false
369
369
  requirements: