googlecontacts 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,10 @@
1
+ ## PROJECT::GENERAL
2
+ coverage
3
+ rdoc
4
+ pkg
5
+
6
+ ## PROJECT::SPECIFIC
7
+ .gitattributes
8
+ .rvmrc
9
+ script/console
10
+ notes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pieter Noordhuis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ *foo*
2
+
3
+
4
+
5
+
6
+ gc = GoogleContacts::Wrapper.new
7
+
8
+ gc.batch do
9
+ john = gc.contacts.find_by_email!('john@doe.com')
10
+
11
+ # Set this email as primary, overwriting other addresses
12
+ john.email = 'johnny@walker.com'
13
+
14
+ # return primary email address (string)
15
+ puts john.email
16
+
17
+ # Add email to the list
18
+ john.emails << 'fuckinghipster@hotmail.com'
19
+
20
+ john.emails.push('fuckinghipster@hotmail.com', attrs)
21
+
22
+ john.emails['fuckinghipster@hotmail.com'] = attrs
23
+
24
+ # this removes any existing label
25
+ john.emails['fuckinghipster@hotmail.com'].rel = 'blaat'
26
+
27
+ # this removes any existing rel
28
+ john.emails['fuckinghipster@hotmail.com'].label = 'personal use'
29
+
30
+ # access new email address. this can be used as a factory
31
+ # method to add new addresses
32
+ john.emails['thisonedidntexistyet@gmail.com'].primary!
33
+
34
+ john.emails.delete('thisonedidntexistyet@gmail.com')
35
+
36
+
37
+ [:rel] = 'foo'
38
+
39
+ # Set the last one as primary
40
+ john.emails.primary('fuckinghipster@hotmail.com')
41
+ end
@@ -0,0 +1,17 @@
1
+ = googlecontacts
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Pieter Noordhuis. See LICENSE for details.
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "googlecontacts"
8
+ gem.summary = %Q{Contacts API on steroids}
9
+ gem.description = %Q{Google Contacts API implementation}
10
+ gem.email = "pcnoordhuis@gmail.com"
11
+ gem.homepage = "http://github.com/pietern/googlecontacts"
12
+ gem.authors = ["Pieter Noordhuis"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_runtime_dependency "activesupport", ">= 2.3.4"
15
+ gem.add_runtime_dependency "nokogiri", ">= 1.4.1"
16
+ gem.add_runtime_dependency "oauth", ">= 0.3.6"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ spec.rcov_opts += ['--exclude', ENV['GEM_HOME']]
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "googlecontacts #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,15 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'oauth'
5
+ require 'nokogiri'
6
+
7
+ require 'google_contacts/auth'
8
+ require 'google_contacts/wrapper'
9
+ require 'google_contacts/base'
10
+ require 'google_contacts/contact'
11
+ require 'google_contacts/group'
12
+
13
+ require 'google_contacts/proxies/array'
14
+ require 'google_contacts/proxies/hash'
15
+ require 'google_contacts/proxies/emails'
@@ -0,0 +1,28 @@
1
+ module GoogleContacts
2
+ class Auth
3
+ GOOGLE_OAUTH = {
4
+ :site => 'https://www.google.com',
5
+ :request_token_path => '/accounts/OAuthGetRequestToken',
6
+ :authorize_path => '/accounts/OAuthAuthorizeToken',
7
+ :access_token_path => '/accounts/OAuthGetAccessToken',
8
+ }.freeze
9
+
10
+ class << self
11
+ attr_accessor :consumer_key
12
+ attr_accessor :consumer_secret
13
+ attr_accessor :callback_url
14
+ end
15
+
16
+ def self.consumer
17
+ ::OAuth::Consumer.new(consumer_key, consumer_secret, GOOGLE_OAUTH)
18
+ end
19
+
20
+ def request_token(options)
21
+ self.class.consumer.get_request_token({
22
+ :oauth_callback => options[:callback]
23
+ }, {
24
+ :scope => 'http://www.google.com/m8/feeds/'
25
+ })
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,163 @@
1
+ module GoogleContacts
2
+ class Base
3
+ NAMESPACES = {
4
+ 'atom' => 'http://www.w3.org/2005/Atom',
5
+ 'openSearch' => 'http://a9.com/-/spec/opensearch/1.1/',
6
+ 'gContact' => 'http://schemas.google.com/contact/2008',
7
+ 'batch' => 'http://schemas.google.com/gdata/batch',
8
+ 'gd' => 'http://schemas.google.com/g/2005',
9
+ }.freeze
10
+
11
+ # DEFAULT_NAMESPACE = 'http://www.w3.org/2005/Atom'.freeze
12
+
13
+ attr_reader :xml
14
+ def initialize(wrapper, xml = nil)
15
+ raise "Cannot create instance of Base" if self.class.name.split(/::/).last == 'Base'
16
+ @wrapper = wrapper
17
+ @xml = self.class.decorate_with_namespaces(xml || initialize_xml_document)
18
+ @proxies = {}
19
+ end
20
+
21
+ def self.namespace(node, prefix)
22
+ node.namespace_definitions.find do |ns|
23
+ ns.prefix == prefix
24
+ end
25
+ end
26
+
27
+ def self.insert_xml(parent, tag, attributes = {}, &blk)
28
+ # Construct new node with the right namespace
29
+ matches = tag.match /^((\w+):)?(\w+)$/
30
+ ns = matches[2] || 'atom'
31
+ tag = matches[3]
32
+ node = Nokogiri::XML::Node.new(tag, parent)
33
+ node.namespace = namespace(parent, ns) || raise("Unknown namespace: #{ns}")
34
+
35
+ attributes.each_pair do |k,v|
36
+ node[k.to_s] = v.to_s
37
+ end
38
+
39
+ parent << node
40
+ yield node if block_given?
41
+ node
42
+ end
43
+
44
+ def remove_xml(tag)
45
+ @xml.xpath(tag).remove
46
+ end
47
+
48
+ def insert_xml(tag, attributes = {}, &blk)
49
+ self.class.insert_xml(@xml, tag, attributes, &blk)
50
+ end
51
+
52
+ def self.feed_for_batch
53
+ xml = Nokogiri::XML::Document.new
54
+ xml.root = decorate_with_namespaces(Nokogiri::XML::Node.new('feed', xml))
55
+ xml.root
56
+ end
57
+
58
+ def xml_copy
59
+ doc = Nokogiri::XML::Document.new
60
+ doc.root = self.class.decorate_with_namespaces(xml.dup)
61
+ doc.root
62
+ end
63
+
64
+ # Create new XML::Document that can be used in a
65
+ # Google Contacts batch operation.
66
+ def entry_for_batch(operation)
67
+ doc = Nokogiri::XML::Document.new
68
+ doc.root = self.class.decorate_with_namespaces(xml.dup) # This automatically dups xml
69
+ doc.root.xpath('./xmlns:link' ).remove
70
+ doc.root.xpath('./xmlns:updated').remove
71
+
72
+ if operation == :update || operation == :destroy
73
+ doc.root.at('./xmlns:id').content = url(:edit)
74
+ end
75
+
76
+ self.class.insert_xml(doc.root, 'batch:id')
77
+ self.class.insert_xml(doc.root, 'batch:operation', :type => operation)
78
+
79
+ doc.root
80
+ end
81
+
82
+ def new?
83
+ at('id').nil?
84
+ end
85
+
86
+ def id
87
+ at('id').text.strip unless new?
88
+ end
89
+
90
+ def updated_at
91
+ Time.parse at('updated').text.strip unless new?
92
+ end
93
+
94
+ def url(rel)
95
+ rel = 'http://schemas.google.com/contacts/2008/rel#photo' if rel == :photo
96
+ at_xpath(%{xmlns:link[@rel="#{rel}"]})[:href]
97
+ end
98
+
99
+ def at(*args)
100
+ xml.at(*args)
101
+ end
102
+
103
+ def at_xpath(*args)
104
+ xml.at_xpath(*args)
105
+ end
106
+
107
+ def xpath(*args)
108
+ xml.xpath(*args)
109
+ end
110
+
111
+ def changed?
112
+ new? || @proxies.values.any?(&:changed?)
113
+ end
114
+
115
+ def save
116
+ return unless changed?
117
+ synchronize_proxies
118
+ @wrapper.save(self)
119
+ end
120
+
121
+ protected
122
+ def register_proxy(name, proxy)
123
+ @proxies[name.to_sym] = proxy
124
+ end
125
+
126
+ def synchronize_proxies
127
+ @proxies.values.map(&:synchronize)
128
+ end
129
+
130
+ # Try to proxy missing method to one of the proxies
131
+ def method_missing(sym, *args, &blk)
132
+ if sym.to_s =~ /^(\w+)(=)?$/ && @proxies[$1.to_sym]
133
+ if $2
134
+ @proxies[sym].replace(args.first)
135
+ else
136
+ @proxies[sym]
137
+ end
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ def initialize_xml_document
144
+ xml = Nokogiri::XML::Document.new
145
+ xml.root = Nokogiri::XML::Node.new('entry', xml)
146
+
147
+ category = Nokogiri::XML::Node.new('category', xml)
148
+ category['scheme'] = 'http://schemas.google.com/g/2005#kind'
149
+ category['term' ] = self.class.const_get(:CATEGORY_TERM)
150
+ xml.root << category
151
+
152
+ xml.root
153
+ end
154
+
155
+ def self.decorate_with_namespaces(node)
156
+ node.default_namespace = NAMESPACES['atom']
157
+ NAMESPACES.each_pair do |prefix, href|
158
+ node.add_namespace(prefix, href)
159
+ end
160
+ node
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,32 @@
1
+ module GoogleContacts
2
+ class Contact < Base
3
+ CATEGORY_TERM = 'http://schemas.google.com/contact/2008#contact'
4
+
5
+ # attr_reader :groups
6
+ def initialize(*args)
7
+ super
8
+
9
+ register_proxy :emails, Proxies::Emails.new(self)
10
+ register_proxy :groups, Proxies::Array.new(self,
11
+ :tag => 'gContact:groupMembershipInfo',
12
+ :attr => 'href')
13
+ register_proxy :properties, Proxies::Hash.new(self,
14
+ :tag => 'gd:extendedProperty',
15
+ :key => 'name',
16
+ :value => 'value')
17
+ end
18
+
19
+ def [](prop)
20
+ properties[prop]
21
+ end
22
+
23
+ def []=(prop, value)
24
+ properties[prop] = value
25
+ end
26
+
27
+ def email=(address)
28
+ emails[address].primary!
29
+ end
30
+
31
+ end # class Contact
32
+ end # module GoogleContacts
@@ -0,0 +1,9 @@
1
+ module GoogleContacts
2
+ class Group < Base
3
+ CATEGORY_TERM = 'http://schemas.google.com/g/2005#group'
4
+
5
+ def system_group?
6
+ @xml.xpath('.//gContact:systemGroup').size > 0
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module GoogleContacts
2
+ module Proxies
3
+ class Array < BlankSlate
4
+ def initialize(parent, options)
5
+ @parent = parent
6
+ @tag = options[:tag]
7
+ @attr = options[:attr]
8
+
9
+ reinitialize
10
+ end
11
+
12
+ def reinitialize
13
+ @current = @parent.xml.xpath("./#{@tag}").map do |entry|
14
+ entry[@attr]
15
+ end.compact.uniq.sort
16
+
17
+ # create a deep copy
18
+ @new = @current.map { |item| item.dup }
19
+ end
20
+
21
+ def changed?
22
+ @current != @new
23
+ end
24
+
25
+ def synchronize
26
+ @parent.remove_xml("./#{@tag}")
27
+ @new.each do |value|
28
+ @parent.insert_xml(@tag, { @attr => value })
29
+ end
30
+ end
31
+
32
+ def push(item)
33
+ item = item.href if item.respond_to?(:href)
34
+ method_missing(:push, item)
35
+ end
36
+ alias :<< :push
37
+
38
+ private
39
+ def method_missing(sym, *args, &blk)
40
+ ret = @new.send(sym, *args, &blk)
41
+ @new = @new.compact.uniq.sort
42
+ ret
43
+ end
44
+ end # class Array
45
+ end # module Proxies
46
+ end # module GoogleContacts