gmail_contacts 1.7 → 2.0

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.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,10 @@
1
+ === 2.0
2
+
3
+ * Major enhancements
4
+ * No longer uses gdata gem, AuthSub is implemented in gmail_contacts now.
5
+ * Ruby 1.9 ready!
6
+ * Now uses net-http-persistent
7
+
1
8
  === 1.7 / 2009-09-03
2
9
 
3
10
  * 1 minor enhancement
data/Rakefile CHANGED
@@ -11,8 +11,8 @@ Hoe.spec 'gmail_contacts' do
11
11
  self.rubyforge_name = 'seattlerb'
12
12
  self.testlib = :minitest
13
13
 
14
- extra_deps << ['gdata', '~> 1.1']
15
- extra_deps << ['nokogiri', '~> 1.2']
14
+ extra_deps << ['nokogiri', '~> 1.4']
15
+ extra_deps << ['net-http-persistent', '~> 1.2', '> 1.2']
16
16
  extra_dev_deps << ['minitest', '~> 1.3']
17
17
  end
18
18
 
@@ -1,16 +1,40 @@
1
- require 'rubygems'
2
- require 'gdata'
1
+ require 'cgi'
2
+ require 'net/http/persistent'
3
3
  require 'nokogiri'
4
4
 
5
5
  ##
6
6
  # GmailContacts sits atop GData and turns the contact feed into
7
7
  # GmailContacts::Contact objects for friendly consumption.
8
8
  #
9
+ # See sample/authsub.rb for an example which uses GmailContacts.
10
+ #
9
11
  # GmailContacts was sponsored by AT&T Interactive.
12
+ #
13
+ # == Upgrading from 1.x
14
+ #
15
+ # gmail_contacts no longer depends on gdata and performs its own AuthSub
16
+ # handling. Use GmailContacts#authsub_url instead of #authsub_url on
17
+ # #contact_api.
18
+ #
19
+ # gmail_contacts no longer uses gdata to raise exceptions and instead raise
20
+ # Net::HTTPServerException instead. Rescue Net::HTTPServerException instead
21
+ # of GData::Client::RequestError. Net::HTTPServerException responds to
22
+ # #response which you can use to determine what kind of error you got:
23
+ #
24
+ # begin
25
+ # gc.fetch 'nobody@example', false
26
+ # rescue Net::HTTPServerException => e
27
+ # case e.response
28
+ # when Net::HTTPForbidden then
29
+ # puts "You are not allowed to view contacts for this user"
30
+ # end
31
+ # end
10
32
 
11
33
  class GmailContacts
12
34
 
13
- VERSION = '1.7'
35
+ VERSION = '2.0'
36
+
37
+ class Error < RuntimeError; end
14
38
 
15
39
  Contact = Struct.new :title, :emails, :ims, :phone_numbers, :addresses,
16
40
  :photo_url
@@ -40,9 +64,10 @@ class GmailContacts
40
64
  attr_reader :author_name
41
65
 
42
66
  ##
43
- # GData::Client::Contacts object accessor for testing
67
+ # The current authsub token. If you upgrade a request token to a session
68
+ # token the value will change.
44
69
 
45
- attr_accessor :contact_api # :nodoc:
70
+ attr_reader :authsub_token
46
71
 
47
72
  ##
48
73
  # Contact data
@@ -63,11 +88,7 @@ class GmailContacts
63
88
 
64
89
  ##
65
90
  # Creates a new GmailContacts using +authsub_token+. If you don't yet have
66
- # an AuthSub token, call <tt>contact_api.auth_url</tt> providing your return
67
- # endpoint.
68
- #
69
- # See GData::Client::Base in the gdata gem and
70
- # http://code.google.com/apis/accounts/docs/AuthSub.html for more details.
91
+ # an AuthSub token, call #authsub_url.
71
92
 
72
93
  def initialize(authsub_token = nil, session_token = false)
73
94
  @authsub_token = authsub_token
@@ -79,8 +100,38 @@ class GmailContacts
79
100
  @author_name = nil
80
101
  @contacts ||= []
81
102
 
