gmail_contacts 1.7 → 2.0

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