ruby_ami 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -4,7 +4,7 @@ rvm:
4
4
  - 1.9.3
5
5
  - jruby-19mode
6
6
  # - rbx-19mode
7
- - ruby-head
7
+ # - ruby-head
8
8
  before_install:
9
9
  - wget http://ftp.us.debian.org/debian/pool/main/r/ragel/ragel_6.7-1.1_i386.deb
10
10
  - sudo dpkg -i ragel_6.7-1.1_i386.deb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # [develop](https://github.com/adhearsion/ruby_ami)
2
2
 
3
+ # [1.2.0](https://github.com/adhearsion/ruby_ami/compare/v1.1.2...v1.2.0) - [2012-07-18](https://rubygems.org/gems/ruby_ami/versions/1.2.0)
4
+ * Feature: Added parsers for (Async)AGI environment and result strings
5
+ * Bugfix: Avoid a race condition in stream establishment and event receipt
6
+ * Bugfix: If socket creation fails, log an appropriate error
7
+
3
8
  # [1.1.2](https://github.com/adhearsion/ruby_ami/compare/v1.1.1...v1.1.2) - [2012-07-04](https://rubygems.org/gems/ruby_ami/versions/1.1.2)
4
9
  * Bugfix: Avoid recursive stream stopping
5
10
 
@@ -0,0 +1,42 @@
1
+ require 'cgi'
2
+
3
+ module RubyAMI
4
+ class AGIResultParser
5
+ attr_reader :code, :result, :data
6
+
7
+ FORMAT = /^(\d{3}) result=(-?\d*) ?(\(?.*\)?)?$/.freeze
8
+ DATA_KV_FORMAT = /([\w\d]+)=([\w\d]*)/.freeze
9
+ DATA_CLEANER = /(^\()|(\)$)/.freeze
10
+
11
+ def initialize(result_string)
12
+ @result_string = result_string.dup
13
+ raise ArgumentError, "The result string did not match the required format." unless match
14
+ parse
15
+ end
16
+
17
+ def data_hash
18
+ return unless data_kv_match
19
+ {data_kv_match[1] => data_kv_match[2]}
20
+ end
21
+
22
+ private
23
+
24
+ def unescape
25
+ CGI.unescape @result_string
26
+ end
27
+
28
+ def match
29
+ @match ||= unescape.chomp.match(FORMAT)
30
+ end
31
+
32
+ def parse
33
+ @code = match[1].to_i
34
+ @result = match[2].to_i
35
+ @data = match[3] ? match[3].gsub(DATA_CLEANER, '').freeze : nil
36
+ end
37
+
38
+ def data_kv_match
39
+ @data_kv_match ||= data.match(DATA_KV_FORMAT)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ require 'cgi'
2
+
3
+ module RubyAMI
4
+ class AsyncAGIEnvironmentParser
5
+ def initialize(environment_string)
6
+ @environment_string = environment_string.dup
7
+ end
8
+
9
+ def to_hash
10
+ to_array.inject({}) do |accumulator, element|
11
+ accumulator[element[0].to_sym] = element[1] || ''
12
+ accumulator
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ @environment_string.dup
18
+ end
19
+
20
+ private
21
+
22
+ def to_array
23
+ CGI.unescape(@environment_string).split("\n").map { |p| p.split ': ' }
24
+ end
25
+ end
26
+ end
@@ -41,8 +41,9 @@ module RubyAMI
41
41
  end
42
42
 
43
43
  def start
44
- @events_stream = start_stream lambda { |event| @event_processor << event }
45
- @actions_stream = start_stream lambda { |message| @message_processor << message }
44
+ @events_stream = new_stream lambda { |event| @event_processor << event }
45
+ @actions_stream = new_stream lambda { |message| @message_processor << message }
46
+ streams.each(&:run!)
46
47
  @state = :started
47
48
  streams.each(&:join)
48
49
  end
@@ -178,7 +179,7 @@ module RubyAMI
178
179
  &block
179
180
  end
180
181
 
181
- def start_stream(callback)
182
+ def new_stream(callback)
182
183
  Stream.new @options[:host], @options[:port], callback
183
184
  end
184
185
 
@@ -15,12 +15,9 @@ module RubyAMI
15
15
 
16
16
  def initialize(host, port, event_callback)
17
17
  super()
18
- @event_callback = event_callback
18
+ @host, @port, @event_callback = host, port, event_callback
19
19
  logger.debug "Starting up..."
20
20
  @lexer = Lexer.new self
21
- @socket = TCPSocket.from_ruby_socket ::TCPSocket.new(host, port)
22
- post_init
23
- run!
24
21
  end
25
22
 
26
23
  [:started, :stopped, :ready].each do |state|
@@ -28,7 +25,12 @@ module RubyAMI
28
25
  end
29
26
 
30
27
  def run
28
+ @socket = TCPSocket.from_ruby_socket ::TCPSocket.new(@host, @port)
29
+ post_init
31
30
  loop { receive_data @socket.readpartial(4096) }
31
+ rescue Errno::ECONNREFUSED, SocketError => e
32
+ logger.error "Connection failed due to #{e.class}. Check your config and the server."
33
+ current_actor.terminate!
32
34
  rescue EOFError
33
35
  logger.info "Client socket closed!"
34
36
  current_actor.terminate!
@@ -1,3 +1,3 @@
1
1
  module RubyAMI
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/ruby_ami.rb CHANGED
@@ -16,6 +16,8 @@ end
16
16
 
17
17
  %w{
18
18
  action
19
+ agi_result_parser
20
+ async_agi_environment_parser
19
21
  client
20
22
  error
21
23
  event
data/ruby_ami.gemspec CHANGED
@@ -25,10 +25,10 @@ Gem::Specification.new do |s|
25
25
  s.add_runtime_dependency %q<countdownlatch>, ["~> 1.0"]
26
26
 
27
27
  s.add_development_dependency %q<bundler>, ["~> 1.0"]
28
- s.add_development_dependency %q<rspec>, [">= 2.5.0"]
28
+ s.add_development_dependency %q<rspec>, ["~> 2.5"]
29
29
  s.add_development_dependency %q<cucumber>, [">= 0"]
30
- s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
31
- s.add_development_dependency %q<yard>, ["~> 0.6.0"]
30
+ s.add_development_dependency %q<ci_reporter>, ["~> 1.6"]
31
+ s.add_development_dependency %q<yard>, ["~> 0.6"]
32
32
  s.add_development_dependency %q<rake>, [">= 0"]
33
33
  s.add_development_dependency %q<mocha>, [">= 0"]
34
34
  s.add_development_dependency %q<simplecov>, [">= 0"]
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyAMI
4
+ describe AGIResultParser do
5
+ subject { described_class.new result_string }
6
+
7
+ context 'with something that does not match the valid format' do
8
+ let(:result_string) { 'foobar' }
9
+
10
+ it 'should raise ArgumentError on creation' do
11
+ expect { subject }.to raise_error(ArgumentError, /format/)
12
+ end
13
+ end
14
+
15
+ context 'with a simple result with no data' do
16
+ let(:result_string) { "200%20result=123%0A" }
17
+
18
+ its(:code) { should == 200 }
19
+ its(:result) { should == 123 }
20
+ its(:data) { should == '' }
21
+ its(:data_hash) { should == nil }
22
+ end
23
+
24
+ context 'with a simple unescaped result with no data' do
25
+ let(:result_string) { "200 result=123" }
26
+
27
+ its(:code) { should == 200 }
28
+ its(:result) { should == 123 }
29
+ its(:data) { should == '' }
30
+ its(:data_hash) { should == nil }
31
+ end
32
+
33
+ context 'with a result and data in parens' do
34
+ let(:result_string) { "200%20result=-123%20(timeout)%0A" }
35
+
36
+ its(:code) { should == 200 }
37
+ its(:result) { should == -123 }
38
+ its(:data) { should == 'timeout' }
39
+ its(:data_hash) { should == nil }
40
+ end
41
+
42
+ context 'with a result and key-value data' do
43
+ let(:result_string) { "200%20result=123%20foo=bar%0A" }
44
+
45
+ its(:code) { should == 200 }
46
+ its(:result) { should == 123 }
47
+ its(:data) { should == 'foo=bar' }
48
+ its(:data_hash) { should == {'foo' => 'bar'} }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyAMI
4
+ describe AsyncAGIEnvironmentParser do
5
+ let :environment_string do
6
+ "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
7
+ end
8
+
9
+ subject { described_class.new environment_string }
10
+
11
+ its(:to_s) { should == environment_string }
12
+ its(:to_s) { should_not be environment_string }
13
+
14
+ describe 'retrieving a hash representation' do
15
+ its(:to_hash) do
16
+ should == {
17
+ :agi_request => 'async',
18
+ :agi_channel => 'SIP/1234-00000000',
19
+ :agi_language => 'en',
20
+ :agi_type => 'SIP',
21
+ :agi_uniqueid => '1320835995.0',
22
+ :agi_version => '1.8.4.1',
23
+ :agi_callerid => '5678',
24
+ :agi_calleridname => 'Jane Smith',
25
+ :agi_callingpres => '0',
26
+ :agi_callingani2 => '0',
27
+ :agi_callington => '0',
28
+ :agi_callingtns => '0',
29
+ :agi_dnid => '1000',
30
+ :agi_rdnis => 'unknown',
31
+ :agi_context => 'default',
32
+ :agi_extension => '1000',
33
+ :agi_priority => '1',
34
+ :agi_enhanced => '0.0',
35
+ :agi_accountcode => '',
36
+ :agi_threadid => '4366221312'
37
+ }
38
+ end
39
+
40
+ it "should not return the same hash object every time" do
41
+ subject.to_hash.should_not be subject.to_hash
42
+ end
43
+ end
44
+ end
45
+ end
@@ -329,8 +329,8 @@ module RubyAMI
329
329
  end
330
330
 
331
331
  describe '#stop' do
332
- let(:mock_actions_stream) { mock 'Actions Stream' }
333
- let(:mock_events_stream) { mock 'Events Stream' }
332
+ let(:mock_actions_stream) { mock 'Actions Stream', :alive? => true }
333
+ let(:mock_events_stream) { mock 'Events Stream', :alive? => true }
334
334
 
335
335
  let(:streams) { [mock_actions_stream, mock_events_stream] }
336
336
 
@@ -26,6 +26,7 @@ module RubyAMI
26
26
  mock_target.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
27
27
  s = ServerMock.new '127.0.0.1', server_port, mock_target
28
28
  @stream = Stream.new '127.0.0.1', server_port, lambda { |m| client.message_received m }
29
+ @stream.run!
29
30
  fake_client.call if fake_client.respond_to? :call
30
31
  s.join
31
32
  @stream.join
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,23 @@
1
- require 'simplecov'
2
- require 'simplecov-rcov'
3
- class SimpleCov::Formatter::MergedFormatter
4
- def format(result)
5
- SimpleCov::Formatter::HTMLFormatter.new.format(result)
6
- SimpleCov::Formatter::RcovFormatter.new.format(result)
1
+ begin
2
+ require 'simplecov'
3
+ require 'simplecov-rcov'
4
+ class SimpleCov::Formatter::MergedFormatter
5
+ def format(result)
6
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
7
+ SimpleCov::Formatter::RcovFormatter.new.format(result)
8
+ end
7
9
  end
8
- end
9
- SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
10
- SimpleCov.start do
11
- add_filter "/vendor/"
10
+ SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
11
+ SimpleCov.start do
12
+ add_filter "/vendor/"
13
+ end
14
+ rescue Exception => e
15
+ puts "Couldn't load simplecov"
16
+ puts e.message
17
+ puts e.backtrace.join("\n")
12
18
  end
13
19
 
14
20
  require 'ruby_ami'
15
- require 'mocha'
16
21
  require 'countdownlatch'
17
22
 
18
23
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_ami
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-04 00:00:00.000000000 Z
12
+ date: 2012-07-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: uuidtools
@@ -112,17 +112,17 @@ dependencies:
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  none: false
114
114
  requirements:
115
- - - ! '>='
115
+ - - ~>
116
116
  - !ruby/object:Gem::Version
117
- version: 2.5.0
117
+ version: '2.5'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  none: false
122
122
  requirements:
123
- - - ! '>='
123
+ - - ~>
124
124
  - !ruby/object:Gem::Version
125
- version: 2.5.0
125
+ version: '2.5'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: cucumber
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -144,17 +144,17 @@ dependencies:
144
144
  requirement: !ruby/object:Gem::Requirement
145
145
  none: false
146
146
  requirements:
147
- - - ! '>='
147
+ - - ~>
148
148
  - !ruby/object:Gem::Version
149
- version: 1.6.3
149
+ version: '1.6'
150
150
  type: :development
151
151
  prerelease: false
152
152
  version_requirements: !ruby/object:Gem::Requirement
153
153
  none: false
154
154
  requirements:
155
- - - ! '>='
155
+ - - ~>
156
156
  - !ruby/object:Gem::Version
157
- version: 1.6.3
157
+ version: '1.6'
158
158
  - !ruby/object:Gem::Dependency
159
159
  name: yard
160
160
  requirement: !ruby/object:Gem::Requirement
@@ -162,7 +162,7 @@ dependencies:
162
162
  requirements:
163
163
  - - ~>
164
164
  - !ruby/object:Gem::Version
165
- version: 0.6.0
165
+ version: '0.6'
166
166
  type: :development
167
167
  prerelease: false
168
168
  version_requirements: !ruby/object:Gem::Requirement
@@ -170,7 +170,7 @@ dependencies:
170
170
  requirements:
171
171
  - - ~>
172
172
  - !ruby/object:Gem::Version
173
- version: 0.6.0
173
+ version: '0.6'
174
174
  - !ruby/object:Gem::Dependency
175
175
  name: rake
176
176
  requirement: !ruby/object:Gem::Requirement
@@ -293,6 +293,8 @@ files:
293
293
  - features/support/lexer_helper.rb
294
294
  - lib/ruby_ami.rb
295
295
  - lib/ruby_ami/action.rb
296
+ - lib/ruby_ami/agi_result_parser.rb
297
+ - lib/ruby_ami/async_agi_environment_parser.rb
296
298
  - lib/ruby_ami/client.rb
297
299
  - lib/ruby_ami/error.rb
298
300
  - lib/ruby_ami/event.rb
@@ -304,6 +306,8 @@ files:
304
306
  - lib/ruby_ami/version.rb
305
307
  - ruby_ami.gemspec
306
308
  - spec/ruby_ami/action_spec.rb
309
+ - spec/ruby_ami/agi_result_parser_spec.rb
310
+ - spec/ruby_ami/async_agi_environment_parser_spec.rb
307
311
  - spec/ruby_ami/client_spec.rb
308
312
  - spec/ruby_ami/error_spec.rb
309
313
  - spec/ruby_ami/event_spec.rb
@@ -326,7 +330,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
326
330
  version: '0'
327
331
  segments:
328
332
  - 0
329
- hash: 726303340289195819
333
+ hash: 1172578337856307051
330
334
  required_rubygems_version: !ruby/object:Gem::Requirement
331
335
  none: false
332
336
  requirements:
@@ -335,7 +339,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
335
339
  version: '0'
336
340
  segments:
337
341
  - 0
338
- hash: 726303340289195819
342
+ hash: 1172578337856307051
339
343
  requirements: []
340
344
  rubyforge_project: ruby_ami
341
345
  rubygems_version: 1.8.24
@@ -350,6 +354,8 @@ test_files:
350
354
  - features/support/introspective_lexer.rb
351
355
  - features/support/lexer_helper.rb
352
356
  - spec/ruby_ami/action_spec.rb
357
+ - spec/ruby_ami/agi_result_parser_spec.rb
358
+ - spec/ruby_ami/async_agi_environment_parser_spec.rb
353
359
  - spec/ruby_ami/client_spec.rb
354
360
  - spec/ruby_ami/error_spec.rb
355
361
  - spec/ruby_ami/event_spec.rb
@@ -357,3 +363,4 @@ test_files:
357
363
  - spec/ruby_ami/stream_spec.rb
358
364
  - spec/spec_helper.rb
359
365
  - spec/support/mock_server.rb
366
+ has_rdoc: