routengn 0.2.9
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/README.rdoc +61 -0
- data/bin/ngncmd +62 -0
- data/docs/RouteNGN-Gems-Install.doc +0 -0
- data/examples/cli.rb +201 -0
- data/examples/provision.rb +32 -0
- data/lib/routengn/console.rb +18 -0
- data/lib/routengn/http/multipart.rb +64 -0
- data/lib/routengn/mapper.rb +159 -0
- data/lib/routengn/models/carrier.rb +8 -0
- data/lib/routengn/models/dialcode.rb +8 -0
- data/lib/routengn/models/endpoint.rb +8 -0
- data/lib/routengn/models/group.rb +31 -0
- data/lib/routengn/models/instance.rb +21 -0
- data/lib/routengn/models/locale.rb +11 -0
- data/lib/routengn/models/rate.rb +11 -0
- data/lib/routengn/models/record.rb +17 -0
- data/lib/routengn/models/region.rb +9 -0
- data/lib/routengn/models/route.rb +8 -0
- data/lib/routengn/models/type.rb +7 -0
- data/lib/routengn/uploader.rb +17 -0
- data/lib/routengn.rb +151 -0
- data/routengn.gemspec +76 -0
- data/script/console +6 -0
- metadata +119 -0
data/README.rdoc
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= Introduction
|
2
|
+
|
3
|
+
This is a Ruby API Wrapper for the RouteNGN SIP routing platform. For more information about
|
4
|
+
RouteNGN, see http://www.routengn.com.
|
5
|
+
|
6
|
+
* Note: This is not the RouteNGN API itself. This a wrapper designed to make it easier to integrate with the RouteNGN API.
|
7
|
+
|
8
|
+
= Installation
|
9
|
+
|
10
|
+
Install the "routengn" gem by typing
|
11
|
+
|
12
|
+
$ sudo gem sources -a http://gems.github.com
|
13
|
+
$ sudo gem install klarrimore-routengn
|
14
|
+
|
15
|
+
The first line tells RubyGems to add the GitHub gem repository. You only need
|
16
|
+
to run this command once.
|
17
|
+
|
18
|
+
= Examples
|
19
|
+
|
20
|
+
Using irb:
|
21
|
+
|
22
|
+
klarrimore@calculatorwristwatch:~$ irb
|
23
|
+
irb(main):001:0> require 'rubygems'
|
24
|
+
=> true
|
25
|
+
irb(main):002:0> require 'routengn'
|
26
|
+
=> true
|
27
|
+
irb(main):003:0> RouteNGN.connect! "http://routengn.net", "qcuL3YAw9ZXdmSdqprBw", "LDI2rgxIhJlNu5FBXh0UvtxJRtpr1OgnCIQ8Mpk"
|
28
|
+
=> #<RouteNGN::Connection:0x7ffaadc3f578 @access_token=#<OAuth::AccessToken:0x....
|
29
|
+
irb(main):004:0> Carrier.all
|
30
|
+
=> [#<Carrier:0x7f1c6e36ce28 @id=152, @saved=true, @name="test">]
|
31
|
+
irb(main):005:0> Carrier.first.groups
|
32
|
+
=> [#<Group:0x7f1c6e335c70 @active_end_day=nil, @id=103, @add_type=nil, @carrier_id=152, @strip_digits=nil,
|
33
|
+
@active_begin_day=nil, @add_field=nil, @saved=true, @active_end_time=nil, @margin=0, @trunkgroup=nil,
|
34
|
+
@add_digits=nil, @active_begin_time=nil, @direction="in", @name="test2", @remove_rn=nil, @prefix=nil,
|
35
|
+
@rn_prefix=nil>, #<Group:0x7f1c6e335c48 @active_end_day=nil, @id=102, @add_type=nil, @carrier_id=152,
|
36
|
+
@strip_digits=nil, @active_begin_day=nil, @add_field=nil, @saved=true, @active_end_time=nil, @margin=0, @trunkgroup=nil,
|
37
|
+
@add_digits=nil, @active_begin_time=nil, @direction="out", @name="test", @remove_rn=nil, @prefix=nil, @rn_prefix=nil>]
|
38
|
+
irb(main):006:0> Carrier.first.groups.first.endpoints
|
39
|
+
=> [#<Endpoint:0x7f1c6e2f21c8 @id=39, @saved=true, @ip="65.200.189.134">]
|
40
|
+
|
41
|
+
= Credits
|
42
|
+
|
43
|
+
klarrimore@gentel.net
|
44
|
+
qhoxie@gentel.net
|
45
|
+
|
46
|
+
= License
|
47
|
+
|
48
|
+
Copyright 2008-2009 GT3 HOLDINGS, LLC
|
49
|
+
|
50
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
51
|
+
you may not use this file except in compliance with the License.
|
52
|
+
You may obtain a copy of the License at
|
53
|
+
|
54
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
55
|
+
|
56
|
+
Unless required by applicable law or agreed to in writing, software
|
57
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
58
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
59
|
+
See the License for the specific language governing permissions and
|
60
|
+
limitations under the License.
|
61
|
+
|
data/bin/ngncmd
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/routengn'
|
6
|
+
|
7
|
+
class NGNCommand < Thor
|
8
|
+
map "--configure" => :configure,
|
9
|
+
"--upload-regions" => :region_upload
|
10
|
+
|
11
|
+
desc "configure", "Set up your RouteNGN API config (.ngnconf)"
|
12
|
+
def configure
|
13
|
+
user = ENV['USER'] ? ENV['USER'] : `who am i`
|
14
|
+
say "Configuring ngncmd for #{user}...", :yellow
|
15
|
+
site = ask "RouteNGN Site (ex. http://routengn.net):", :magenta
|
16
|
+
key = ask "API Key:", :magenta
|
17
|
+
secret = ask "API Secret:", :magenta
|
18
|
+
say "Trying to connect... ", :yellow
|
19
|
+
begin
|
20
|
+
if RouteNGN.connect! site, key, secret
|
21
|
+
say "Success", :green
|
22
|
+
say "Writing config file... ", :yellow
|
23
|
+
RouteNGN.write_config
|
24
|
+
return say("Success", :green)
|
25
|
+
end
|
26
|
+
rescue Exception
|
27
|
+
# noop
|
28
|
+
end
|
29
|
+
say "Failed", :red
|
30
|
+
say "\nPlease check your site and credentials and try again.", :red
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "upload regions", "Upload a regions file"
|
35
|
+
def region_upload
|
36
|
+
file = ask "Path to file:", :magenta
|
37
|
+
unless File.readable? file
|
38
|
+
say "Error: file is not readable"
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
connect
|
42
|
+
say "Uploading file... "
|
43
|
+
Region.upload(file)
|
44
|
+
say "Success"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
no_tasks do
|
49
|
+
def connect
|
50
|
+
say "Establishing connection... "
|
51
|
+
RouteNGN.connect!
|
52
|
+
say "Success"
|
53
|
+
rescue Exception
|
54
|
+
say "Failed"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
NGNCommand.start
|
62
|
+
|
Binary file
|
data/examples/cli.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'json'
|
7
|
+
gem 'oauth'
|
8
|
+
require 'oauth/consumer'
|
9
|
+
require 'http/multipart'
|
10
|
+
|
11
|
+
@consumer=OAuth::Consumer.new "qcuL3sYAw9ZXdmSdqprBw", #API key
|
12
|
+
"LDI2rgxnIhJlNu5FBXh0UvtxJRtpr1OgnCIQ8Mpk", #API secret
|
13
|
+
{ :signature_method => 'HMAC-SHA1',
|
14
|
+
:site=>"http://127.0.0.1:3000" #EX: http://routengn.net
|
15
|
+
}
|
16
|
+
|
17
|
+
@request_token = @consumer.get_request_token
|
18
|
+
|
19
|
+
@consumer.request(:get, @request_token.authorize_url)
|
20
|
+
|
21
|
+
@access_token = @request_token.get_access_token
|
22
|
+
@access_token.consumer.http.read_timeout = 5000
|
23
|
+
|
24
|
+
def main_menu
|
25
|
+
puts "\nMain Menu:\n1. manage carriers\n2. manage regions\n3. upload regions or rates\n4. manage route tables\n"
|
26
|
+
str = gets.chomp
|
27
|
+
case str
|
28
|
+
when '1'
|
29
|
+
carrier_menu
|
30
|
+
when '2'
|
31
|
+
region_menu
|
32
|
+
when '3'
|
33
|
+
upload_menu
|
34
|
+
when '4'
|
35
|
+
route_table_menu
|
36
|
+
else
|
37
|
+
puts 'wtf'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def carrier_menu
|
42
|
+
puts "\nCarrier Menu:\n1. list carriers\n2. list groups\n3. list end points\n4. add carriers\n5. add groups\n6. add end points\n7. delete carriers\n8. delete groups\n9. delete end points\n"
|
43
|
+
str = gets.chomp
|
44
|
+
case str
|
45
|
+
when '1' #list carriers
|
46
|
+
puts "\n" + JSON.parse(@access_token.get('/carrier/carrier').body).inspect
|
47
|
+
when '2' #list groups
|
48
|
+
print "carrier id: "
|
49
|
+
carrier_id = gets.chomp
|
50
|
+
puts "\n" + JSON.parse(@access_token.get("/carrier/group/#{carrier_id}").body).inspect
|
51
|
+
when '3' #list end points
|
52
|
+
print "group id: "
|
53
|
+
group_id = gets.chomp
|
54
|
+
puts "\n" + JSON.parse(@access_token.get("/carrier/endpoint/#{group_id}").body).inspect
|
55
|
+
when '4' #add carriers
|
56
|
+
print "carrier name: "
|
57
|
+
carrier_name = gets.chomp
|
58
|
+
puts "\n" + JSON.parse(@access_token.post("/carrier/carrier/#{carrier_name}").body).inspect
|
59
|
+
when '5' #add groups
|
60
|
+
print "carrier id: "
|
61
|
+
carrier_id = gets.chomp
|
62
|
+
print "group name: "
|
63
|
+
group_name = gets.chomp
|
64
|
+
puts "\n" + JSON.parse(@access_token.post("/carrier/group/#{group_name}/#{carrier_id}").body).inspect
|
65
|
+
when '6' #add end points
|
66
|
+
print "group id: "
|
67
|
+
group_id = gets.chomp
|
68
|
+
print "end point name: "
|
69
|
+
endpoint_name = gets.chomp
|
70
|
+
puts "\n" + JSON.parse(@access_token.post("/carrier/endpoint/#{endpoint_name}/#{group_id}").body).inspect
|
71
|
+
when '7' #delete carriers
|
72
|
+
print "carrier id: "
|
73
|
+
carrier_id = gets.chomp
|
74
|
+
puts "\n" + JSON.parse(@access_token.delete("/carrier/carrier/#{carrier_id}").body).inspect
|
75
|
+
when '8' #delete groups
|
76
|
+
print "group id: "
|
77
|
+
group_id = gets.chomp
|
78
|
+
puts "\n" + JSON.parse(@access_token.delete("/carrier/group/#{group_id}").body).inspect
|
79
|
+
when '9' #delete end points
|
80
|
+
print "end point id: "
|
81
|
+
endpoint_id = gets.chomp
|
82
|
+
puts "\n" + JSON.parse(@access_token.delete("/carrier/endpoint/#{endpoint_id}").body).inspect
|
83
|
+
when 'back'
|
84
|
+
return
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def region_menu
|
89
|
+
puts "\nRegion Menu:\n1. list regions\n2. list locales\n3. list dial codes\n4. add regions\n5. add locales\n6. add dial codes\n7. delete regions\n8. delete locales\n9. delete dial codes\n"
|
90
|
+
str = gets.chomp
|
91
|
+
case str
|
92
|
+
when '1' #list regions
|
93
|
+
puts "\n" + JSON.parse(@access_token.get('/region/region').body).inspect
|
94
|
+
when '2' #list locales
|
95
|
+
print "region id: "
|
96
|
+
region_id = gets.chomp
|
97
|
+
puts "\n" + JSON.parse(@access_token.get("/region/locale/#{region_id}").body).inspect
|
98
|
+
when '3' #list dial codes
|
99
|
+
print "locale id: "
|
100
|
+
locale_id = gets.chomp
|
101
|
+
puts "\n" + JSON.parse(@access_token.get("/region/dialcode/#{locale_id}").body).inspect
|
102
|
+
when '4' #add regions
|
103
|
+
print "region name: "
|
104
|
+
region_name = gets.chomp
|
105
|
+
puts "\n" + JSON.parse(@access_token.post("/region/region/#{region_name}").body).inspect
|
106
|
+
when '5' #add locales
|
107
|
+
print "region id: "
|
108
|
+
region_id = gets.chomp
|
109
|
+
print "locale name: "
|
110
|
+
locale_name = gets.chomp
|
111
|
+
puts "\n" + JSON.parse(@access_token.post("/region/locale/#{locale_name}/#{region_id}").body).inspect
|
112
|
+
when '6' #add dial codes
|
113
|
+
print "locale id: "
|
114
|
+
locale_id = gets.chomp
|
115
|
+
print "dialcode: "
|
116
|
+
dialcode = gets.chomp
|
117
|
+
puts "\n" + JSON.parse(@access_token.post("/region/dialcode/#{dialcode}/#{locale_id}").body).inspect
|
118
|
+
when '7' #delete regions
|
119
|
+
print "region id: "
|
120
|
+
region_id = gets.chomp
|
121
|
+
puts "\n" + JSON.parse(@access_token.delete("/region/region/#{region_id}").body).inspect
|
122
|
+
when '8' #delete locales
|
123
|
+
print "locale id: "
|
124
|
+
locale_id = gets.chomp
|
125
|
+
puts "\n" + JSON.parse(@access_token.delete("/region/locale/#{locale_id}").body).inspect
|
126
|
+
when '9' #delete dial codes
|
127
|
+
print "dialcode id: "
|
128
|
+
dialcode_id = gets.chomp
|
129
|
+
puts "\n" + JSON.parse(@access_token.delete("/region/dialcode/#{dialcode_id}").body).inspect
|
130
|
+
when 'back'
|
131
|
+
return
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def upload_menu
|
136
|
+
puts "\nUpload Menu:\n1. upload regions\n2. upload rates\n"
|
137
|
+
str = gets.chomp
|
138
|
+
case str
|
139
|
+
when '1'
|
140
|
+
print "path: "
|
141
|
+
path = gets.chomp
|
142
|
+
data, headers = Multipart::Post.prepare_query("title" => 'title', "uploaded_data" => File.new(path))
|
143
|
+
puts @access_token.put('/upload/regions', data, headers)
|
144
|
+
when '2'
|
145
|
+
print "path: "
|
146
|
+
path = gets.chomp
|
147
|
+
print "rate type: "
|
148
|
+
rate_type = gets.chomp
|
149
|
+
print "groups: "
|
150
|
+
groups = gets.chomp
|
151
|
+
data, headers = Multipart::Post.prepare_query("title" => 'title', "uploaded_data" => File.new(path))
|
152
|
+
puts @access_token.put("/upload/rates/#{rate_type}/#{groups}", data, headers)
|
153
|
+
when 'back'
|
154
|
+
return
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def route_table_menu
|
159
|
+
puts "\nRoute Table Menu:\n1. list instances\n2. list routes\n3. create instance\n4. generate lcr\n5. search\n"
|
160
|
+
str = gets.chomp
|
161
|
+
case str
|
162
|
+
when '1'
|
163
|
+
puts @access_token.get('/route/instances').body
|
164
|
+
when '2'
|
165
|
+
|
166
|
+
puts @access_token.get('/route/routes/13').body
|
167
|
+
when '3'
|
168
|
+
print "table name: "
|
169
|
+
table_name = gets.chomp
|
170
|
+
print "table type: "
|
171
|
+
table_type = gets.chomp
|
172
|
+
puts @access_token.post("/route/table/#{table_name}/#{table_type}").body
|
173
|
+
when '4'
|
174
|
+
print "table type: "
|
175
|
+
table_type = gets.chomp
|
176
|
+
print "instance id: "
|
177
|
+
instance_id = gets.chomp
|
178
|
+
print "groups: "
|
179
|
+
groups = gets.chomp
|
180
|
+
puts @access_token.put("/route/update/lcr/#{table_type}/#{instance_id}/#{groups}").body
|
181
|
+
when '5'
|
182
|
+
print "search type: "
|
183
|
+
search_type = gets.chomp
|
184
|
+
print "instance id: "
|
185
|
+
instance_id = gets.chomp
|
186
|
+
print "table type: "
|
187
|
+
table_type = gets.chomp
|
188
|
+
print "route type: "
|
189
|
+
route_type = gets.chomp
|
190
|
+
print "search: "
|
191
|
+
search = gets.chomp
|
192
|
+
puts @access_token.get("/route/search/#{search_type}/#{instance_id}/#{table_type}/#{route_type}/#{search}").body
|
193
|
+
when 'back'
|
194
|
+
return
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
while true
|
199
|
+
main_menu
|
200
|
+
end
|
201
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
require 'routengn'
|
5
|
+
|
6
|
+
RouteNGN.connect! "http://beta.routengn.net", "qcuL3sYAw9ZXdmSdqprBw", "LDI2rgxnIhJlNu5FBXh0UvtxJRtpr1OgnCIQ8Mpk"
|
7
|
+
|
8
|
+
Region.upload('my_regions.csv')
|
9
|
+
|
10
|
+
carrier = Carrier.new({'name' => 'api_test_carrier'})
|
11
|
+
carrier.save
|
12
|
+
puts carrier.inspect
|
13
|
+
|
14
|
+
group = Group.new({'name' => 'api_test_group', 'direction' => 'out', 'carrier_id' => carrier.id})
|
15
|
+
group.save
|
16
|
+
puts group.inspect
|
17
|
+
|
18
|
+
endpoint = Endpoint.new({'ip' => '3.3.3.3', 'epgroup_id' => group.id})
|
19
|
+
endpoint.save
|
20
|
+
puts endpoint.inspect
|
21
|
+
|
22
|
+
type_id = Type.first('name'=> 'lcr').id
|
23
|
+
|
24
|
+
instance = Instance.new('name' => 'api_test_table', 'type_id' => type_id)
|
25
|
+
instance.save
|
26
|
+
puts instance.inspect
|
27
|
+
|
28
|
+
Rate.upload('my_rates.csv', 'group' => group.id, 'type_id' => type_id)
|
29
|
+
|
30
|
+
Instance.first.generate(Group.all.collect{|x| x.id})
|
31
|
+
|
32
|
+
RouteNGN.apply!
|
@@ -0,0 +1,18 @@
|
|
1
|
+
HISTFILE = "~/.routengn.irb.hist"
|
2
|
+
MAXHISTSIZE = 1000
|
3
|
+
|
4
|
+
if defined? Readline::HISTORY
|
5
|
+
histfile = File.expand_path(HISTFILE)
|
6
|
+
if File.exists?(histfile)
|
7
|
+
lines = IO.readlines(histfile).collect { |line| line.chomp }
|
8
|
+
Readline::HISTORY.push(*lines)
|
9
|
+
end
|
10
|
+
|
11
|
+
Kernel.at_exit do
|
12
|
+
lines = Readline::HISTORY.to_a.reverse.uniq.reverse
|
13
|
+
lines = lines[-MAXHISTSIZE, MAXHISTSIZE] if lines.nitems > MAXHISTSIZE
|
14
|
+
File.open(histfile, File::WRONLY|File::CREAT|File::TRUNC) do |f|
|
15
|
+
lines.each { |line| f.puts line }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RouteNGN
|
2
|
+
module HTTP
|
3
|
+
module Multipart
|
4
|
+
# From: http://deftcode.com/code/flickr_upload/multipartpost.rb
|
5
|
+
## Helper class to prepare an HTTP POST request with a file upload
|
6
|
+
## Mostly taken from
|
7
|
+
#http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/113774
|
8
|
+
### WAS:
|
9
|
+
## Anything that's broken and wrong probably the fault of Bill Stilwell
|
10
|
+
##(bill@marginalia.org)
|
11
|
+
### NOW:
|
12
|
+
## Everything wrong is due to keith@oreilly.com
|
13
|
+
|
14
|
+
BOUNDARY = 'larrimores-rule0000'
|
15
|
+
HEADER = {"Content-type" => "multipart/form-data, boundary=" + BOUNDARY + " "}
|
16
|
+
|
17
|
+
class Param
|
18
|
+
attr_accessor :k, :v
|
19
|
+
|
20
|
+
def initialize( k, v )
|
21
|
+
@k = k
|
22
|
+
@v = v
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_multipart
|
26
|
+
#return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
|
27
|
+
# Don't escape mine...
|
28
|
+
return "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class FileParam
|
33
|
+
attr_accessor :k, :filename, :content
|
34
|
+
|
35
|
+
def initialize( k, filename, content )
|
36
|
+
@k = k
|
37
|
+
@filename = filename
|
38
|
+
@content = content
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_multipart
|
42
|
+
#return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{filename}\"\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Type: #{MIME::Types.type_for(@filename)}\r\n\r\n" + content + "\r\n "
|
43
|
+
# Don't escape mine
|
44
|
+
return "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Type: #{MIME::Types.type_for(@filename)}\r\n\r\n" + content + "\r\n"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.prepare_query(params)
|
49
|
+
fp = []
|
50
|
+
params.each {|k, v|
|
51
|
+
if v.respond_to?(:read)
|
52
|
+
fp.push(FileParam.new(k, v.path, v.read))
|
53
|
+
else
|
54
|
+
fp.push(Param.new(k, v))
|
55
|
+
end
|
56
|
+
}
|
57
|
+
query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
|
58
|
+
return query, HEADER
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module RouteNGN
|
2
|
+
module Mapper
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
#TODO perhaps we can add some of the goodies provided through relationships like '<<'
|
12
|
+
module InstanceMethods
|
13
|
+
attr_accessor :saved
|
14
|
+
|
15
|
+
def attributes
|
16
|
+
self.class.fields.inject({}) do |_, field|
|
17
|
+
_[field] = send field
|
18
|
+
_
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def attr_params
|
23
|
+
self.attributes.inject({}) do |params, (k, v)|
|
24
|
+
params["#{self.class.name.downcase}[#{k}]"] = v
|
25
|
+
params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def new?
|
30
|
+
!(@saved ||= false)
|
31
|
+
end
|
32
|
+
|
33
|
+
def save
|
34
|
+
response = if new?
|
35
|
+
RouteNGN.post self.class.base_url, attr_params
|
36
|
+
else
|
37
|
+
RouteNGN.put self.class.base_url, attr_params
|
38
|
+
end
|
39
|
+
|
40
|
+
return false if !response.success?
|
41
|
+
|
42
|
+
@saved = true
|
43
|
+
|
44
|
+
# populates fields with data that was sent back
|
45
|
+
self.class.fields.each do |field|
|
46
|
+
element = response['data'].first
|
47
|
+
self.send :"#{field}=", element[field.to_s] if element[field.to_s]
|
48
|
+
end
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def destroy
|
54
|
+
response = RouteNGN.delete self.class.base_url, :id => primary
|
55
|
+
response.success?
|
56
|
+
end
|
57
|
+
end # InstanceMethods
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
attr_reader :fields
|
61
|
+
attr_reader :primary_field
|
62
|
+
|
63
|
+
def new(opts = {})
|
64
|
+
instance = super() # don't want implicit args
|
65
|
+
@fields.each do |field|
|
66
|
+
field = field.to_s
|
67
|
+
next unless opts.has_key? field
|
68
|
+
instance.send :"#{field}=", opts[field]
|
69
|
+
end
|
70
|
+
raise PrimaryFieldException.new unless instance.respond_to? :primary
|
71
|
+
instance
|
72
|
+
end
|
73
|
+
|
74
|
+
def new_from_saved(opts = {})
|
75
|
+
instance = new opts
|
76
|
+
instance.saved = true
|
77
|
+
instance
|
78
|
+
end
|
79
|
+
|
80
|
+
def base_url
|
81
|
+
"/#{name.downcase.pluralize}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def field(name, opts = {})
|
85
|
+
@fields ||= []
|
86
|
+
return if @fields.include? name
|
87
|
+
@fields << name
|
88
|
+
define_method(name) { instance_variable_get :"@#{name}" }
|
89
|
+
define_method(:"#{name}=") { |val| instance_variable_set :"@#{name}", val }
|
90
|
+
if opts[:primary]
|
91
|
+
raise MultiplePrimaryFieldsException.new if @primary_field
|
92
|
+
alias_method :primary, name
|
93
|
+
@primary_field = name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def belongs_to(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
|
98
|
+
attr = opts[:column] ? opts[:column] : :"#{klass}_id"
|
99
|
+
field attr
|
100
|
+
define_method(klass) { klass.to_s.camelize.constantize.first :id => send(attr) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_one(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
|
104
|
+
attr = opts[:column] ? opts[:column] : :"#{name.downcase}_id"
|
105
|
+
define_method(klass) { klass.to_s.camelize.constantize.first attr => primary }
|
106
|
+
end
|
107
|
+
|
108
|
+
def has_many(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
|
109
|
+
attr = opts[:column] ? opts[:column] : :"#{name.downcase}_id"
|
110
|
+
define_method(klass.to_s.pluralize.to_sym) { klass.to_s.singularize.camelize.constantize.all attr => primary }
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete(id)
|
114
|
+
response = RouteNGN.delete base_url, :id => id
|
115
|
+
response.success?
|
116
|
+
end
|
117
|
+
|
118
|
+
def find(*args)
|
119
|
+
case args.first
|
120
|
+
when :all
|
121
|
+
all(*args[1..-1])
|
122
|
+
else
|
123
|
+
first(*args[1..-1])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def all(opts = {})
|
128
|
+
result = []
|
129
|
+
|
130
|
+
response = RouteNGN.get base_url, opts
|
131
|
+
|
132
|
+
data = response['data']
|
133
|
+
|
134
|
+
return [new_from_saved(data)] unless data.is_a? Array
|
135
|
+
|
136
|
+
data.each do |d|
|
137
|
+
result << new_from_saved(d)
|
138
|
+
end
|
139
|
+
|
140
|
+
result
|
141
|
+
end
|
142
|
+
|
143
|
+
def first(opts = {})
|
144
|
+
all(opts).first # optimize/simplify?
|
145
|
+
end
|
146
|
+
end # ClassMethods
|
147
|
+
|
148
|
+
class PrimaryFieldException < Exception
|
149
|
+
def initialize(msg = "Model does not specify a primary field.")
|
150
|
+
super
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class MultiplePrimaryFieldsException < Exception
|
155
|
+
def initialize(msg = "Model specifies too many primary fields.")
|
156
|
+
super
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end # RouteNGN
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Group
|
2
|
+
include RouteNGN::Mapper
|
3
|
+
|
4
|
+
#TODO: fixure out a fix for abnormal foreign key 'epgroup_id'
|
5
|
+
|
6
|
+
field :id, :primary => true
|
7
|
+
field :name
|
8
|
+
field :direction
|
9
|
+
field :margin
|
10
|
+
field :prefix
|
11
|
+
field :strip_digits
|
12
|
+
field :rn_prefix
|
13
|
+
field :add_digits
|
14
|
+
field :add_field
|
15
|
+
field :add_type
|
16
|
+
field :blocked_region_id
|
17
|
+
field :route_on_type_id
|
18
|
+
field :remove_rn
|
19
|
+
field :trunkgroup
|
20
|
+
field :active_begin_day
|
21
|
+
field :active_end_day
|
22
|
+
field :active_begin_time
|
23
|
+
field :active_end_time
|
24
|
+
|
25
|
+
belongs_to :instance
|
26
|
+
belongs_to :carrier
|
27
|
+
|
28
|
+
has_many :endpoints, :column => :epgroup_id
|
29
|
+
has_many :rates
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Instance
|
2
|
+
include RouteNGN::Mapper
|
3
|
+
|
4
|
+
field :id, :primary => true
|
5
|
+
field :name
|
6
|
+
|
7
|
+
belongs_to :type
|
8
|
+
|
9
|
+
# custom method
|
10
|
+
# TODO make this more generic... possibly add functionality to the mapper that will allow for custom actions
|
11
|
+
def generate(groups)
|
12
|
+
groups_params = groups.inject({}) do |params, (k, v)|
|
13
|
+
params["groups[#{k}]"] = 1
|
14
|
+
params
|
15
|
+
end
|
16
|
+
|
17
|
+
response = RouteNGN.put self.class.base_url, {:id => self.id}.merge!(groups_params)
|
18
|
+
response.success?
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Record
|
2
|
+
include RouteNGN::Mapper
|
3
|
+
|
4
|
+
field :dialcode, :primary => true
|
5
|
+
field :account_id
|
6
|
+
field :rn
|
7
|
+
field :locale_id
|
8
|
+
field :sent
|
9
|
+
field :received
|
10
|
+
field :detail
|
11
|
+
field :server
|
12
|
+
field :response_time
|
13
|
+
field :created_at_in_seconds
|
14
|
+
field :created_at
|
15
|
+
field :updated_at
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RouteNGN
|
2
|
+
module Uploader
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def upload(file, opts = {})
|
12
|
+
args = {:uploaded_data => File.new(file)}.merge! opts
|
13
|
+
data, headers = HTTP::Multipart.prepare_query(args)
|
14
|
+
RouteNGN.upload("/upload#{base_url}", data, headers)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/routengn.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'oauth'
|
3
|
+
require 'oauth/consumer'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
$: << File.expand_path(File.join File.dirname(__FILE__), 'routengn')
|
7
|
+
require 'mime/types'
|
8
|
+
require 'cgi'
|
9
|
+
require 'http/multipart'
|
10
|
+
require 'mapper'
|
11
|
+
require 'uploader'
|
12
|
+
require 'active_support'
|
13
|
+
|
14
|
+
module RouteNGN
|
15
|
+
Dir.glob(File.dirname(__FILE__) + "/routengn/models/*.rb").each do |model|
|
16
|
+
require File.expand_path(model)
|
17
|
+
end
|
18
|
+
|
19
|
+
CONF_FILE = File.join ENV['HOME'], ".ngnconf"
|
20
|
+
|
21
|
+
class Connection
|
22
|
+
attr_accessor :access_token
|
23
|
+
|
24
|
+
def initialize(*args)
|
25
|
+
@site, @key, @secret = args.empty? ? file_config : args
|
26
|
+
@consumer = OAuth::Consumer.new @key, @secret, {
|
27
|
+
:signature_method => 'HMAC-SHA1',
|
28
|
+
:site => @site
|
29
|
+
}
|
30
|
+
|
31
|
+
@request_token = @consumer.get_request_token
|
32
|
+
@consumer.request(:get, @request_token.authorize_url)
|
33
|
+
|
34
|
+
@access_token = @request_token.get_access_token
|
35
|
+
@access_token.consumer.http.read_timeout = 5000
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
{:site => @site, :key => @key, :secret => @secret}.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def file_config
|
44
|
+
data = File.open(CONF_FILE).read
|
45
|
+
data = JSON.parse data
|
46
|
+
[data['site'], data['key'], data['secret']]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def connect!(*args)
|
52
|
+
@connection = Connection.new *args
|
53
|
+
end
|
54
|
+
|
55
|
+
def connection
|
56
|
+
@connection
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_config
|
60
|
+
File.open(CONF_FILE, 'w') do |f|
|
61
|
+
f << connection.to_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
[:get, :delete].each do |http_method|
|
66
|
+
define_method(http_method) do |*args|
|
67
|
+
uri, params = args
|
68
|
+
params ||= {}
|
69
|
+
#TODO remove the trailing '&' from url
|
70
|
+
url = params.inject("#{uri}?") do |_, (k, v)|
|
71
|
+
if v.is_a? Hash # Handle nesting of depth 1
|
72
|
+
_ << v.collect { |nested_k, nested_v| "#{k}[#{nested_k}]=#{nested_v}&" }.join
|
73
|
+
else
|
74
|
+
_ << "#{k}=#{v}&"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
check_connection!
|
78
|
+
raw = @connection.access_token.send http_method, url
|
79
|
+
Response.new raw
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
[:post, :put].each do |http_method|
|
84
|
+
define_method(http_method) do |*args|
|
85
|
+
uri, params = args
|
86
|
+
params ||= {}
|
87
|
+
params = params.inject({}) do |_, (k, v)|
|
88
|
+
_[k.to_s] = v.to_s
|
89
|
+
_
|
90
|
+
end
|
91
|
+
check_connection!
|
92
|
+
raw = @connection.access_token.send http_method, uri, params
|
93
|
+
Response.new raw
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
define_method :upload do |*args|
|
98
|
+
uri, data, headers = args
|
99
|
+
raw = @connection.access_token.send :post, uri, data, headers
|
100
|
+
end
|
101
|
+
|
102
|
+
# TODO: find a better place to do this
|
103
|
+
def apply!
|
104
|
+
response = post '/apply'
|
105
|
+
response.success?
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def check_connection!
|
110
|
+
raise ConnectionException.new unless @connection
|
111
|
+
raise OAuthException.new unless @connection.access_token
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# TODO: validation errors are now stored in failure message's data... we should figure out what to do with it
|
116
|
+
class Response
|
117
|
+
SUCCESS_RANGE = (200..399)
|
118
|
+
|
119
|
+
attr_reader :data, :status
|
120
|
+
|
121
|
+
def initialize(raw)
|
122
|
+
@status = raw.code.to_i
|
123
|
+
# TODO: if it's not a success code we probably shouldn't bother parsing the JSON
|
124
|
+
@data = JSON.parse raw.body
|
125
|
+
end
|
126
|
+
|
127
|
+
def [](key)
|
128
|
+
@data[key]
|
129
|
+
end
|
130
|
+
|
131
|
+
def success?
|
132
|
+
SUCCESS_RANGE.include? @status
|
133
|
+
end
|
134
|
+
|
135
|
+
def failure?
|
136
|
+
!success?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ConnectionException < Exception
|
141
|
+
def initialize(msg = "No connection to RouteNGN established.")
|
142
|
+
super
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class OAuthException < Exception
|
147
|
+
def initialize(msg = "Invalid RouteNGN OAuth credentials.")
|
148
|
+
super
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/routengn.gemspec
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{routengn}
|
8
|
+
s.version = "0.2.9"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Keith Larrimore", "Quin Hoxie"]
|
12
|
+
s.date = %q{2009-09-30}
|
13
|
+
s.default_executable = %q{ngncmd}
|
14
|
+
s.description = %q{RouteNGN is an Advanced SIP Redirect Routing Platform.}
|
15
|
+
s.email = %q{keithlarrimore+github@gmail.com}
|
16
|
+
s.executables = ["ngncmd"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
"README.rdoc",
|
22
|
+
"bin/ngncmd",
|
23
|
+
"docs/RouteNGN-Gems-Install.doc",
|
24
|
+
"examples/cli.rb",
|
25
|
+
"examples/provision.rb",
|
26
|
+
"lib/routengn.rb",
|
27
|
+
"lib/routengn/console.rb",
|
28
|
+
"lib/routengn/http/multipart.rb",
|
29
|
+
"lib/routengn/mapper.rb",
|
30
|
+
"lib/routengn/models/carrier.rb",
|
31
|
+
"lib/routengn/models/dialcode.rb",
|
32
|
+
"lib/routengn/models/endpoint.rb",
|
33
|
+
"lib/routengn/models/group.rb",
|
34
|
+
"lib/routengn/models/instance.rb",
|
35
|
+
"lib/routengn/models/locale.rb",
|
36
|
+
"lib/routengn/models/rate.rb",
|
37
|
+
"lib/routengn/models/record.rb",
|
38
|
+
"lib/routengn/models/region.rb",
|
39
|
+
"lib/routengn/models/route.rb",
|
40
|
+
"lib/routengn/models/type.rb",
|
41
|
+
"lib/routengn/uploader.rb",
|
42
|
+
"routengn.gemspec",
|
43
|
+
"script/console"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/klarrimore/routengn}
|
46
|
+
s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.3.1}
|
49
|
+
s.summary = %q{RouteNGN is an Advanced SIP Redirect Routing Platform.}
|
50
|
+
s.test_files = [
|
51
|
+
"examples/provision.rb",
|
52
|
+
"examples/cli.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 2
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
61
|
+
s.add_runtime_dependency(%q<mime-types>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
63
|
+
s.add_runtime_dependency(%q<oauth>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
66
|
+
s.add_dependency(%q<mime-types>, [">= 0"])
|
67
|
+
s.add_dependency(%q<json>, [">= 0"])
|
68
|
+
s.add_dependency(%q<oauth>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
72
|
+
s.add_dependency(%q<mime-types>, [">= 0"])
|
73
|
+
s.add_dependency(%q<json>, [">= 0"])
|
74
|
+
s.add_dependency(%q<oauth>, [">= 0"])
|
75
|
+
end
|
76
|
+
end
|
data/script/console
ADDED
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: routengn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Keith Larrimore
|
8
|
+
- Quin Hoxie
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-09-30 00:00:00 -04:00
|
14
|
+
default_executable: ngncmd
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: activesupport
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: mime-types
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
type: :runtime
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: oauth
|
48
|
+
type: :runtime
|
49
|
+
version_requirement:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
description: RouteNGN is an Advanced SIP Redirect Routing Platform.
|
57
|
+
email: keithlarrimore+github@gmail.com
|
58
|
+
executables:
|
59
|
+
- ngncmd
|
60
|
+
extensions: []
|
61
|
+
|
62
|
+
extra_rdoc_files:
|
63
|
+
- README.rdoc
|
64
|
+
files:
|
65
|
+
- README.rdoc
|
66
|
+
- bin/ngncmd
|
67
|
+
- docs/RouteNGN-Gems-Install.doc
|
68
|
+
- examples/cli.rb
|
69
|
+
- examples/provision.rb
|
70
|
+
- lib/routengn.rb
|
71
|
+
- lib/routengn/console.rb
|
72
|
+
- lib/routengn/http/multipart.rb
|
73
|
+
- lib/routengn/mapper.rb
|
74
|
+
- lib/routengn/models/carrier.rb
|
75
|
+
- lib/routengn/models/dialcode.rb
|
76
|
+
- lib/routengn/models/endpoint.rb
|
77
|
+
- lib/routengn/models/group.rb
|
78
|
+
- lib/routengn/models/instance.rb
|
79
|
+
- lib/routengn/models/locale.rb
|
80
|
+
- lib/routengn/models/rate.rb
|
81
|
+
- lib/routengn/models/record.rb
|
82
|
+
- lib/routengn/models/region.rb
|
83
|
+
- lib/routengn/models/route.rb
|
84
|
+
- lib/routengn/models/type.rb
|
85
|
+
- lib/routengn/uploader.rb
|
86
|
+
- routengn.gemspec
|
87
|
+
- script/console
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://github.com/klarrimore/routengn
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options:
|
94
|
+
- --inline-source
|
95
|
+
- --charset=UTF-8
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
version:
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: "0"
|
109
|
+
version:
|
110
|
+
requirements: []
|
111
|
+
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.3.5
|
114
|
+
signing_key:
|
115
|
+
specification_version: 2
|
116
|
+
summary: RouteNGN is an Advanced SIP Redirect Routing Platform.
|
117
|
+
test_files:
|
118
|
+
- examples/provision.rb
|
119
|
+
- examples/cli.rb
|