82
- @contact_api = GData::Client::Contacts.new
83
- @contact_api.authsub_token = @authsub_token if @authsub_token
103
+ @google = URI.parse 'https://www.google.com'
104
+ @http = Net::HTTP::Persistent.new "gmail_contacts_#{object_id}"
105
+ @http.debug_output = $stderr
106
+ @http.headers['Authorization'] = "AuthSub token=\"#{@authsub_token}\""
107
+ end
108
+
109
+ ##
110
+ # Returns a URL that will allow a user to authorize contact retrieval.
111
+ # Redirect the user to this URL and they will (should) approve your contact
112
+ # retrieval request.
113
+ #
114
+ # +next_url+ is where Google will redirect the user after they grant your
115
+ # request.
116
+ #
117
+ # See http://code.google.com/apis/accounts/docs/AuthSub.html for more
118
+ # details.
119
+
120
+ def authsub_url next_url, secure = false, session = true, domain = nil
121
+ query = {
122
+ 'next' => CGI.escape(next_url),
123
+ 'scope' => 'http%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds%2F',
124
+ 'secure' => (secure ? 1 : 0),
125
+ 'session' => (session ? 1 : 0),
126
+ }
127
+
128
+ query['hd'] = CGI.escape domain if domain
129
+
130
+ query = query.map do |key, value|
131
+ "#{key}=#{value}"
132
+ end.sort.join '&'
133
+
134
+ "https://www.google.com/accounts/AuthSubRequest?#{query}"
84
135
  end
85
136
 
86
137
  ##
@@ -89,10 +140,10 @@ class GmailContacts
89
140
  def fetch(email, revoke = true)
90
141
  get_token
91
142
 
92
- uri = "http://www.google.com/m8/feeds/contacts/#{email}/full"
143
+ uri = URI.parse "http://www.google.com/m8/feeds/contacts/#{email}/full"
93
144
 
94
145
  loop do
95
- res = @contact_api.get uri
146
+ res = request uri
96
147
 
97
148
  xml = Nokogiri::XML res.body
98
149
 
@@ -101,7 +152,7 @@ class GmailContacts
101
152
  next_uri = xml.xpath('//xmlns:feed/xmlns:link[@rel="next"]').first
102
153
  break unless next_uri
103
154
 
104
- uri = next_uri['href']
155
+ uri += next_uri['href']
105
156
  end
106
157
 
107
158
  yield if block_given?
@@ -118,17 +169,24 @@ class GmailContacts
118
169
  else
119
170
  contact.photo_url
120
171
  end
121
- res = @contact_api.get photo_url
172
+ response = request URI.parse photo_url
122
173
 
123
- res.body
174
+ response.body
124
175
  end
125
176
 
126
177
  ##
127
- # Fetches an AuthSub session token
178
+ # Fetches an AuthSub session token. Changes the value of #authsub_token so
179
+ # you can store it in a database or wherever.
128
180
 
129
181
  def get_token
130
182
  return if @session_token
131
- @contact_api.auth_handler.upgrade
183
+
184
+ response = request @google + '/accounts/AuthSubSessionToken'
185
+
186
+ response.body =~ /^Token=(.*)/
187
+
188
+ @authsub_token = $1
189
+ @http.headers['Authorization'] = "AuthSub token=\"#{@authsub_token}\""
132
190
  @session_token = true
133
191
  end
134
192
 
@@ -181,10 +239,27 @@ class GmailContacts
181
239
  end
182
240
 
183
241
  ##
184
- # Revokes our AuthSub token
242
+ # Performs a GET for +uri+ and returns the Net::HTTPResponse. Raises
243
+ # Net::HTTPError if the response wasn't a success (OK, Created, Found).
244
+
245
+ def request uri
246
+ response = @http.request uri
247
+
248
+ case response
249
+ when Net::HTTPOK, Net::HTTPCreated, Net::HTTPFound then
250
+ response
251
+ else
252
+ response.error!
253
+ end
254
+ end
255
+
256
+ ##
257
+ # Revokes our AuthSub session token
185
258
 
186
259
  def revoke_token
187
- @contact_api.auth_handler.revoke
260
+ request @google + '/accounts/AuthSubRevokeToken'
261
+
262
+ @session_token = false
188
263
  end
189
264
 
190
265
  ##
@@ -8,13 +8,13 @@ require 'gmail_contacts'
8
8
  #
9
9
  # require 'gmail_contacts'
10
10
  # require 'gmail_contacts/test_stub'
11
- #
11
+ #
12
12
  # class TestMyClass < Test::Unit::TestCase
13
13
  # def test_something
14
14
  # GmailContacts.stub
15
- #
15
+ #
16
16
  # # ... your code using gc
17
- #
17
+ #
18
18
  # end
19
19
  # end
20
20
  #
@@ -23,6 +23,15 @@ require 'gmail_contacts'
23
23
  #
24
24
  # If you set the authsub token to 'wrong_user_authsub_token', the test stub
25
25
  # will raise a GData::Client::AuthorizationError when you call #fetch.
26
+ #
27
+ # If you set the authsub token to 'authsub_token', #get_token will change it
28
+ # to 'authsub_token-upgraded' to indicate it was upgraded to a session token.
29
+ #
30
+ # If you fetch a photo with 404 in the url, #fetch_photo will raise a
31
+ # Net::HTTPServerException with a 404 response inside.
32
+ #
33
+ # #revoke_token will set the token to 'authsub_token-revoked' to indicate it
34
+ # was revoked.
26
35
 
27
36
  class GmailContacts::TestStub
28
37
 
@@ -138,6 +147,8 @@ class GmailContacts
138
147
 
139
148
  @stubbed = false
140
149
 
150
+ attr_reader :http
151
+
141
152
  def self.stub
142
153
  return if @stubbed
143
154
  @stubbed = true
@@ -146,25 +157,23 @@ class GmailContacts
146
157
 
147
158
  def get_token
148
159
  if @authsub_token == 'recycled_authsub_token' then
149
- res = GData::HTTP::Response.new
150
- res.status_code = 403
151
- res.body = 'recycled token'
152
- raise GData::Client::AuthorizationError, res
160
+ Net::HTTPForbidden.new(nil, '403', 'Forbidden').error!
153
161
  end
154
- old_get_token
155
- @contact_api.stub_reset
156
- @contact_api.stub_data << GmailContacts::TestStub::CONTACTS
157
- @contact_api.stub_data << GmailContacts::TestStub::CONTACTS2
162
+
163
+ @authsub_token = 'authsub_token-upgraded' if
164
+ @authsub_token == 'authsub_token'
165
+ @session_token = true
166
+
167
+ @http.stub_reset
168
+ @http.stub_data << GmailContacts::TestStub::CONTACTS
169
+ @http.stub_data << GmailContacts::TestStub::CONTACTS2
158
170
  end
159
171
 
160
172
  alias old_fetch fetch
161
173
 
162
174
  def fetch(*args)
163
175
  if @authsub_token == 'wrong_user_authsub_token' then
164
- res = GData::HTTP::Response.new
165
- res.status_code = 403
166
- res.body = 'wrong user'
167
- raise GData::Client::AuthorizationError, res
176
+ Net::HTTPForbidden.new(nil, '403', 'Forbidden').error!
168
177
  end
169
178
 
170
179
  old_fetch(*args)
@@ -179,77 +188,51 @@ class GmailContacts
179
188
  contact.photo_url
180
189
  end
181
190
 
182
- if photo_url =~ /404/ then
183
- res = Object.new
184
- def res.status_code() 404 end
185
- def res.body() 'Photo not found' end
186
- raise GData::Client::UnknownError, res
187
- end
191
+ Net::HTTPNotFound.new(nil, '404', 'Not Found').error! if
192
+ photo_url =~ /404/
188
193
 
189
194
  old_fetch_photo contact
190
195
  end
191
196
 
197
+ alias old_revoke_token revoke_token
198
+
199
+ def revoke_token
200
+ @authsub_token = 'authsub_token-revoked'
201
+ @session_token = false
202
+ end
203
+
192
204
  end
193
205
 
194
206
  end
195
207
  # :startdoc:
196
208
 
197
- ##
198
- # Extension that provides a stub for testing GmailContacts
199
- #
200
- # See GmailContacts::TestStub for usage details
201
-
202
- class GData::Client::Contacts
209
+ class Net::HTTP::Persistent
203
210
 
204
211
  ##
205
212
  # Accessor for data the stub will return
206
213
 
207
214
  attr_accessor :stub_data
208
215
 
209
- ##
210
- # Accessor for the stub AuthSub token
211
-
212
- attr_accessor :stub_token
213
-
214
216
  ##
215
217
  # Accessor for URLs the stub accessed
216
218
 
