punchblock 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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: