amazon-ecs 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +25 -21
  2. data/lib/amazon/ecs.rb +67 -87
  3. data/test/amazon/ecs_test.rb +94 -80
  4. metadata +10 -9
data/README CHANGED
@@ -1,16 +1,18 @@
1
1
  == amazon-ecs
2
2
 
3
- Generic Product Advertising Ruby API using Hpricot. Uses Response and
3
+ Generic Product Advertising Ruby API using Nokogiri. Uses Response and
4
4
  Element wrapper classes for easy access to the REST API XML output.
5
5
 
6
6
  It is generic, so you can easily extend <tt>Amazon::Ecs</tt> to support
7
7
  other not implemented REST operations; and it is also generic because it just wraps around
8
- Hpricot element object, instead of providing one-to-one object/attributes to XML elements map.
8
+ Nokogiri element object, instead of providing one-to-one object/attributes to XML elements map.
9
9
 
10
10
  The idea is as the API evolves, there is a change in REST XML output structure,
11
11
  no updates will be required on <tt>amazon-ecs</tt> gem,
12
12
  instead you just need to update the element path.
13
13
 
14
+ For HPricot dependency implementation, please install 1.2.x version or checkout v1.2 branch.
15
+
14
16
  == INSTALLATION
15
17
 
16
18
  $ gem install amazon-ecs
@@ -48,45 +50,45 @@ instead you just need to update the element path.
48
50
  # traverse through each item (Amazon::Element)
49
51
  res.items.each do |item|
50
52
  # retrieve string value using XML path
51
- item.get('asin')
52
- item.get('itemattributes/title')
53
+ item.get('ASIN')
54
+ item.get('ItemAttributes/Title')
53
55
 
54
56
  # return Amazon::Element instance
55
- item_attributes = item.get_element('itemattributes')
56
- item_attributes.get('title')
57
+ item_attributes = item.get_element('ItemAttributes')
58
+ item_attributes.get('Title')
57
59
 
58
60
  # return first author or a string array of authors
59
- item_attributes.get('author') # 'Author 1'
60
- item_attributes.get_array('author') # ['Author 1', 'Author 2', ...]
61
+ item_attributes.get('Author') # 'Author 1'
62
+ item_attributes.get_array('Author') # ['Author 1', 'Author 2', ...]
61
63
 
62
64
  # return an hash of children text values with the element names as the keys
63
- item.get_hash('smallimage') # {:url => ..., :width => ..., :height => ...}
65
+ item.get_hash('SmallImage') # {:url => ..., :width => ..., :height => ...}
64
66
 
65
67
  # return the first matching path as Amazon::Element
66
- item_height = item.get_element('itemdimensions/height')
68
+ item_height = item.get_element('ItemDimensions/Height')
67
69
 
68
70
  # retrieve attributes from Amazon::Element
69
- item_height.attributes['units'] # 'hundredths-inches'
71
+ item_height.attributes['Units'] # 'hundredths-inches'
70
72
 
71
73
  # return an array of Amazon::Element
72
- authors = item.get_elements('author')
74
+ authors = item.get_elements('Author')
73
75
 
74
- # return Hpricot::Elements object or nil if not found
75
- reviews = item/'editorialreview'
76
+ # return Nokogiri::XML::NodeSet object or nil if not found
77
+ reviews = item/'EditorialReview'
76
78
 
77
- # traverse through Hpricot elements
79
+ # traverse through Nokogiri elements
78
80
  reviews.each do |review|
79
- # Getting hash value out of Hpricot element
81
+ # Getting hash value out of Nokogiri element
80
82
  Amazon::Element.get_hash(review) # [:source => ..., :content ==> ...]
81
83
 
82
84
  # Or to get unescaped HTML values
83
- Amazon::Element.get_unescaped(review, 'source')
84
- Amazon::Element.get_unescaped(review, 'content')
85
+ Amazon::Element.get_unescaped(review, 'Source')
86
+ Amazon::Element.get_unescaped(review, 'Content')
85
87
 
86
88
  # Or this way
87
89
  el = Amazon::Element.new(review)
