gmail_contacts 1.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/.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