217
219
  attr_accessor :stub_urls
218
220
 
219
- ##
220
- # Sets the authsub token to +token+, but does no HTTP requests
221
-
222
- def authsub_token=(token)
223
- @stub_token = token
224
- auth_handler = Object.new
225
- auth_handler.instance_variable_set :@revoked, nil
226
- auth_handler.instance_variable_set :@upgraded, nil
227
- auth_handler.instance_variable_set :@token, token
228
- def auth_handler.revoke() @revoked = true end
229
- def auth_handler.revoked?() @revoked end
230
- def auth_handler.upgrade() @upgraded = true end
231
- def auth_handler.upgraded?() @upgraded end
232
- def auth_handler.token()
233
- [@token,
234
- (@revoked ? 'revoked' : nil),
235
- (@upgraded ? 'upgraded' : nil)].compact.join '-'
236
- end
237
- self.auth_handler = auth_handler
238
- end
221
+ alias old_request request
239
222
 
240
223
  ##
241
- # Fetches +url+, records in in stub_urls, and returns data if there's still
224
+ # Fetches +url+, records in stub_urls, and returns data if there's still
242
225
  # data in stub_data, or raises an exception
243
226
 
244
- def get(url)
245
- @stub_urls << url
227
+ def request(url)
228
+ @stub_urls << url.to_s
246
229
  raise 'stub data empty' if @stub_data.empty?
247
230
 
248
231
  data = @stub_data.shift
249
232
 
250
233
  return data.call if Proc === data
251
234
 
252
- res = Object.new
235
+ res = Net::HTTPOK.new nil, '200', 'OK'
253
236
  def res.body() @data end
254
237
  res.instance_variable_set :@data, data
255
238
  res
@@ -7,6 +7,9 @@ webrick = WEBrick::HTTPServer.new :Port => 3000
7
7
  webrick.mount_proc '/' do |req, res|
8
8
  res.content_type = 'text/html'
9
9
 
10
+ ##
11
+ # This is the initial page, a form with an email to fetch contacts for
12
+
10
13
  if req.path == '/' then
11
14
  res.body = <<-BODY
12
15
  <form action="http://#{Socket.gethostname}:3000/go">
@@ -15,12 +18,20 @@ webrick.mount_proc '/' do |req, res|
15
18
  </form>
16
19
  BODY
17
20
 
21
+ ##
22
+ # This is where we redirect the user to Google for approval, click "grant"
23
+ # on that page.
24
+
18
25
  elsif req.path == '/go' then
19
26
  gmail_contacts = GmailContacts.new
20
- url = gmail_contacts.contact_api.authsub_url \
21
- "http://#{Socket.gethostname}:3000/return?email=#{req.query['email']}"
27
+ url = gmail_contacts.authsub_url \
28
+ "http://localhost:3000/return?email=#{req.query['email']}"
22
29
  res.set_redirect WEBrick::HTTPStatus::SeeOther, url
23
30
 
31
+ ##
32
+ # Google sends us back here, so we create a new GmailContacts with the token
33
+ # they gave us and fetch the contacts for the email provided to /go
34
+
24
35
  elsif req.path == '/return' then
25
36
  res.body = "<h1>contacts</h1>\n"
26
37
 
@@ -37,7 +48,7 @@ webrick.mount_proc '/' do |req, res|
37
48
  res.body << "<li>#{contact.title} - #{contact.primary_email}\n"
38
49
  end
39
50
 
40
- res.body << "<\ul>\n"
51
+ res.body << "</ul>\n"
41
52
  rescue => e
42
53
  res.body << <<-BODY
43
54
  <h1>error</h1>
@@ -7,9 +7,9 @@ require 'pp'
7
7
  class TestGmailContacts < MiniTest::Unit::TestCase
8
8
 
9
9
  def setup
10
- @gc = GmailContacts.new 'token'
11
- @api = @gc.contact_api
12
- @api.stub_reset
10
+ @gc = GmailContacts.new 'token', true
11
+ @http = @gc.http
12
+ @http.stub_reset
13
13
 
14
14
  @eric =
15
15
  GmailContacts::Contact.new('Eric', %w[eric@example.com eric@example.net],
@@ -20,64 +20,70 @@ class TestGmailContacts < MiniTest::Unit::TestCase
20
20
  "http://www.google.com/m8/feeds/photos/media/eric%40example.com/18")
21
21
  end
22
22
 
23
+ def test_initialize
24
+ assert_equal 'AuthSub token="token"', @http.headers['Authorization']
25
+ assert_match %r%_\d+$%, @http.name
26
+ end
27
+
23
28
  def test_fetch
24
- @api.stub_data << GmailContacts::TestStub::CONTACTS
25
- @api.stub_data << GmailContacts::TestStub::CONTACTS2
29
+ @http.stub_data << GmailContacts::TestStub::CONTACTS
30
+ @http.stub_data << GmailContacts::TestStub::CONTACTS2
31
+ @http.stub_data << ''
26
32
 
27
33
  @gc.fetch 'eric@example.com'
28
34
 
29
35
  assert_equal 3, @gc.contacts.length
30
36
 
31
- assert_equal 2, @api.stub_urls.length
37
+ assert_equal 3, @http.stub_urls.length
32
38
  assert_equal 'http://www.google.com/m8/feeds/contacts/eric@example.com/full',
33
- @api.stub_urls.shift
39
+ @http.stub_urls.shift
34
40
  assert_equal 'http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&max-results=2',
35
- @api.stub_urls.shift
36
-
37
- assert @api.auth_handler.upgraded?
38
- assert @api.auth_handler.revoked?
41
+ @http.stub_urls.shift
42
+ assert_equal 'https://www.google.com/accounts/AuthSubRevokeToken',
43
+ @http.stub_urls.shift
39
44
  end
40
45
 
41
- def test_fetch_forbidden
42
- @api.stub_data << proc do
43
- res = GData::HTTP::Response.new
44
- raise GData::Client::AuthorizationError, res
45
- end
46
-
47
- assert_raises GData::Client::AuthorizationError do
48
- @gc.fetch 'notme@example.com'
49
- end
46
+ def test_fetch_auto_upgrade
47
+ @gc = GmailContacts.new 'token'
48
+ @http = @gc.http
49
+ @http.stub_reset
50
+ @http.stub_data << ''
51
+ @http.stub_data << GmailContacts::TestStub::CONTACTS
52
+ @http.stub_data << GmailContacts::TestStub::CONTACTS2
53
+ @http.stub_data << ''
50
54
 
51
- assert_equal 0, @gc.contacts.length
55
+ @gc.fetch 'eric@example.com'
52
56
 
53
- assert_equal 1, @api.stub_urls.length
54
- assert_equal 'http://www.google.com/m8/feeds/contacts/notme@example.com/full',
55
- @api.stub_urls.shift
57
+ assert_equal 3, @gc.contacts.length
56
58
 
57
- assert @api.auth_handler.upgraded?
58
- assert @api.auth_handler.revoked?
59
+ assert_equal 4, @http.stub_urls.length
60
+ assert_equal 'https://www.google.com/accounts/AuthSubSessionToken',
61
+ @http.stub_urls.shift
62
+ assert_equal 'http://www.google.com/m8/feeds/contacts/eric@example.com/full',
63
+ @http.stub_urls.shift
64
+ assert_equal 'http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&max-results=2',
65
+ @http.stub_urls.shift
66
+ assert_equal 'https://www.google.com/accounts/AuthSubRevokeToken',
67
+ @http.stub_urls.shift
59
68
  end
60
69
 
61
70
  def test_fetch_no_revoke
62
- @api.stub_data << GmailContacts::TestStub::CONTACTS
63
- @api.stub_data << GmailContacts::TestStub::CONTACTS2
71
+ @http.stub_data << GmailContacts::TestStub::CONTACTS
72
+ @http.stub_data << GmailContacts::TestStub::CONTACTS2
64
73
 
65
74
  @gc.fetch 'eric@example.com', false
66
75
 
67
76
  assert_equal 3, @gc.contacts.length
68
77
 
69
- assert_equal 2, @api.stub_urls.length
78
+ assert_equal 2, @http.stub_urls.length
70
79
  assert_equal 'http://www.google.com/m8/feeds/contacts/eric@example.com/full',
71
- @api.stub_urls.shift
80
+ @http.stub_urls.shift
72
81
  assert_equal 'http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&max-results=2',
73
- @api.stub_urls.shift
74
-
75
- assert @api.auth_handler.upgraded?
76
- refute @api.auth_handler.revoked?
82
+ @http.stub_urls.shift
77
83
  end
