cloudsponge 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY ADDED
@@ -0,0 +1,4 @@
1
+ === 0.9.2 / 2010-04-20
2
+
3
+ * Created Gem version of basic library functionality
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,15 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/cloudsponge.rb
6
+ lib/cloudsponge/contact.rb
7
+ lib/cloudsponge/contact_importer.rb
8
+ lib/cloudsponge/cs_exception.rb
9
+ lib/cloudsponge/event.rb
10
+ lib/cloudsponge/utility.rb
11
+ test/test_contact.rb
12
+ test/test_contact_importer.rb
13
+ test/test_cs_exception.rb
14
+ test/test_utility.rb
15
+ TODO
data/README ADDED
@@ -0,0 +1,70 @@
1
+ = cs_import
2
+
3
+ http://www.cloudsponge.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ Spread the word with an email invite. Import contacts from Yahoo, Hotmail, MSN, Gmail, AOL, Outlook and Mac Address Book.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ === Problem
12
+ Websites looking to grow their customer base usually turn to traditional, paid methods of advertising like search engine keywords, media buys, and generic email lists. Though these methods can be effective at times, they are no match for the testimonial power your very own customers can bring. By encouraging your customers to promote your site to their family and friends, you have an inexpensive advertising channel to acquire customers. If one customer refers their friends, and their friends refer their friends, and so on, and so on, you now have a viral site!
13
+
14
+ === Solution
15
+ CloudSponge.com is THE tool you need to go viral! Here's how it works:
16
+ * Ask your customers to invite all their friends and family to your site.
17
+ * Use CloudSponge.com to pull your customer's contact list with their permission.
18
+ * Send an email invite to all the contacts provided by CloudSponge.com.
19
+ * Sit back and watch your customer base grow virally!
20
+
21
+ == SYNOPSIS:
22
+
23
+ Usage:
24
+ contacts = nil
25
+ importer = Cloudsponge::ContactImporter.new(DOMAIN_KEY, DOMAIN_PASSWORD)
26
+ resp = importer.begin_import('YAHOO')
27
+ puts "Navigate to #{resp[:consent_url]} and complete the authentication process."
28
+ loop do
29
+ events = importer.get_events
30
+ break unless events.select{ |e| e.is_error? }.empty?
31
+ unless events.select{ |e| e.is_complete? }.empty?
32
+ contacts = importer.get_contacts
33
+ break
34
+ end
35
+ end
36
+
37
+ == REQUIREMENTS:
38
+
39
+ JSON decoding package, currently supported are ActiveSupport::JSON and the JSON gem.
40
+ Neither of these are required, but if you don't have either on your system, a runtime
41
+ error will be generated.
42
+
43
+ == INSTALL:
44
+
45
+ sudo gem install cloudsponge
46
+
47
+ == LICENSE:
48
+
49
+ (The MIT License)
50
+
51
+ Copyright (c) 2010 Cloud Copy, Inc.
52
+
53
+ Permission is hereby granted, free of charge, to any person obtaining
54
+ a copy of this software and associated documentation files (the
55
+ 'Software'), to deal in the Software without restriction, including
56
+ without limitation the rights to use, copy, modify, merge, publish,
57
+ distribute, sublicense, and/or sell copies of the Software, and to
58
+ permit persons to whom the Software is furnished to do so, subject to
59
+ the following conditions:
60
+
61
+ The above copyright notice and this permission notice shall be
62
+ included in all copies or substantial portions of the Software.
63
+
64
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
65
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
66
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
67
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
68
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
69
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
70
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/gempackagetask'
6
+ require 'lib/cloudsponge'
7
+
8
+ PKG_FILES = FileList["**/*"].exclude(/CVS|pkg|tmp|coverage|Makefile|\.nfs\./)
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'cloudsponge'
12
+ s.version = Cloudsponge::VERSION
13
+ s.author = "Graeme Rouse"
14
+ s.email = "graeme@cloudsponge.com"
15
+ s.homepage = "http://www.cloudsponge.com"
16
+ s.platform = Gem::Platform::RUBY
17
+ s.summary = "A library wrapper for Cloudsponge.com's API"
18
+ s.description = <<-EOF
19
+ Usage:
20
+ contacts = nil
21
+ importer = Cloudsponge::ContactImporter.new(DOMAIN_KEY, DOMAIN_PASSWORD)
22
+ importer.begin_import('YAHOO')
23
+ loop do
24
+ events = importer.get_events
25
+ break unless events.select{ |e| e.is_error? }.empty?
26
+ unless events.select{ |e| e.is_completed? }.empty?
27
+ contacts = importer.get_contacts
28
+ break
29
+ end
30
+ end
31
+ EOF
32
+
33
+ #s.files = FileList["{test,lib}/**/*"].exclude("rdoc").to_a
34
+ s.files = PKG_FILES
35
+ # s.files = PKG_FILES
36
+ s.require_path = "lib"
37
+ s.has_rdoc = true
38
+ s.extra_rdoc_files = ["README", "HISTORY", "TODO"]
39
+ end
40
+
41
+
42
+ Rake::GemPackageTask.new(spec) do |pkg|
43
+ pkg.need_tar = true
44
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * Write comprehensive tests
2
+ * Add interactive command line example program
3
+ * Add xml decoding in no JSON lib is available.
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/cloudsponge/contact_importer')
2
+ require File.expand_path(File.dirname(__FILE__) + '/cloudsponge/contact')
3
+ require File.expand_path(File.dirname(__FILE__) + '/cloudsponge/event')
4
+ require File.expand_path(File.dirname(__FILE__) + '/cloudsponge/cs_exception')
5
+ require File.expand_path(File.dirname(__FILE__) + '/cloudsponge/utility')
6
+
7
+ module Cloudsponge
8
+ VERSION = '0.9.3'
9
+ end
@@ -0,0 +1,40 @@
1
+ module Cloudsponge
2
+
3
+ class Contact
4
+ attr_accessor :first_name, :last_name, :emails, :phones
5
+
6
+ def self.from_array(list)
7
+ list.map { |contact_data| Contact.new(contact_data) }.compact
8
+ end
9
+
10
+ def initialize(contact_data)
11
+ # get the basic data
12
+ self.first_name = contact_data['first_name']
13
+ self.last_name = contact_data['last_name']
14
+ # get the phone numbers
15
+ self.phones = []
16
+ contact_data['phone'] && contact_data['phone'].each do |phone|
17
+ self.add_array_value(self.phones, phone['number'], phone['type'])
18
+ end
19
+ self.emails = []
20
+ contact_data['email'] && contact_data['email'].each do |email|
21
+ self.add_array_value(self.emails, email['address'], email['type'])
22
+ end
23
+ self
24
+ end
25
+
26
+ def name
27
+ "#{self.first_name} #{self.last_name}"
28
+ end
29
+ def email
30
+ self.emails && self.emails.first && self.emails.first[:value]
31
+ end
32
+ def phone
33
+ self.phones && self.phones.first && self.phones.first[:value]
34
+ end
35
+ def add_array_value(collection, value, type = nil)
36
+ collection << {:value => value, :type => type}
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,176 @@
1
+ # CloudSponge.com Ruby Library
2
+ # http://www.cloudsponge.com
3
+ # Copyright (c) 2010 Cloud Copy, Inc.
4
+ # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
5
+ #
6
+ # Written by Graeme Rouse
7
+ # graeme@cloudsponge.com
8
+
9
+ module Cloudsponge
10
+ # Constants
11
+ URL_BASE = "https://api.cloudsponge.com/"
12
+ BEGIN_PATH = "begin_import/"
13
+ CONSENT_PATH = "user_consent/"
14
+ IMPORT_PATH = "import/"
15
+ APPLET_PATH = "desktop_applet/"
16
+ EVENTS_PATH = "events/"
17
+ CONTACTS_PATH = "contacts/"
18
+
19
+ class ContactImporter
20
+ attr_accessor :key, :password, :import_id
21
+
22
+ def initialize(key = nil, password = nil)
23
+ @key, @password = [key, password]
24
+ end
25
+
26
+ # guesses the most appropriate invocation for begin_import_xxx()
27
+ # returns an array of possible objects
28
+ # [
29
+ # :import_id => <import_id>,
30
+ # :consent_url => nil | <consent_url>,
31
+ # :applet_tag => nil | <applet_tag>
32
+ # ]
33
+ def begin_import(source_name, username = nil, password = nil, user_id = '', redirect_url = nil)
34
+ id = nil
35
+ consent_url = nil
36
+ applet_tag = nil
37
+
38
+ # look at the given service and decide how which begin function to invoke.
39
+ unless username.nil? || username.empty?
40
+ resp = begin_import_username_password(source_name, username, password, user_id)
41
+ else
42
+ unless (source_name =~ /OUTLOOK/i || source_name =~ /ADDRESSBOOK/i).nil?
43
+ resp = begin_import_applet(source_name, user_id)
44
+ applet_tag = create_applet_tag(resp['id'], resp['url'])
45
+ else
46
+ resp = begin_import_consent(source_name, user_id, redirect_url)
47
+ consent_url = resp['url']
48
+ end
49
+ end
50
+
51
+ @import_id = resp['import_id']
52
+
53
+ {:import_id => resp['import_id'], :consent_url => consent_url, :applet_tag => applet_tag}
54
+ end
55
+
56
+ # returns an array of Cloudsponge::Events with any new event or nil if no new events are available
57
+ def get_events(import_id = nil)
58
+ import_id ||= @import_id
59
+
60
+ # call to CloudSponge.com for the latest event status and return it
61
+ # create the appropriate url to fetch the contacts
62
+ full_url = generate_poll_url(EVENTS_PATH, import_id)
63
+
64
+ # get the response from the server and decode it
65
+ resp = Utility.get_and_decode_response(full_url)
66
+ # interpret the result
67
+ Event.from_array(resp['events'])
68
+ end
69
+
70
+ # call to CloudSponge.com for the contacts,
71
+ # returns nil (not ready)
72
+ # or [an array of Contacts, the contacts_owner record]
73
+ def get_contacts(import_id = nil)
74
+ import_id ||= @import_id
75
+ contacts = nil
76
+ if resp = get_contacts_raw(import_id)
77
+ # decode the contacts for consumption
78
+ contacts = Contact.from_array(resp['contacts'])
79
+ contacts_owner = Contact.new(resp['contacts_owner']) rescue nil
80
+ end
81
+ [contacts, contacts_owner]
82
+ end
83
+
84
+ # call to CloudSponge.com for the contacts,
85
+ # returns nil (not ready)
86
+ # or a hashmap of contact data
87
+ def get_contacts_raw(import_id)
88
+ resp = nil
89
+
90
+ # get the response from the server and decode it
91
+ begin
92
+ resp = Utility.get_and_decode_response(generate_poll_url(CONTACTS_PATH, import_id))
93
+ rescue CsException => e
94
+ raise e unless e.code == 404
95
+ end
96
+
97
+ resp
98
+ end
99
+
100
+ private
101
+
102
+ # invokes the begin import action for the user consent process.
103
+ # returns the URL of the consent page that the user must use to sign in and grant consent
104
+ # throws an exception if an invalid service is invoked.
105
+ def begin_import_consent(source_name, user_id = nil, redirect_url = nil)
106
+ # we need to pass in all params to the call
107
+ params = {:service => source_name, :user_id => user_id, :redirect_url => redirect_url}.reject{ |k,v| v.nil? || v.empty? }
108
+
109
+ # get and decode the response into an associated array
110
+ # Throws an exception if there was a problem at the server
111
+ Utility.post_and_decode_response(full_url(CONSENT_PATH), authenticated_params(params))
112
+ end
113
+
114
+ # invokes the begin import action for the desktop applet import process.
115
+ # returns the URL of the applet that should be displayed to the user within the appropriate applet tag
116
+ # throws an exception if an invalid service is invoked.
117
+ def begin_import_applet(source_name, user_id = nil)
118
+ # we need to pass in all params to the call
119
+ params = {:service => source_name, :user_id => user_id}
120
+
121
+ # get and decode the response into an associated array
122
+ # Throws an exception if there was a problem at the server
123
+ Utility.post_and_decode_response(full_url(APPLET_PATH), authenticated_params(params))
124
+ end
125
+
126
+ # invokes the begin import action for the desktop applet import process.
127
+ # returns the URL of the applet that should be displayed to the user within the appropriate applet tag
128
+ # throws an exception if an invalid service is invoked.
129
+ def begin_import_username_password(source_name, username, password, user_id)
130
+ # we need to pass in all params to the call
131
+ params = {:service => source_name, :user_id => user_id, :username => username, :password => password}
132
+
133
+ # get and decode the response into an associated array
134
+ # Throws an exception if there was a problem at the server
135
+ Utility.post_and_decode_response(full_url(IMPORT_PATH), authenticated_params(params))
136
+ end
137
+
138
+ def full_url(path)
139
+ "#{URL_BASE}#{BEGIN_PATH}#{path}"
140
+ end
141
+
142
+ def authenticated_params(params = {})
143
+ # append domain_key, domain_password to params and serialze into a query string
144
+ params.merge({:domain_key => self.key, :domain_password => self.password})
145
+ end
146
+
147
+ def authenticated_query(params = {})
148
+ # append domain_key, domain_password to params and serialze into a query string
149
+ Utility.object_to_query(authenticated_params(params))
150
+ end
151
+
152
+ def create_applet_tag(id, url)
153
+ <<-EOS
154
+ <APPLET archive="#{url}" code="ContactsApplet" id="Contact_Importer" width="0" height="0">
155
+ <PARAM name="cookieValue" value="document.cookie"/>
156
+ <PARAM name="importId" value="#{id}"/>
157
+ Your browser does not support Java which is required for this utility to operate correctly.
158
+ </APPLET>
159
+ EOS
160
+ end
161
+
162
+ def generate_poll_url(path, import_id)
163
+ # get the query_string with authentication params and assemble the full url
164
+ "#{URL_BASE}#{path}#{import_id}?#{authenticated_query}"
165
+ end
166
+
167
+ end
168
+ end
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
@@ -0,0 +1,9 @@
1
+ module Cloudsponge
2
+ class CsException < StandardError
3
+ attr :code
4
+ def initialize(message, code)
5
+ super(message)
6
+ @code = code
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ module Cloudsponge
2
+ EVENT_TYPES = %w{ INITIALIZING GATHERING SUBMITTING COMPLETE ERROR}
3
+ EVENT_STATUSES = %w{ PENDING INPROGRESS COMPLETED ERROR }
4
+
5
+ class Event
6
+ attr_accessor :event_type, :status, :value, :description
7
+
8
+ def self.from_array(list)
9
+ list.map { |event_data| Event.new(event_data) }.compact
10
+ end
11
+
12
+ def initialize(event_data)
13
+ # is it an error?
14
+
15
+ # get the basic data
16
+ self.event_type = event_data['event_type']
17
+ self.status = event_data['status']
18
+ self.value = event_data['value']
19
+ self.description = event_data['description']
20
+ self
21
+ end
22
+
23
+ def is_error?
24
+ self.event_type == 'ERROR'
25
+ end
26
+
27
+ def is_complete?
28
+ self.event_type == 'COMPLETE' && self.status == 'COMPLETED'
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,82 @@
1
+ module Cloudsponge
2
+
3
+ require "net/http"
4
+ require "net/https"
5
+ require "uri"
6
+
7
+ begin
8
+ require 'json'
9
+ rescue
10
+ end
11
+
12
+ class Utility
13
+
14
+ def self.object_to_query(object)
15
+ return object unless object.is_a? Hash
16
+ object.map{ |k,v| "#{URI.encode(k.to_s)}=#{URI.encode(v.to_s)}" }.join('&')
17
+ end
18
+
19
+ def self.post_and_decode_response(url, params)
20
+ # post the response
21
+ response = post_url(url, params)
22
+ if response.code_type == Net::HTTPOK
23
+ # decode the response into an asscoiative array
24
+ resp = decode_response(response.body, 'json')
25
+ raise CsException.new(resp['error']['message'], response['code']) if resp['error']
26
+ else
27
+ raise CsException.new(response.body, response.code)
28
+ end
29
+ resp
30
+ end
31
+
32
+ def self.get_and_decode_response(full_url)
33
+ # get the response
34
+ response = get_url(full_url)
35
+
36
+ if response.code_type == Net::HTTPOK
37
+ # decode the response into an asscoiative array
38
+ resp = decode_response(response.body, 'json')
39
+ raise CsException.new(resp['error']['message'], response['code']) if resp['error']
40
+ else
41
+ raise CsException.new(response.body, response.code)
42
+ end
43
+ resp
44
+ end
45
+
46
+ def self.decode_response(response, format = 'json')
47
+ # TODO: account for systems that use a different JSON parser. Look for json gem...
48
+ # TODO: implement alternate formats: XML
49
+ object = {'error' => {'message' => 'failed to parse data.', 'code' => 1}}
50
+ begin
51
+ object = ActiveSupport::JSON.decode(response)
52
+ rescue
53
+ begin
54
+ object = JSON.parse(response)
55
+ rescue
56
+ end
57
+ end
58
+ object
59
+ end
60
+
61
+ def self.get_url(url)
62
+ url = URI.parse(url)
63
+ open_http(url).get("#{url.path}?#{url.query}")
64
+ end
65
+
66
+ def self.post_url(url, params)
67
+ url = URI.parse(url)
68
+ open_http(url).post("#{url.path}","#{object_to_query(params)}")
69
+ end
70
+
71
+ def self.open_http(url)
72
+ http = Net::HTTP.new(url.host, url.port)
73
+ # @csimport_http.read_timeout = @timeout || 30
74
+ if url.port == 443
75
+ http.use_ssl = true
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
77
+ end
78
+ http.start unless http.started?
79
+ http
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ require "test/unit"
2
+ require "cloudsponge"
3
+
4
+ class TestContact < Test::Unit::TestCase
5
+ def test_new_from_data
6
+ data = {'first_name' => 'John', 'last_name' => 'Smith', 'email' => nil, 'phone' => nil}
7
+ assert contact = Cloudsponge::Contact.new(data)
8
+ assert_equal data['first_name'], contact.first_name
9
+ assert_equal data['last_name'], contact.last_name
10
+ assert_equal "#{data['first_name']} #{data['last_name']}", contact.name
11
+ assert_equal nil, contact.email
12
+ assert_equal nil, contact.phone
13
+
14
+ data = {'first_name' => 'John', 'last_name' => 'Smith', 'email' => [{'address' => 'joe@example.com'}], 'phone' => [{'number' => '555-1234'}]}
15
+ assert contact = Cloudsponge::Contact.new(data)
16
+ assert_equal data['first_name'], contact.first_name
17
+ assert_equal data['last_name'], contact.last_name
18
+ assert_equal "#{data['first_name']} #{data['last_name']}", contact.name
19
+ assert_equal 'joe@example.com', contact.email
20
+ assert_equal '555-1234', contact.phone
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ require "test/unit"
2
+ require File.dirname(__FILE__) + "/../lib/cloudsponge"
3
+
4
+ class TestContactImporter < Test::Unit::TestCase
5
+ def test_version_exists
6
+ assert Cloudsponge::VERSION
7
+ end
8
+
9
+ DOMAIN_KEY = "Your Domain Key"
10
+ DOMAIN_PASSWORD = "Your Domain Password"
11
+
12
+ def test_u_p_import
13
+ contacts = nil
14
+ importer = Cloudsponge::ContactImporter.new(DOMAIN_KEY, DOMAIN_PASSWORD)
15
+ importer.begin_import('AOL', 'username', 'password')
16
+ loop do
17
+ events = importer.get_events
18
+ break unless events.select{ |e| e.is_error? }.empty?
19
+ unless events.select{ |e| e.is_complete? }.empty?
20
+ contacts = importer.get_contacts
21
+ break
22
+ end
23
+ end
24
+ assert contacts
25
+ end
26
+
27
+ def test_auth_import
28
+ contacts = nil
29
+ importer = Cloudsponge::ContactImporter.new(DOMAIN_KEY, DOMAIN_PASSWORD)
30
+ resp = importer.begin_import('YAHOO')
31
+ puts "Navigate to #{resp[:consent_url]} and complete the authentication process."
32
+ loop do
33
+ events = importer.get_events
34
+ break unless events.select{ |e| e.is_error? }.empty?
35
+ unless events.select{ |e| e.is_complete? }.empty?
36
+ contacts = importer.get_contacts
37
+ break
38
+ end
39
+ end
40
+ assert contacts
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ require "test/unit"
2
+ require "cloudsponge"
3
+
4
+ class TestCsException < Test::Unit::TestCase
5
+ def test_version_exists
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require "test/unit"
3
+ require "../lib/cloudsponge"
4
+
5
+ class TestUtility < Test::Unit::TestCase
6
+ def test_decode_response
7
+ require "json"
8
+ object = {'key1' => 'value1', 'key2' => 'value2', 'integer_key' => 5}
9
+ assert_equal object, Cloudsponge::Utility.decode_response(JSON.generate(object))
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudsponge
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 3
9
+ version: 0.9.3
10
+ platform: ruby
11
+ authors:
12
+ - Graeme Rouse
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-20 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: " Usage:\n contacts = nil\n importer = Cloudsponge::ContactImporter.new(DOMAIN_KEY, DOMAIN_PASSWORD)\n importer.begin_import('YAHOO')\n loop do\n events = importer.get_events\n break unless events.select{ |e| e.is_error? }.empty?\n unless events.select{ |e| e.is_completed? }.empty?\n contacts = importer.get_contacts\n break\n end\n end\n"
22
+ email: graeme@cloudsponge.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README
29
+ - HISTORY
30
+ - TODO
31
+ files:
32
+ - HISTORY
33
+ - lib/cloudsponge/contact.rb
34
+ - lib/cloudsponge/contact_importer.rb
35
+ - lib/cloudsponge/cs_exception.rb
36
+ - lib/cloudsponge/event.rb
37
+ - lib/cloudsponge/utility.rb
38
+ - lib/cloudsponge.rb
39
+ - Manifest.txt
40
+ - Rakefile
41
+ - README
42
+ - test/test_contact.rb
43
+ - test/test_contact_importer.rb
44
+ - test/test_cs_exception.rb
45
+ - test/test_utility.rb
46
+ - TODO
47
+ has_rdoc: true
48
+ homepage: http://www.cloudsponge.com
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.6
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A library wrapper for Cloudsponge.com's API
77
+ test_files: []
78
+