gmail_contacts 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ History.txt
2
+ README.txt
3
+ lib
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-04-08
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ .document
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/gmail_contacts.rb
7
+ lib/gmail_contacts/test_stub.rb
8
+ sample/authsub.rb
9
+ test/test_gmail_contacts.rb
data/README.txt ADDED
@@ -0,0 +1,51 @@
1
+ = gmail_contacts
2
+
3
+ * http://seattlerb.rubyforge.org/gmail_contacts
4
+
5
+ == DESCRIPTION:
6
+
7
+ Simple Gmail contacts extraction using GData.
8
+
9
+ gmail_contacts development was sponsored by AT&T Interactive.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Lets you extract contacts from GMail
14
+
15
+ == SYNOPSIS:
16
+
17
+ See sample/authsub.rb
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * gdata gem
22
+ * Gmail account
23
+
24
+ == INSTALL:
25
+
26
+ * sudo gem install gmail_contacts
27
+
28
+ == LICENSE:
29
+
30
+ (The MIT License)
31
+
32
+ Copyright (c) 2009 Eric Hodel
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining
35
+ a copy of this software and associated documentation files (the
36
+ 'Software'), to deal in the Software without restriction, including
37
+ without limitation the rights to use, copy, modify, merge, publish,
38
+ distribute, sublicense, and/or sell copies of the Software, and to
39
+ permit persons to whom the Software is furnished to do so, subject to
40
+ the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be
43
+ included in all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
49
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
51
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/gmail_contacts.rb'
6
+
7
+ Hoe.new('gmail_contacts', GmailContacts::VERSION) do |p|
8
+ p.rubyforge_name = 'seattlerb' # if different than lowercase project name
9
+ p.developer 'Eric Hodel', 'drbrain@segment7.net'
10
+
11
+ p.extra_deps << ['gdata', '~> 1.0']
12
+ p.extra_deps << ['nokogiri', '~> 1.2']
13
+ end
14
+
15
+ # vim: syntax=Ruby
@@ -0,0 +1,209 @@
1
+ require 'gmail_contacts'
2
+
3
+ ##
4
+ # Handy data files and GData modifications that allow GmailContacts to be
5
+ # more easily tested
6
+ #
7
+ # To setup:
8
+ #
9
+ # require 'gmail_contacts'
10
+ # require 'gmail_contacts/test_stub'
11
+ #
12
+ # class TestMyClass < Test::Unit::TestCase
13
+ # def test_something
14
+ # GmailContacts.stub
15
+ #
16
+ # # ... your code using gc
17
+ #
18
+ # end
19
+ # end
20
+ #
21
+ # If you set the authsub token to 'recycled_authsub_token', the test stub will
22
+ # raise a GData::Client::AuthorizationError.
23
+
24
+ class GmailContacts::TestStub
25
+
26
+ ##
27
+ # First page of contacts
28
+
29
+ CONTACTS = <<-ATOM
30
+ <?xml version="1.0" encoding="UTF-8"?>
31
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;CEMBRHY6fSp7ImA9WxVUGEk.&quot;">
32
+ <id>eric@example.com</id>
33
+ <updated>2009-03-23T21:07:35.815Z</updated>
34
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
35
+ <title>drbrain's Contacts</title>
36
+ <link rel="alternate" type="text/html" href="http://www.google.com/"/>
37
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full"/>
38
+ <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full"/>
39
+ <link rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/batch"/>
40
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full?max-results=2"/>
41
+ <link rel="next" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&amp;max-results=2"/>
42
+ <author>
43
+ <name>drbrain</name>
44
+ <email>eric@example.com</email>
45
+ </author>
46
+ <generator version="1.0" uri="http://www.google.com/m8/feeds">Contacts</generator>
47
+ <openSearch:totalResults>3</openSearch:totalResults>
48
+ <openSearch:startIndex>1</openSearch:startIndex>
49
+ <openSearch:itemsPerPage>2</openSearch:itemsPerPage>
50
+ <entry gd:etag="&quot;SHg-cTVSLip7ImA9WB5WGUUIQgc.&quot;">
51
+ <id>http://www.google.com/m8/feeds/contacts/eric%40example.com/base/0</id>
52
+ <updated>2007-08-01T15:35:39.659Z</updated>
53
+ <app:edited xmlns:app="http://www.w3.org/2007/app">2007-08-01T15:35:39.659Z</app:edited>
54
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
55
+ <title>Sean</title>
56
+ <link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="http://www.google.com/m8/feeds/photos/media/eric%40example.com/0"/>
57
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/0"/>
58
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/0"/>
59
+ <gd:email rel="http://schemas.google.com/g/2005#other" address="sean@example.com" primary="true"/>
60
+ </entry>
61
+ <entry gd:etag="&quot;QXk4fjVSLyp7ImA9WxVUGUwDRgE.&quot;">
62
+ <id>http://www.google.com/m8/feeds/contacts/eric%40example.com/base/18</id>
63
+ <updated>2009-03-24T18:25:50.736Z</updated>
64
+ <app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-24T18:25:50.736Z</app:edited>
65
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
66
+ <title>Eric</title>
67
+ <link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="http://www.google.com/m8/feeds/photos/media/eric%40example.com/18" gd:etag="&quot;UD9rbkUqSip7ImBkJkcZdVBoHxkeNFMKV1E.&quot;"/>
68
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/18"/>
69
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/18"/>
70
+ <gd:email rel="http://schemas.google.com/g/2005#other" address="eric@example.com" primary="true"/>
71
+ <gd:email rel="http://schemas.google.com/g/2005#other" address="eric@example.net"/>
72
+ <gd:im address="example" protocol="http://schemas.google.com/g/2005#AIM" rel="http://schemas.google.com/g/2005#other"/>
73
+ <gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">999 555 1212</gd:phoneNumber>
74
+ <gd:postalAddress rel="http://schemas.google.com/g/2005#home">123 Any Street
75
+ AnyTown, ZZ 99999</gd:postalAddress>
76
+ <gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/eric%40example.com/base/6"/>
77
+ </entry>
78
+ </feed>
79
+ ATOM
80
+
81
+ ##
82
+ # Second page of contacts
83
+
84
+ CONTACTS2 = <<-ATOM
85
+ <?xml version="1.0" encoding="UTF-8"?>
86
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;CEYFQXkzfCp7ImA9WxVUGEg.&quot;">
87
+ <id>eric@example.com</id>
88
+ <updated>2009-03-23T23:48:30.784Z</updated>
89
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
90
+ <title>drbrain's Contacts</title>
91
+ <link rel="alternate" type="text/html" href="http://www.google.com/"/>
92
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full"/>
93
+ <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full"/>
94
+ <link rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/batch"/>
95
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&amp;max-results=2"/>
96
+ <link rel="previous" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=1&amp;max-results=2"/>
97
+ <author>
98
+ <name>drbrain</name>
99
+ <email>eric@example.com</email>
100
+ </author>
101
+ <generator version="1.0" uri="http://www.google.com/m8/feeds">Contacts</generator>
102
+ <openSearch:totalResults>3</openSearch:totalResults>
103
+ <openSearch:startIndex>3</openSearch:startIndex>
104
+ <openSearch:itemsPerPage>2</openSearch:itemsPerPage>
105
+ <entry gd:etag="&quot;QXk_fTVSLyp7ImA9WxVUFUQITgA.&quot;">
106
+ <id>http://www.google.com/m8/feeds/contacts/eric%40example.com/base/5834fb5d0b47bfd7</id>
107
+ <updated>2009-03-20T23:49:00.745Z</updated>
108
+ <app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-20T23:49:00.745Z</app:edited>
109
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
110
+ <title>Coby</title>
111
+ <link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="http://www.google.com/m8/feeds/photos/media/eric%40example.com/5834fb5d0b47bfd7" gd:etag="&quot;eRJhPnolbCp7ImBjG0U0GBtuHmVAdnsJYzM.&quot;"/>
112
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/5834fb5d0b47bfd7"/>
113
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/m8/feeds/contacts/eric%40example.com/full/5834fb5d0b47bfd7"/>
114
+ <gd:email rel="http://schemas.google.com/g/2005#other" address="coby@example.com" primary="true"/>
115
+ <gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/eric%40example.com/base/6"/>
116
+ </entry>
117
+ </feed>
118
+ ATOM
119
+
120
+ end
121
+
122
+ # :stopdoc:
123
+ class GmailContacts
124
+
125
+ @stubbed = false
126
+
127
+ def self.stub
128
+ return if @stubbed
129
+ @stubbed = true
130
+
131
+ alias old_get_token get_token
132
+
133
+ def get_token
134
+ if @authsub_token == 'recycled_authsub_token' then
135
+ raise GData::Client::AuthorizationError, 'recycled token'
136
+ end
137
+ old_get_token
138
+ @contact_api.stub_reset
139
+ @contact_api.stub_data << GmailContacts::TestStub::CONTACTS
140
+ @contact_api.stub_data << GmailContacts::TestStub::CONTACTS2
141
+ end
142
+ end
143
+
144
+ end
145
+ # :startdoc:
146
+
147
+ ##
148
+ # Extension that provides a stub for testing GmailContacts
149
+ #
150
+ # See GmailContacts::TestStub for usage details
151
+
152
+ class GData::Client::Contacts
153
+
154
+ ##
155
+ # Accessor for data the stub will return
156
+
157
+ attr_accessor :stub_data
158
+
159
+ ##
160
+ # Accessor for the stub AuthSub token
161
+
162
+ attr_accessor :stub_token
163
+
164
+ ##
165
+ # Accessor for URLs the stub accessed
166
+
167
+ attr_accessor :stub_urls
168
+
169
+ ##
170
+ # Sets the authsub token to +token+, but does no HTTP requests
171
+
172
+ def authsub_token=(token)
173
+ @stub_token = token
174
+ auth_handler = Object.new
175
+ def auth_handler.upgrade() @upgraded = true end
176
+ def auth_handler.upgraded?() @upgraded end
177
+ def auth_handler.revoke() @revoked = true end
178
+ def auth_handler.revoked?() @revoked end
179
+ self.auth_handler = auth_handler
180
+ end
181
+
182
+ ##
183
+ # Fetches +url+, records in in stub_urls, and returns data if there's still
184
+ # data in stub_data, or raises an exception
185
+
186
+ def get(url)
187
+ @stub_urls << url
188
+ raise 'stub data empty' if @stub_data.empty?
189
+
190
+ data = @stub_data.shift
191
+
192
+ return data.call if Proc === data
193
+
194
+ res = Object.new
195
+ def res.body() @data end
196
+ res.instance_variable_set :@data, data
197
+ res
198
+ end
199
+
200
+ ##
201
+ # Resets the stub to empty
202
+
203
+ def stub_reset
204
+ @stub_urls = []
205
+ @stub_data = []
206
+ end
207
+
208
+ end
209
+
@@ -0,0 +1,228 @@
1
+ require 'rubygems'
2
+ require 'gdata'
3
+ require 'nokogiri'
4
+
5
+ ##
6
+ # GmailContacts sits atop GData and turns the contact feed into
7
+ # GmailContacts::Contact objects for friendly consumption.
8
+ #
9
+ # GmailContacts was sponsored by AT&T Interactive.
10
+
11
+ class GmailContacts
12
+
13
+ VERSION = '1.0'
14
+
15
+ Contact = Struct.new :title, :emails, :ims, :phone_numbers, :addresses
16
+
17
+ ##
18
+ # Struct containing title, emails, ims, phone_numbers and addresses
19
+
20
+ class Contact
21
+
22
+ def pretty_print(q) # :nodoc:
23
+ q.text "#{title}"
24
+ q.breakable
25
+
26
+ q.group 2 do
27
+ q.breakable
28
+ q.group 2, 'emails: ' do
29
+ emails.each_with_index do |email, i|
30
+ unless i == 0 then
31
+ q.text ','
32
+ q.breakable
33
+ end
34
+
35
+ email += " (Primary)" if i == 0
36
+ q.text email
37
+ end
38
+ end
39
+
40
+ q.breakable
41
+ q.group 2, 'ims: ' do
42
+ ims.each_with_index do |(address, type), i|
43
+ unless i == 0 then
44
+ q.text ','
45
+ q.breakable
46
+ end
47
+
48
+ q.text "#{address} (#{type.sub(/.*#/, '')})"
49
+ end
50
+ end
51
+
52
+ q.breakable
53
+ q.group 2, 'phone numbers: ' do
54
+ phone_numbers.each_with_index do |(number, type), i|
55
+ unless i == 0 then
56
+ q.text ','
57
+ q.breakable
58
+ end
59
+
60
+ q.text "#{number} (#{type.sub(/.*#/, '')})"
61
+ end
62
+ end
63
+
64
+ q.breakable
65
+ addresses.each do |address, type|
66
+ q.group 2, "#{type.sub(/.*#/, '')} address:\n" do
67
+ address = address.split("\n").each do |line|
68
+ q.text "#{' ' * q.indent}#{line}\n"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ ##
76
+ # Returns the user's primary email address
77
+
78
+ def primary_email
79
+ emails.first
80
+ end
81
+
82
+ end
83
+
84
+ ##
85
+ # Contact list author's email
86
+
87
+ attr_reader :author_email
88
+
89
+ ##
90
+ # Contact list author's name
91
+
92
+ attr_reader :author_name
93
+
94
+ ##
95
+ # GData::Client::Contacts object accessor for testing
96
+
97
+ attr_accessor :contact_api # :nodoc:
98
+
99
+ ##
100
+ # Contact data
101
+ #
102
+ # An Array with contact title, primary email and alternate emails
103
+
104
+ attr_reader :contacts
105
+
106
+ ##
107
+ # Contacts list identifier
108
+
109
+ attr_reader :id
110
+
111
+ ##
112
+ # Contacts list title
113
+
114
+ attr_reader :title
115
+
116
+ ##
117
+ # Creates a new GmailContacts using +authsub_token+. If you don't yet have
118
+ # an AuthSub token, call <tt>contact_api.auth_url</tt> providing your return
119
+ # endpoint.
120
+ #
121
+ # See GData::Client::Base in the gdata gem and
122
+ # http://code.google.com/apis/accounts/docs/AuthSub.html for more details.
123
+
124
+ def initialize(authsub_token = nil)
125
+ @authsub_token = authsub_token
126
+ @session_token = false
127
+
128
+ @id = nil
129
+ @title = nil
130
+ @author_email = nil
131
+ @author_name = nil
132
+ @contacts ||= []
133
+
134
+ @contact_api = GData::Client::Contacts.new
135
+ end
136
+
137
+ ##
138
+ # Fetches contacts from google for +email+.
139
+
140
+ def fetch(email)
141
+ get_token
142
+
143
+ uri = "http://www.google.com/m8/feeds/contacts/#{email}/full"
144
+
145
+ loop do
146
+ res = @contact_api.get uri
147
+
148
+ xml = Nokogiri::XML res.body
149
+
150
+ parse xml
151
+
152
+ next_uri = xml.xpath('//xmlns:feed/xmlns:link[@rel="next"]').first
153
+ break unless next_uri
154
+
155
+ uri = next_uri['href']
156
+ end
157
+ ensure
158
+ revoke_token if token?
159
+ end
160
+
161
+ ##
162
+ # Fetches an AuthSub session token
163
+
164
+ def get_token
165
+ @contact_api.authsub_token = @authsub_token
166
+ @contact_api.auth_handler.upgrade
167
+ @session_token = true
168
+ end
169
+
170
+ ##
171
+ # Extracts contact information from +xml+, appending it to the current
172
+ # contact information
173
+
174
+ def parse(xml)
175
+ @id = xml.xpath('//xmlns:feed/xmlns:id').first.text
176
+ @title = xml.xpath('//xmlns:feed/xmlns:title').first.text
177
+
178
+ @author_email =
179
+ xml.xpath('//xmlns:feed/xmlns:author/xmlns:email').first.text
180
+ @author_name = xml.xpath('//xmlns:feed/xmlns:author/xmlns:name').first.text
181
+
182
+ xml.xpath('//xmlns:feed/xmlns:entry').each do |entry|
183
+ title = entry.xpath('.//xmlns:title').first.text
184
+ emails = []
185
+ emails << entry.xpath('.//gd:email[@primary]').first['address']
186
+ alternates = entry.xpath('.//gd:email[not(@primary)]')
187
+
188
+ emails.push(*alternates.map { |e| e['address'] })
189
+
190
+ ims = []
191
+ entry.xpath('.//gd:im').each do |im|
192
+ ims << [im['address'], im['protocol']]
193
+ end
194
+
195
+ phones = []
196
+ entry.xpath('.//gd:phoneNumber').each do |phone|
197
+ phones << [phone.text, phone['rel']]
198
+ end
199
+
200
+ addresses = []
201
+ entry.xpath('.//gd:postalAddress').each do |address|
202
+ addresses << [address.text, address['rel']]
203
+ end
204
+
205
+ contact = Contact.new title, emails, ims, phones, addresses
206
+
207
+ @contacts << contact
208
+ end
209
+
210
+ self
211
+ end
212
+
213
+ ##
214
+ # Revokes our AuthSub token
215
+
216
+ def revoke_token
217
+ @contact_api.auth_handler.revoke
218
+ end
219
+
220
+ ##
221
+ # Do we have an AuthSub session token?
222
+
223
+ def token?
224
+ @session_token
225
+ end
226
+
227
+ end
228
+
data/sample/authsub.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'webrick'
3
+ require 'gmail_contacts'
4
+
5
+ webrick = WEBrick::HTTPServer.new :Port => 3000
6
+
7
+ webrick.mount_proc '/' do |req, res|
8
+ res.content_type = 'text/html'
9
+
10
+ if req.path == '/' then
11
+ res.body = <<-BODY
12
+ <form action="http://#{Socket.gethostname}:3000/go">
13
+ <input name="email">
14
+ <input type="submit" value="go">
15
+ </form>
16
+ BODY
17
+
18
+ elsif req.path == '/go' then
19
+ gmail_contacts = GmailContacts.new
20
+ url = gmail_contacts.contact_api.authsub_url \
21
+ "http://#{Socket.gethostname}:3000/return?email=#{req.query['email']}"
22
+ res.set_redirect WEBrick::HTTPStatus::SeeOther, url
23
+
24
+ elsif req.path == '/return' then
25
+ res.body = "<h1>contacts</h1>\n"
26
+
27
+ begin
28
+ gmail_contacts = GmailContacts.new req.query['token'].to_s
29
+
30
+ email = req.query['email']
31
+
32
+ gmail_contacts.fetch email
33
+
34
+ res.body << "<h1>contacts</h1>\n\n<ul>\n"
35
+
36
+ gmail_contacts.contacts.each do |contact|
37
+ res.body << "<li>#{contact.title} - #{contact.primary_email}\n"
38
+ end
39
+
40
+ res.body << "<\ul>\n"
41
+ rescue => e
42
+ res.body << <<-BODY
43
+ <h1>error</h1>
44
+
45
+ <p>#{e.message}
46
+
47
+ <pre>#{e.backtrace.join "\n"}</pre>
48
+ BODY
49
+ end
50
+ end
51
+ end
52
+
53
+ trap 'INT' do webrick.shutdown end
54
+ trap 'TERM' do webrick.shutdown end
55
+
56
+ webrick.start
57
+
@@ -0,0 +1,112 @@
1
+ require 'test/unit'
2
+ require 'gmail_contacts'
3
+ require 'gmail_contacts/test_stub'
4
+ require 'pp'
5
+
6
+ class TestGmailContacts < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @gc = GmailContacts.new 'token'
10
+ @api = @gc.contact_api
11
+ @api.stub_reset
12
+
13
+ @eric =
14
+ GmailContacts::Contact.new('Eric', %w[eric@example.com eric@example.net],
15
+ [%w[example http://schemas.google.com/g/2005#AIM]],
16
+ [%w[999\ 555\ 1212 http://schemas.google.com/g/2005#mobile]],
17
+ [["123 Any Street\nAnyTown, ZZ 99999",
18
+ "http://schemas.google.com/g/2005#home"]])
19
+ end
20
+
21
+ def test_contact_pretty_print
22
+ str = ''
23
+ PP.pp @eric, str
24
+
25
+ expected = <<-EXPECTED
26
+ Eric
27
+
28
+ emails: eric@example.com (Primary), eric@example.net
29
+ ims: example (AIM)
30
+ phone numbers: 999 555 1212 (mobile)
31
+ home address:
32
+ 123 Any Street
33
+ AnyTown, ZZ 99999
34
+
35
+ EXPECTED
36
+
37
+ assert_equal expected, str
38
+ end
39
+
40
+ def test_fetch
41
+ @api.stub_data << GmailContacts::TestStub::CONTACTS
42
+ @api.stub_data << GmailContacts::TestStub::CONTACTS2
43
+
44
+ @gc.fetch 'eric@example.com'
45
+
46
+ assert_equal 3, @gc.contacts.length
47
+
48
+ assert_equal 2, @api.stub_urls.length
49
+ assert_equal 'http://www.google.com/m8/feeds/contacts/eric@example.com/full',
50
+ @api.stub_urls.shift
51
+ assert_equal 'http://www.google.com/m8/feeds/contacts/eric%40example.com/full?start-index=3&max-results=2',
52
+ @api.stub_urls.shift
53
+
54
+ assert @api.auth_handler.upgraded?
55
+ assert @api.auth_handler.revoked?
56
+ end
57
+
58
+ def test_fetch_forbidden
59
+ @api.stub_data << proc do
60
+ raise GData::Client::AuthorizationError
61
+ end
62
+
63
+ assert_raise GData::Client::AuthorizationError do
64
+ @gc.fetch 'notme@example.com'
65
+ end
66
+
67
+ assert_equal 0, @gc.contacts.length
68
+
69
+ assert_equal 1, @api.stub_urls.length
70
+ assert_equal 'http://www.google.com/m8/feeds/contacts/notme@example.com/full',
71
+ @api.stub_urls.shift
72
+
73
+ assert @api.auth_handler.upgraded?
74
+ assert @api.auth_handler.revoked?
75
+ end
76
+
77
+ def test_parse
78
+ @gc.parse Nokogiri::XML(GmailContacts::TestStub::CONTACTS)
79
+
80
+ assert_equal 'eric@example.com', @gc.id
81
+ assert_equal 'drbrain\'s Contacts', @gc.title
82
+ assert_equal 'drbrain', @gc.author_name
83
+ assert_equal 'eric@example.com', @gc.author_email
84
+
85
+ expected = [
86
+ GmailContacts::Contact.new('Sean', %w[sean@example.com], [], [], []),
87
+ @eric
88
+ ]
89
+
90
+ assert_equal expected, @gc.contacts
91
+ end
92
+
93
+ def test_parse_two_pages
94
+ @gc.parse Nokogiri::XML(GmailContacts::TestStub::CONTACTS)
95
+ @gc.parse Nokogiri::XML(GmailContacts::TestStub::CONTACTS2)
96
+
97
+ assert_equal 'eric@example.com', @gc.id
98
+ assert_equal 'drbrain\'s Contacts', @gc.title
99
+ assert_equal 'drbrain', @gc.author_name
100
+ assert_equal 'eric@example.com', @gc.author_email
101
+
102
+ expected = [
103
+ GmailContacts::Contact.new('Sean', %w[sean@example.com], [], [], []),
104
+ @eric,
105
+ GmailContacts::Contact.new('Coby', %w[coby@example.com], [], [], []),
106
+ ]
107
+
108
+ assert_equal expected, @gc.contacts
109
+ end
110
+
111
+ end
112
+
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gmail_contacts
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Eric Hodel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
14
+ YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
15
+ ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk
16
+ cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
17
+ FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76
18
+ LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J
19
+ U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm
20
+ Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY
21
+ mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd
22
+ g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh
23
+ sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
24
+ BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX
25
+ kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19
26
+ bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF
27
+ DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6
28
+ UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag
29
+ 14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v
30
+ x52qPcexcYZR7w==
31
+ -----END CERTIFICATE-----
32
+
33
+ date: 2009-04-08 00:00:00 -07:00
34
+ default_executable:
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: gdata
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: "1.0"
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ type: :runtime
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: "1.2"
55
+ version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: hoe
58
+ type: :development
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.12.1
65
+ version:
66
+ description: |-
67
+ Simple Gmail contacts extraction using GData.
68
+
69
+ gmail_contacts development was sponsored by AT&T Interactive.
70
+ email:
71
+ - drbrain@segment7.net
72
+ executables: []
73
+
74
+ extensions: []
75
+
76
+ extra_rdoc_files:
77
+ - History.txt
78
+ - Manifest.txt
79
+ - README.txt
80
+ files:
81
+ - .document
82
+ - History.txt
83
+ - Manifest.txt
84
+ - README.txt
85
+ - Rakefile
86
+ - lib/gmail_contacts.rb
87
+ - lib/gmail_contacts/test_stub.rb
88
+ - sample/authsub.rb
89
+ - test/test_gmail_contacts.rb
90
+ has_rdoc: true
91
+ homepage: http://seattlerb.rubyforge.org/gmail_contacts
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --main
97
+ - README.txt
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ version:
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: "0"
111
+ version:
112
+ requirements: []
113
+
114
+ rubyforge_project: seattlerb
115
+ rubygems_version: 1.3.1.2162
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Simple Gmail contacts extraction using GData
119
+ test_files:
120
+ - test/test_gmail_contacts.rb
metadata.gz.sig ADDED
Binary file