78
84
 
79
85
  def test_fetch_photo
80
- @api.stub_data << 'THIS IS A PHOTO!'
86
+ @http.stub_data << 'THIS IS A PHOTO!'
81
87
 
82
88
  photo = @gc.fetch_photo @eric
83
89
 
@@ -85,13 +91,36 @@ class TestGmailContacts < MiniTest::Unit::TestCase
85
91
  end
86
92
 
87
93
  def test_fetch_photo_url
88
- @api.stub_data << 'THIS IS A PHOTO!'
94
+ @http.stub_data << 'THIS IS A PHOTO!'
89
95
 
90
96
  photo = @gc.fetch_photo @eric.photo_url
91
97
 
92
98
  assert_equal 'THIS IS A PHOTO!', photo
93
99
  end
94
100
 
101
+ def test_get_token
102
+ assert @gc.token?, 'sanity, we should already have a session token'
103
+
104
+ assert_nil @gc.get_token
105
+ end
106
+
107
+ def test_get_token_non_session
108
+ @gc = GmailContacts.new 'token'
109
+ @http = @gc.http
110
+ @http.stub_reset
111
+ @http.stub_data << 'Token=new token'
112
+
113
+ @gc.get_token
114
+
115
+ assert @gc.token?
116
+ assert_equal 'new token', @gc.authsub_token
117
+ assert_equal 'AuthSub token="new token"', @http.headers['Authorization']
118
+
119
+ assert_equal 1, @http.stub_urls.length
120
+ assert_equal 'https://www.google.com/accounts/AuthSubSessionToken',
121
+ @http.stub_urls.shift
122
+ end
123
+
95
124
  def test_parse
96
125
  @gc.parse Nokogiri::XML(GmailContacts::TestStub::CONTACTS)
97
126
 
@@ -134,5 +163,41 @@ class TestGmailContacts < MiniTest::Unit::TestCase
134
163
  assert_equal expected, @gc.contacts
135
164
  end
136
165
 
166
+ def test_request
167
+ @http.stub_data << 'blah'
168
+
169
+ res = @gc.request 'http://example'
170
+
171
+ assert_equal 'blah', res.body
172
+ end
173
+
174
+ def test_request_unsuccessful
175
+ @http.stub_data << proc do
176
+ Net::HTTPForbidden.new nil, '403', 'Forbidden'
177
+ end
178
+
179
+ assert_raises Net::HTTPServerException do
180
+ @gc.request 'http://example'
181
+ end
182
+ end
183
+
184
+ def test_revoke_token
185
+ @http.stub_data << ''
186
+
187
+ @gc.revoke_token
188
+
189
+ refute @gc.token?
190
+
191
+ assert_equal 1, @http.stub_urls.length
192
+ assert_equal 'https://www.google.com/accounts/AuthSubRevokeToken',
193
+ @http.stub_urls.shift
194
+ end
195
+
196
+ def test_token_eh
197
+ assert @gc.token?
198
+
199
+ refute GmailContacts.new.token?
200
+ end
201
+
137
202
  end
138
203
 
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gmail_contacts
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.7"
4
+ hash: 3
5
+ prerelease: false
6
+ segments:
7
+ - 2
8
+ - 0
9
+ version: "2.0"
5
10
  platform: ruby
6
11
  authors:
7
12
  - Eric Hodel
@@ -30,49 +35,125 @@ cert_chain:
30
35
  x52qPcexcYZR7w==
31
36
  -----END CERTIFICATE-----
32
37
 
33
- date: 2009-09-03 00:00:00 -07:00
38
+ date: 2010-06-03 00:00:00 -07:00
34
39
  default_executable:
35
40
  dependencies:
36
41
  - !ruby/object:Gem::Dependency
37
- name: gdata
38
- type: :runtime
39
- version_requirement:
40
- version_requirements: !ruby/object:Gem::Requirement
42
+ name: nokogiri
43
+ prerelease: false
44
+ requirement: &id001 !ruby/object:Gem::Requirement
45
+ none: false
41
46
  requirements:
42
47
  - - ~>
43
48
  - !ruby/object:Gem::Version