88
- el.get_unescaped('source')
89
- el.get_unescaped('content')
90
+ el.get_unescaped('Source')
91
+ el.get_unescaped('Content')
90
92
  end
91
93
  end
92
94
 
@@ -121,8 +123,10 @@ http://www.awszone.com/scratchpads/aws/ecs.us/index.aws
121
123
 
122
124
  Thanks to Dan Milne (http://da.nmilne.com/) for the signed request patch.
123
125
 
126
+ Thanks to Bryan Housel (https://github.com/bhousel/amazon-ecs) for the initial Nokogiri patch
127
+
124
128
  == LICENSE
125
129
 
126
130
  (The MIT License)
127
131
 
128
- Copyright (c) 2010 Herryanto Siatono, http://www.siatono.com
132
+ Copyright (c) 2011 Herryanto Siatono, http://www.siatono.com
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009 Herryanto Siatono, Pluit Solutions
2
+ # Copyright (c) 2010 Herryanto Siatono
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -22,7 +22,7 @@
22
22
  #++
23
23
 
24
24
  require 'net/http'
25
- require 'hpricot'
25
+ require 'nokogiri'
26
26
  require 'cgi'
27
27
  require 'hmac-sha2'
28
28
  require 'base64'
@@ -121,19 +121,23 @@ module Amazon
121
121
 
122
122
  # Response object returned after a REST call to Amazon service.
123
123
  class Response
124
+
124
125
  # XML input is in string format
125
126
  def initialize(xml)
126
- @doc = Hpricot(xml)
127
+ @doc = Nokogiri::XML(xml)
128
+ @doc.remove_namespaces!
129
+ # @doc.xpath("//*").each { |elem| elem.name = elem.name.downcase }
130
+ # @doc.xpath("//@*").each { |att| att.name = att.name.downcase }
127
131
  end
128
132
 
129
- # Return Hpricot object.
133
+ # Return Nokogiri::XML::Document object.
130
134
  def doc
131
135
  @doc
132
136
  end
133
137
 
134
138
  # Return true if request is valid.
135
139
  def is_valid_request?
136
- (@doc/"isvalid").inner_html == "True"
140
+ Element.get(@doc, "//IsValid") == "True"
137
141
  end
138
142
 
139
143
  # Return true if response has an error.
@@ -143,20 +147,17 @@ module Amazon
143
147
 
144
148
  # Return error message.
145
149
  def error
146
- Element.get(@doc, "error/message")
150
+ Element.get(@doc, "//Error/Message")
147
151
  end
148
152
 
149
153
  # Return error code
150
154
  def error_code
151
- Element.get(@doc, "error/code")
155
+ Element.get(@doc, "//Error/Code")
152
156
  end
153
157
 
154
158
  # Return an array of Amazon::Element item objects.
155
159
  def items
156
- unless @items
157
- @items = (@doc/"item").collect {|item| Element.new(item)}
158
- end
159
- @items
160
+ @items ||= (@doc/"Item").collect { |item| Element.new(item) }
160
161
  end
161
162
 
162
163
  # Return the first item (Amazon::Element)
@@ -166,26 +167,17 @@ module Amazon
166
167
 
167
168
  # Return current page no if :item_page option is when initiating the request.
168
169
  def item_page
169
- unless @item_page
170
- @item_page = (@doc/"itemsearchrequest/itempage").inner_html.to_i
171
- end
172
- @item_page
170
+ @item_page ||= Element.get(@doc, "//ItemPage").to_i
173
171
  end
174
172
 
175
173
  # Return total results.
176
174
  def total_results
177
- unless @total_results
178
- @total_results = (@doc/"totalresults").inner_html.to_i
179
- end
180
- @total_results
175
+ @total_results ||= Element.get(@doc, "//TotalResults").to_i
181
176
  end
182
177
 
183
178
  # Return total pages.
184
179
  def total_pages
185
- unless @total_pages
186
- @total_pages = (@doc/"totalpages").inner_html.to_i
187
- end
188
- @total_pages
180
+ @total_pages ||= Element.get(@doc, "//TotalPages").to_i
189
181
  end
190
182
 
191
183
  def marshal_dump
@@ -272,35 +264,68 @@ module Amazon
272
264
  end
273
265
  end
274
266
 
275
- # Internal wrapper class to provide convenient method to access Hpricot element value.
267
+ # Internal wrapper class to provide convenient method to access Nokogiri element value.
276
268
  class Element
277
- # Pass Hpricot::Elements object
269
+ class << self
270
+ # Return the text value of an element.
271
+ def get(element, path='.')
272
+ return unless element
273
+ result = element.at_xpath(path)
274
+ result = result.inner_html if result
275
+ result
276
+ end
277
+
278
+ # Return an unescaped text value of an element.
279
+ def get_unescaped(element, path='.')
280
+ result = self.get(element, path)
281
+ CGI::unescapeHTML(result) if result
282
+ end
283
+
284
+ # Return an array of values based on the given path.
285
+ def get_array(element, path='.')
286
+ return unless element
287
+
288
+ result = element/path
289
+ if (result.is_a? Nokogiri::XML::NodeSet) || (result.is_a? Array)
290
+ result.collect { |item| self.get(item) }
291
+ else
292
+ [self.get(result)]
293
+ end
294
+ end
295
+
296
+ # Return child element text values of the given path.
297
+ def get_hash(element, path='.')
298
+ return unless element
299
+
300
+ result = element.at_xpath(path)
301
+ if result
302
+ hash = {}
303
+ result = result.children
304
+ result.each do |item|
305
+ hash[item.name] = item.inner_html
306
+ end
307
+ hash
308
+ end
309
+ end
310
+ end
311
+
312
+ # Pass Nokogiri::XML::Element object
278
313
  def initialize(element)
279
314
  @element = element
280
315
  end
281
316
 
282
- # Returns Hpricot::Elments object
317
+ # Returns Nokogiri::XML::Element object
283
318
  def elem
284
319
  @element
285
320
  end
286
321
 
287
- # Find Hpricot::Elements matching the given path. Example: element/"author".
322
+ # Returns a Nokogiri::XML::NodeSet of elements matching the given path. Example: element/"author".
288
323
  def /(path)
289
324
  elements = @element/path
290
325
  return nil if elements.size == 0
291
326
  elements
292
327
  end
293
-
294
- # Return an array of Amazon::Element matching the given path, or Amazon::Element if there
295
- # is only one element found.
296
- #
297
- # <b>DEPRECATED:</b> Please use <tt>get_elements</tt> and <tt>get_element</tt> instead.
298
- def search_and_convert(path)
299
- elements = self.get_elements(path)
300
- return elements.first if elements and elements.size == 1
301
- elements
302
- end
303
-
328
+
304
329
  # Return an array of Amazon::Element matching the given path
305
330
  def get_elements(path)
306
331
  elements = self./(path)
@@ -315,22 +340,22 @@ module Amazon
315
340
  end
316
341
 
317
342
  # Get the text value of the given path, leave empty to retrieve current element value.
318
- def get(path='')
343
+ def get(path='.')
319
344
  Element.get(@element, path)
320
345
  end
321
346
 
322
347
  # Get the unescaped HTML text of the given path.
323
- def get_unescaped(path='')
348
+ def get_unescaped(path='.')
324
349
  Element.get_unescaped(@element, path)
325
350
  end
326
351
 
327
352
  # Get the array values of the given path.
328
- def get_array(path='')
353
+ def get_array(path='.')
329
354
  Element.get_array(@element, path)
330
355
  end
331
356
 
332
357
  # Get the children element text values in hash format with the element names as the hash keys.
333
- def get_hash(path='')
358
+ def get_hash(path='.')
334
359
  Element.get_hash(@element, path)
335
360
  end
336
361
 
@@ -339,51 +364,6 @@ module Amazon
339
364
  self.elem.attributes
340
365
  end
341
366
 
342
- # Similar to #get, except an element object must be passed-in.
343
- def self.get(element, path='')
344
- return unless element
345
- result = element.at(path)
346
- result = result.inner_html if result
347
- result
348
- end
349
-
350
- # Similar to #get_unescaped, except an element object must be passed-in.
351
- def self.get_unescaped(element, path='')
352
- result = get(element, path)
353
- CGI::unescapeHTML(result) if result
354
- end
355
-
356
- # Similar to #get_array, except an element object must be passed-in.
357
- def self.get_array(element, path='')
358
- return unless element
359
-
360
- result = element/path
361
- if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
362
- parsed_result = []
363
- result.each {|item|
364
- parsed_result << Element.get(item)
365
- }
366
- parsed_result
367
- else
368
- [Element.get(result)]
369
- end
370
- end
371
-
372
- # Similar to #get_hash, except an element object must be passed-in.
373
- def self.get_hash(element, path='')
374
- return unless element
375
-
376
- result = element.at(path)
377
- if result
378
- hash = {}
379
- result = result.children
380
- result.each do |item|
381
- hash[item.name.to_sym] = item.inner_html
382
- end
383
- hash
384
- end
385
- end
386
-
387
367
  def to_s
388
368
  elem.to_s if elem
389
369
  end
@@ -19,54 +19,56 @@ class Amazon::EcsTest < Test::Unit::TestCase
19
19
  options[:aWS_secret_key] = AWS_SECRET_KEY
20
20
  end
21
21
 
22
- ## Test item_search
22
+ # Test item_search
23
23
  def test_item_search
24
- resp = Amazon::Ecs.item_search('ruby')
25
-
26
- assert(resp.is_valid_request?)
27
- assert(resp.total_results >= 3600)
28
- assert(resp.total_pages >= 360)
29
-
30
- signature_elements = (resp.doc/"arguments/argument").select do |ele|
31
- ele.attributes['name'] == 'Signature' || ele.attributes['Name'] == 'Signature'
32
- end.length
33
-
34
- assert(signature_elements == 1)
24
+ resp = Amazon::Ecs.item_search('ruby')
25
+ assert resp.is_valid_request?,
26
+ "Not a valid request"
27
+ assert (resp.total_results >= 3600),
28
+ "Results returned (#{resp.total_results}) were < 3600"
29
+ assert (resp.total_pages >= 360),
30
+ "Pages returned (#{resp.total_pages}) were < 360"
35
31
  end
36
-
32
+
37
33
  def test_item_search_with_special_characters
38
- Amazon::Ecs.debug = true
34
+ Amazon::Ecs.debug = true
39
35
  resp = Amazon::Ecs.item_search('()*&^%$')
40
- assert(resp.is_valid_request?)
36
+ assert resp.is_valid_request?,
37
+ "Not a valid request"
41
38
  end
42
-
39
+
43
40
  def test_item_search_with_paging
44
41
  resp = Amazon::Ecs.item_search('ruby', :item_page => 2)
45
- assert resp.is_valid_request?
46
- assert 2, resp.item_page
42
+ assert resp.is_valid_request?,
43
+ "Not a valid request"
44
+ assert_equal 2, resp.item_page,
45
+ "Page returned (#{resp.item_page}) different from expected (2)"
47
46
  end
48
-
47
+
49
48
  def test_item_search_with_invalid_request
50
49
  resp = Amazon::Ecs.item_search(nil)
51
- assert !resp.is_valid_request?
50
+ assert !resp.is_valid_request?,
51
+ "Expected invalid request error"
52
52
  end
53
-
53
+
54
54
  def test_item_search_with_no_result
55
- resp = Amazon::Ecs.item_search('afdsafds')
56
-
57
- assert resp.is_valid_request?
58
- assert_equal "We did not find any matches for your request.",
59
- resp.error
55
+ resp = Amazon::Ecs.item_search('afdsafds')
56
+ assert resp.is_valid_request?,
57
+ "Not a valid request"
58
+ assert_equal "We did not find any matches for your request.", resp.error,
59
+ "Error string different from expected"
60
60
  end
61
61
 
62
62
  def test_item_search_uk
63
63
  resp = Amazon::Ecs.item_search('ruby', :country => :uk)
64
- assert resp.is_valid_request?
64
+ assert resp.is_valid_request?,
65
+ "Not a valid request"
65
66
  end
66
67
 
67
68
  def test_item_search_by_author
68
69
  resp = Amazon::Ecs.item_search('dave', :type => :author)
69
- assert resp.is_valid_request?
70
+ assert resp.is_valid_request?,
71
+ "Not a valid request"
70
72
  end
71
73
 
72
74
  def test_item_get
@@ -75,96 +77,108 @@ class Amazon::EcsTest < Test::Unit::TestCase
75
77
 
76
78
  # test get
77
79
  assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
78
- item.get("itemattributes/title")
80
+ item.get("ItemAttributes/Title"),
81
+ "Title different from expected"
79
82
 
80
83
  # test get_array
81
84
  assert_equal ['Dave Thomas', 'Chad Fowler', 'Andy Hunt'],
82
- item.get_array("author")
83
-
85
+ item.get_array("Author"),
86
+ "Authors Array different from expected"
87
+
84
88
  # test get_hash
85
- small_image = item.get_hash("smallimage")
86
-
87
- assert_equal 3, small_image.keys.size
88
- assert_match ".jpg", small_image[:url]
89
- assert_equal "75", small_image[:height]
90
- assert_equal "59", small_image[:width]
89
+ small_image = item.get_hash("SmallImage")
91
90
 
91
+ assert_equal 3, small_image.keys.size,
92
+ "Image hash key count (#{small_image.keys.size}) different from expected (3)"
93
+ assert_match ".jpg", small_image['URL'],
94
+ "Image type different from expected (.jpg)"
95
+ assert_equal "75", small_image['Height'],
96
+ "Image height (#{small_image['Height']}) different from expected (75)"
97
+ assert_equal "59", small_image['Width'],
98
+ "Image width (#{small_image['Width']}) different from expected (59)"
99
+
92
100
  # test /
93
- reviews = item/"editorialreview"
101
+ reviews = item/"EditorialReview"
94
102
  reviews.each do |review|
95
- # returns unescaped HTML content, Hpricot escapes all text values
96
- assert Amazon::Element.get_unescaped(review, 'source')
97
- assert Amazon::Element.get_unescaped(review, 'content')
103
+ # returns unescaped HTML content, Nokogiri escapes all text values
104
+ assert Amazon::Element.get_unescaped(review, 'Source'),
105
+ "XPath editorialreview failed to get source"
106
+ assert Amazon::Element.get_unescaped(review, 'Content'),
107
+ "XPath editorialreview failed to get content"
98
108
  end
99
109
  end
100
-
110
+
101
111
  ## Test item_lookup
102
112
  def test_item_lookup
103
113
  resp = Amazon::Ecs.item_lookup('0974514055')
104
114
  assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
105
- resp.first_item.get("itemattributes/title")
115
+ resp.first_item.get("ItemAttributes/Title"),
116
+ "Title different from expected"
106
117
  end
107
118
 
108
119
  def test_item_lookup_with_invalid_request
109
120
  resp = Amazon::Ecs.item_lookup(nil)
110
- assert resp.has_error?
111
- assert resp.error
121
+ assert resp.has_error?,
122
+ "Response should have been invalid"
123
+ assert resp.error,
124
+ "Response should have contained an error"
112
125
  end
113
-
114
- def test_item_lookup_with_no_result
115
- resp = Amazon::Ecs.item_lookup('abc')
116
-
117
- assert resp.is_valid_request?
118
- assert_match(/ABC is not a valid value for ItemId/, resp.error)
119
- end
120
126
 
121
- def test_search_and_convert
122
- resp = Amazon::Ecs.item_lookup('0974514055')
123
- title = resp.first_item.get("itemattributes/title")
124
- authors = resp.first_item.search_and_convert("author")
125
-
126
- assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition", title
127
- assert authors.is_a?(Array)
128
- assert 3, authors.size
129
- assert_equal "Dave Thomas", authors.first.get
127
+ def test_item_lookup_with_no_result
128
+ resp = Amazon::Ecs.item_lookup('abc')
129
+ assert resp.is_valid_request?,
130
+ "Not a valid request"
131
+ assert_match /ABC is not a valid value for ItemId/, resp.error,
132
+ "Error Message for lookup of ASIN = ABC different from expected"
130
133
  end
131
134
 
132
135
  def test_get_elements
133
136
  resp = Amazon::Ecs.item_lookup('0974514055')
134
137
  item = resp.first_item
138
+
139
+ authors = item.get_elements("Author")
140
+ assert_instance_of Array, authors,
141
+ "Authors should be an Array"
142
+ assert_equal 3, authors.size,
143
+ "Author array size (#{authors.size}) different from expected (3)"
144
+ assert_instance_of Amazon::Element, authors.first,
145
+ "Authors array first element (#{authors.first.class}) should be an Amazon::Element"
146
+ assert_equal "Dave Thomas", authors.first.get,
147
+ "First Author (#{authors.first.get}) different from expected (Dave Thomas)"
135
148
 
136
- authors = item.get_elements("author")
137
- assert authors.is_a?(Array)
138
- assert 3, authors.size
139
- assert authors.first.is_a?(Amazon::Element)
140
- assert_equal "Dave Thomas", authors.first.get
141
-
142
- asin = item.get_elements("asin")
143
- assert asin.is_a?(Array)
144
- assert 1, authors.size
149
+ asin = item.get_elements("./ASIN")
150
+ assert_instance_of Array, asin,
151
+ "ASIN should be an Array"
152
+ assert_equal 1, asin.size,
153
+ "ASIN array size (#{asin.size}) different from expected (1)"
145
154
  end
146
155
 
147
156
  def test_get_element_and_attributes
148
157
  resp = Amazon::Ecs.item_lookup('0974514055')
149
158
  item = resp.first_item
150
-
151
- first_author = item.get_element("author")
152
- assert_equal "Dave Thomas", first_author.get
153
- assert_equal '', first_author.attributes['unknown']
159
+
160
+ first_author = item.get_element("Author")
161
+ assert_equal "Dave Thomas", first_author.get,
162
+ "First Author (#{first_author.get}) different from expected (Dave Thomas)"
163
+ assert_nil first_author.attributes['unknown'],
164
+ "First Author 'unknown' attributes should be nil"
154
165
 
155
- item_height = item.get_element("itemdimensions/height")
156
- assert_equal "hundredths-inches", item_height.attributes['units']
166
+ item_height = item.get_element("ItemDimensions/Height")
167
+ units = item_height.attributes['Units'].inner_html if item_height
168
+ assert_equal "hundredths-inches", units,
169
+ "Item Height 'units' attributes (#{units}) different from expected (hundredths-inches)"
157
170
  end
158
-
171
+
159
172
  def test_multibyte_search
160
173
  resp = Amazon::Ecs.item_search("パソコン")
161
- assert(resp.is_valid_request?)
174
+ assert resp.is_valid_request?,
175
+ "Not a valid request"
162
176
  end
163
-
177
+
164
178
  def test_marshal_dump_and_load
165
179
  resp = Amazon::Ecs::Response.new(File.read(File.expand_path('../../fixtures/item_search.xml', __FILE__)))
166
180
  dumped_resp = Marshal.load(Marshal.dump(resp))
167
-
181
+
168
182
  assert_equal resp.doc.to_s, dumped_resp.doc.to_s
169
183
  assert_equal resp.items.size, dumped_resp.items.size
170
184
  assert_equal resp.item_page, dumped_resp.item_page
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amazon-ecs
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
- - 1
8
7
  - 2
9
- - 2
10
- version: 1.2.2
8
+ - 0
9
+ - 0
10
+ version: 2.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Herryanto Siatono
@@ -15,22 +15,23 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-07 00:00:00 +08:00
18
+ date: 2011-05-09 00:00:00 +08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: hpricot
22
+ name: nokogiri
23
23
  prerelease: false
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- hash: 3
29
+ hash: 7
30
30
  segments:
31
- - 0
31
+ - 1
32
32
  - 4
33
- version: "0.4"
33
+ - 0
34
+ version: 1.4.0
34
35
  type: :runtime
35
36
  version_requirements: *id001
36
37
  - !ruby/object:Gem::Dependency