blythedunham-base4r 0.2.0.6 → 0.2.0.7

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.
@@ -0,0 +1,98 @@
1
+ # Base4R is a ruby interface to Google Base
2
+ # Copyright 2007, 2008 Dan Dukeson
3
+
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License along
15
+ # with this program; if not, write to the Free Software Foundation, Inc.,
16
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+
18
+ #
19
+ # Authenticates the user with google ClientLogin
20
+ #
21
+ #
22
+ require 'net/https'
23
+
24
+ module Base4R
25
+
26
+ #
27
+ # ClientLogin authenticates the user with Google Accounts using the ClientLogin service.
28
+ #
29
+ class ClientLogin
30
+
31
+ include Base4R::HTTPLogger
32
+ ServiceURL = URI.parse('https://www.google.com/accounts/ClientLogin')
33
+
34
+ # Create a ClientLogin. _options_ is optional and by default logs into
35
+ # the Google Base API.
36
+ def initialize(options={})
37
+
38
+ defaults = {:accountType => 'GOOGLE',
39
+ :service => 'gbase',
40
+ :source => 'base4r-clientloginapp-v1'}
41
+
42
+ @config = defaults.merge(options)
43
+ end
44
+
45
+ # attempt to authenticate _email_ and _password_ with Google.
46
+ # Returns an authentication token on success, raises Exception on failure.
47
+ # todo: make exception handling more useful
48
+ def authenticate(email, password)
49
+
50
+ params = {
51
+ 'accountType' => @config[:accountType],
52
+ 'Email' => email,
53
+ 'Passwd' => password,
54
+ 'service' => @config[:service],
55
+ 'source' => @config[:source]
56
+ }
57
+
58
+ req = Net::HTTP::Post.new(ServiceURL.path)
59
+ req.set_form_data(params)
60
+
61
+ http = Net::HTTP.new(ServiceURL.host, 443)
62
+ http.use_ssl = true
63
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
64
+ http.ca_file = File.expand_path(File.dirname(__FILE__)+"/../cert/cacert.pem")
65
+ # todo - above is probably reading file for every request, cache certs instead
66
+
67
+ log_request req
68
+ resp = http.request(req)
69
+ log_response resp
70
+
71
+ unless resp.instance_of? Net::HTTPOK then
72
+ resp.body =~ /^Error=(.+)$/
73
+
74
+ raise CaptchaRequiredException.new(email) if 'CaptchaRequired' == $1
75
+ raise 'unknown exception authenticating with google:'+$1
76
+ end
77
+
78
+ # parse the auth key from the body
79
+ resp.body =~ /^Auth=(.+)$/
80
+ return $1
81
+
82
+ end
83
+ end
84
+
85
+ # thrown when ClientLogin fails to authenticate because the ClientLogin API has
86
+ # requested that the user complete a captcha validation.
87
+ class CaptchaRequiredException < Exception
88
+
89
+ attr_reader :email
90
+
91
+ # Create a CaptchaRequiredException to notify the user that _email_ must complete
92
+ # a captcha validation
93
+ def initialize(email)
94
+ @email = email
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,32 @@
1
+ module Base4R
2
+ module HTTPLogger
3
+
4
+ def self.included(base)
5
+ base.extend self
6
+ end
7
+
8
+ def verbose?; false; end
9
+
10
+ def log(message)
11
+ return unless verbose?
12
+ STDOUT.puts message
13
+ end
14
+
15
+ def log_request(request, options={})
16
+ log "-------#{request.to_s}----------"
17
+ log "URL: #{options[:url]}" if options[:url]
18
+ log "METHOD: #{options[:method]}" if options[:method]
19
+
20
+ request.each_capitalized {|k, v| log "#{k}: #{v}" } if request.respond_to? :each_capitalized
21
+
22
+ log((options[:data] ? options[:data] : request.body).to_s.gsub('><',">\n<"))
23
+
24
+ log "-------#{request.to_s}----------"
25
+ end
26
+
27
+ def log_response(response)
28
+ log_request(response)
29
+ end
30
+
31
+ end
32
+ end
data/lib/item.rb ADDED
@@ -0,0 +1,290 @@
1
+ # Base4R is a ruby interface to Google Base
2
+ # Copyright 2007, 2008 Dan Dukeson
3
+
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License along
15
+ # with this program; if not, write to the Free Software Foundation, Inc.,
16
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+
18
+ require 'rexml/document'
19
+
20
+ module Base4R
21
+ module ItemAttributeMethods
22
+ def self.included(base)
23
+ base.send :include, InstanceMethods
24
+ base.send :extend, ClassMethods
25
+ end
26
+ module ClassMethods
27
+ #set the attribute definitions and define setters and getters
28
+ def define_attributes(options)
29
+ @attribute_definitions ||= {}
30
+ @attribute_definitions.update(options)
31
+ options.each {|k,v| define_setter_method(k, v)}
32
+ end
33
+
34
+ #define setter methods on the object
35
+ def define_setter_method(method, options={})
36
+ class_eval <<-EOS, __FILE__, __LINE__
37
+ def set_#{method}(value); add_attribute(:#{method}, value); end
38
+ alias_method "#{method}=", "set_#{method}"
39
+ EOS
40
+ end
41
+
42
+ # Define setter and getter methods. The example will define author_name and author_name=
43
+ # define_child_method :author, :name
44
+ def define_child_accessor(parent, child)
45
+ class_eval <<-EOF
46
+ def #{parent}_#{child}; child_value(:#{parent}, :#{child}); end
47
+ def #{parent}_#{child}=(v); set_child_value(:#{parent}, :#{child}, v); end
48
+ EOF
49
+ end
50
+
51
+ # return all actual and inherited definitions
52
+ def attribute_definitions#:nodoc
53
+ @all_attribute_definitions ||=
54
+ (superclass.respond_to?(:attribute_definitions) ?
55
+ superclass.attribute_definitions :
56
+ {}).merge(@attribute_definitions||{})
57
+ end
58
+
59
+ # return the attribute definition for a specific attribute
60
+ def attribute_definition(name)#:nodoc:
61
+ attribute_definitions[name.to_sym]
62
+ end
63
+ end
64
+ module InstanceMethods
65
+
66
+ def child_value(parent_name, child_name)
67
+ parent = get_attribute(parent_name)
68
+ child = parent.send(child_name) if parent
69
+ child.value if child
70
+ end
71
+
72
+ def set_child_value(parent_name, child_name, value)
73
+ parent = get_attribute(parent_name)||add_attribute(parent_name, nil)
74
+ parent.add_child(child_name, value)
75
+ end
76
+
77
+ # return the attribute objects that correspond to this element
78
+ def get_attributes(name); attributes.select {|a| a.name.to_s == name.to_s }; end
79
+ def get_attribute(name); attributes.detect {|a| a.name.to_s == name.to_s }; end
80
+
81
+ # set the google private flag for the specified attribute
82
+ # item.set_attribute_private :location
83
+ def set_attribute_private(name, is_private=true)
84
+ attributes.each { |attr| attr.private_attribute = is_private if name.to_s == attr.name.to_s }
85
+ end
86
+
87
+ # Add the attribute to the item
88
+ # item.add_attribute :price, :value => 5, :units => 'USD'
89
+ # item.add_attribute :item_type, 'Products'
90
+ def add_attribute(attribute_name, value)
91
+ # get the options passed in from the value
92
+ options = options_from_value(value)
93
+
94
+ attr_def = self.class.attribute_definition(attribute_name)||{}
95
+ attr_class = type_to_attribute_class(options[:type]||attr_def[:type]||:text)
96
+
97
+ # add a namespace param if there is one
98
+ options[:namespace] ||= attr_def[:namespace] if attr_def.has_key?(:namespace)
99
+
100
+ #raise options.inspect if attribute_name.to_s == 'customme'
101
+ # create a new attribute
102
+ attr = attr_class.new(attr_def[:name]||attribute_name, options)
103
+
104
+ @attributes << attr
105
+ attr
106
+ end
107
+ # convert type field to the corresponding Attribute class
108
+ # Example :text is AttributeText
109
+ def type_to_attribute_class(type_name)#:nodoc:
110
+ return type_name if type_name.is_a?(Attribute)
111
+
112
+ Base4R.const_get "#{type_name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase } }Attribute"
113
+ end
114
+ end
115
+ end
116
+
117
+
118
+ # Item describes a single entry that will be or is already stored in Google Base
119
+ class Item
120
+
121
+ include ItemAttributeMethods
122
+
123
+ # array of Attribute objects that describe the Item
124
+ attr_accessor :attributes
125
+
126
+ # Title of the Item
127
+ attr_accessor :title
128
+
129
+ # ID of this Item as assigned by Google
130
+ attr_accessor :base_id
131
+
132
+ # unique alphnumeric identifier for the item - e.g. your internal ID code.
133
+ # IMPORTANT: Once you submit an item with a unique id, this identifier must
134
+ # not change when you send in a new data feed. Each item must retain the same
135
+ # id in subsequent feeds.
136
+ attr_accessor :unique_id
137
+
138
+ attr_accessor :draft
139
+
140
+ define_child_accessor :author, :name
141
+ define_child_accessor :author, :email
142
+
143
+ def options_from_value(value, default_options={})#:nodoc:
144
+ default_options.merge(value.is_a?(Hash) ? value : {:value => value})
145
+ end
146
+
147
+ # Represents this Item as Atom XML which is the format required by the Base API.
148
+ def to_xml
149
+
150
+ doc = REXML::Document.new('<?xml version="1.0" ?>')
151
+
152
+ entry = doc.add_element 'entry',
153
+ 'xmlns'=>'http://www.w3.org/2005/Atom',
154
+ 'xmlns:g' => 'http://base.google.com/ns/1.0',
155
+ 'xmlns:app'=>'http://purl.org/atom/app#',
156
+ 'xmlns:gm'=>'http://base.google.com/ns-metadata/1.0'
157
+
158
+ #bjd: not sure why these are instance variables but others are not
159
+ #if @author_name || @author_email
160
+ # AuthorAttribute.to_xml(entry, :author, nil, :email => @author_email, :name => @author_name)
161
+ #end
162
+
163
+ entry.add_element 'category',
164
+ 'scheme'=>'http://www.google.com/type',
165
+ 'term' => 'googlebase.item'
166
+
167
+ if draft?
168
+ goog_control = entry.add_element('app:control', 'xmlns:app'=>'http://purl.org/atom/app#')
169
+ goog_control.add_element('app:draft').text = 'yes'
170
+ end
171
+
172
+ entry.add_element('title').text= @title
173
+
174
+ @attributes.each do |attr|
175
+ attr.to_xml(entry)
176
+ end
177
+
178
+ doc
179
+ end
180
+
181
+
182
+ def draft=(value)
183
+ @draft = value.is_a?(TrueClass) || value.to_s.downcase == 'yes'
184
+ end
185
+
186
+ def draft?; @draft; end
187
+ end
188
+
189
+
190
+ #
191
+ # Item with a minimal set of Attributes, extend for specific Item Types
192
+ #
193
+ #
194
+ class UniversalItem < Item
195
+ define_attributes(
196
+ :description => nil,
197
+ :contact_phone => nil,
198
+ :item_type => nil,
199
+ :item_language => nil,
200
+ :target_country => nil,
201
+ :application => nil,
202
+ :link => { :type => :url, :namespace => nil },
203
+ :expiration_date=> { :type => :dateTime },
204
+ :label => nil,
205
+ :unique_id => { :name => :id },
206
+ :author => { :value => nil, :namespace => nil, :type => :author },
207
+ :image_link => { :type => :bare },
208
+ :location => { :type => :location}
209
+ )
210
+
211
+ # Create a new UniversalItem, with _unique_id_, created by _author_name_ who's email is _author_email_,
212
+ # described by _description_, found at URL _link_, entitled _title_, phone number is
213
+ # _contact_phone_, item type is _item_type_, _target_country_ e.g. 'GB', _item_language_ e.g. 'EN'
214
+ #
215
+ # Args can also be a hash of attributes
216
+ def initialize(*args)
217
+ options = initialize_args_to_options(args)
218
+ #allow an option to specify which columns are private
219
+ private_attributes = [options.delete(:private_attributes)].flatten.inject([]){|list, val| list << val.to_s if val; list}.uniq
220
+
221
+ @title = options.delete(:title)
222
+ self.draft = options.delete(:draft)
223
+ @attributes = []
224
+
225
+ options.each do |key, value|
226
+ v = options_from_value(value, :private_attribute => private_attributes.include?(key.to_s))
227
+ #back support for author name and email
228
+ if value && [:author_name, :author_email].include?(key.to_sym)
229
+ send "#{key}=", value
230
+ else
231
+ add_attribute key, v
232
+ end
233
+ end
234
+ end
235
+
236
+ protected
237
+ # convert old style input args to new style
238
+ def initialize_args_to_options(args)#:nodoc:
239
+ options = if args.first.is_a?(Hash)
240
+ args.first.dup
241
+ else
242
+ %w(unique_id author_name author_email description link title contact_phone item_type target_country item_lang).inject({}) {|map, option|
243
+ map[option.to_sym] = args.shift
244
+ map
245
+ }
246
+ end
247
+ end
248
+
249
+ public
250
+
251
+ # Define for backwards compatibility
252
+ alias_method :add_image_link, :set_image_link
253
+ alias_method :add_label, :set_label
254
+ alias_method :context=, :set_description
255
+ alias_method :item_lang=, :set_item_language
256
+ end
257
+
258
+ # ProductItem is a standard item type. This class includes some of the attributes that are
259
+ # suggested or required for the Product item type.
260
+ #
261
+ class ProductItem < UniversalItem
262
+
263
+
264
+ define_attributes({
265
+ :condition => nil,
266
+ :will_deliver => {:type => :boolean },
267
+ :delivery_notes => nil,
268
+ :department => nil,
269
+ :payment => nil,
270
+ :payment_notes => nil,
271
+ :pickup => {:type => :boolean },
272
+ :price_type => nil,
273
+ :price_units => nil,
274
+ :price => {:type => :float_unit},
275
+ :image_like => { :type => :bare },
276
+ :quantity => { :type => :int }})
277
+
278
+ #rewrite to satisfy old style of price setting
279
+ # set_price 5, 'usd'
280
+ # set_price :value => 5, :units => 'usd'
281
+ def price(price_amount, price_units=nil)
282
+ add_attribute :price, :value => price_amount, :units => price_units
283
+ end
284
+
285
+ alias_method :add_payment, :payment=
286
+ alias_method :add_custom, :add_attribute
287
+ alias_method :add_custom_test, :add_attribute
288
+ end
289
+
290
+ end
@@ -0,0 +1,58 @@
1
+ # Base4R is a ruby interface to Google Base
2
+ # Copyright 2007 Dan Dukeson
3
+
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License along
15
+ # with this program; if not, write to the Free Software Foundation, Inc.,
16
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ #
18
+ # tests for Attribute and subclasses
19
+ #
20
+ require 'test/unit'
21
+ require 'attribute'
22
+
23
+ module Base4R
24
+
25
+ class AttributeTest < Test::Unit::TestCase #:nodoc:
26
+
27
+ def setup
28
+
29
+ @text_attr = TextAttribute.new('testname', 'testvalue', :g)
30
+ @url_attr = UrlAttribute.new('testname', 'testvalue', :g)
31
+ @date_time_attr = DateTimeAttribute.new('testname', 'testvalue', :g)
32
+ @int_attr = IntAttribute.new('testname', 'testvalue', :g)
33
+
34
+ end
35
+
36
+ def test_type_names
37
+ assert_equal 'text', @text_attr.type_name
38
+ assert_equal 'url', @url_attr.type_name
39
+ assert_equal 'dateTime', @date_time_attr.type_name
40
+ assert_equal 'int', @int_attr.type_name
41
+ end
42
+
43
+ def test_accessors
44
+
45
+ @text_attr.name = 'foo'
46
+ @text_attr.value = 'bar'
47
+ @text_attr.type_name = 'text'
48
+ @text_attr.namespace = :atom
49
+
50
+ assert_equal 'foo', @text_attr.name
51
+ assert_equal 'bar', @text_attr.value
52
+ assert_equal 'text',@text_attr.type_name
53
+ assert_equal :atom, @text_attr.namespace
54
+ end
55
+
56
+ end
57
+
58
+ end