44
- version: "1.1"
45
- version:
46
- - !ruby/object:Gem::Dependency
47
- name: nokogiri
49
+ hash: 7
50
+ segments:
51
+ - 1
52
+ - 4
53
+ version: "1.4"
48
54
  type: :runtime
49
- version_requirement:
50
- version_requirements: !ruby/object:Gem::Requirement
55
+ version_requirements: *id001
56
+ - !ruby/object:Gem::Dependency
57
+ name: net-http-persistent
58
+ prerelease: false
59
+ requirement: &id002 !ruby/object:Gem::Requirement
60
+ none: false
51
61
  requirements:
52
62
  - - ~>
53
63
  - !ruby/object:Gem::Version
64
+ hash: 11
65
+ segments:
66
+ - 1
67
+ - 2
68
+ version: "1.2"
69
+ - - ">"
70
+ - !ruby/object:Gem::Version
71
+ hash: 11
72
+ segments:
73
+ - 1
74
+ - 2
54
75
  version: "1.2"
55
- version:
76
+ type: :runtime
77
+ version_requirements: *id002
78
+ - !ruby/object:Gem::Dependency
79
+ name: rubyforge
80
+ prerelease: false
81
+ requirement: &id003 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 9
87
+ segments:
88
+ - 2
89
+ - 0
90
+ - 3
91
+ version: 2.0.3
92
+ type: :development
93
+ version_requirements: *id003
94
+ - !ruby/object:Gem::Dependency
95
+ name: gemcutter
96
+ prerelease: false
97
+ requirement: &id004 !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 11
103
+ segments:
104
+ - 0
105
+ - 5
106
+ - 0
107
+ version: 0.5.0
108
+ type: :development
109
+ version_requirements: *id004
56
110
  - !ruby/object:Gem::Dependency
57
111
  name: minitest
112
+ prerelease: false
113
+ requirement: &id005 !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 1
121
+ - 5
122
+ - 0
123
+ version: 1.5.0
58
124
  type: :development
59
- version_requirement:
60
- version_requirements: !ruby/object:Gem::Requirement
125
+ version_requirements: *id005
126
+ - !ruby/object:Gem::Dependency
127
+ name: minitest
128
+ prerelease: false
129
+ requirement: &id006 !ruby/object:Gem::Requirement
130
+ none: false
61
131
  requirements:
62
132
  - - ~>
63
133
  - !ruby/object:Gem::Version
134
+ hash: 9
135
+ segments:
136
+ - 1
137
+ - 3
64
138
  version: "1.3"
65
- version:
139
+ type: :development
140
+ version_requirements: *id006
66
141
  - !ruby/object:Gem::Dependency
67
142
  name: hoe
68
- type: :development
69
- version_requirement:
70
- version_requirements: !ruby/object:Gem::Requirement
143
+ prerelease: false
144
+ requirement: &id007 !ruby/object:Gem::Requirement
145
+ none: false
71
146
  requirements:
72
147
  - - ">="
73
148
  - !ruby/object:Gem::Version
74
- version: 2.3.3
75
- version:
149
+ hash: 27
150
+ segments:
151
+ - 2
152
+ - 5
153
+ - 0
154
+ version: 2.5.0
155
+ type: :development
156
+ version_requirements: *id007
76
157
  description: |-
77
158
  Simple Gmail contacts extraction using GData.
78
159
 
@@ -109,21 +190,27 @@ rdoc_options:
109
190
  require_paths:
110
191
  - lib
111
192
  required_ruby_version: !ruby/object:Gem::Requirement
193
+ none: false
112
194
  requirements:
113
195
  - - ">="
114
196
  - !ruby/object:Gem::Version
197
+ hash: 3
198
+ segments:
199
+ - 0
115
200
  version: "0"
116
- version:
117
201
  required_rubygems_version: !ruby/object:Gem::Requirement
202
+ none: false
118
203
  requirements:
119
204
  - - ">="
120
205
  - !ruby/object:Gem::Version
206
+ hash: 3
207
+ segments:
208
+ - 0
121
209
  version: "0"
122
- version:
123
210
  requirements: []
124
211
 
125
212
  rubyforge_project: seattlerb
126
- rubygems_version: 1.3.5
213
+ rubygems_version: 1.3.7
127
214
  signing_key:
128
215
  specification_version: 3
129
216
  summary: Simple Gmail contacts extraction using GData
metadata.gz.sig CHANGED
Binary file