wedding_registry_scraper 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4574355a920b3e4e74e24c5515895efe2cb49113
4
- data.tar.gz: bd33c074a9562b32a6ca198b642951e64d87c8e6
3
+ metadata.gz: 1d95783063ce46d1a28af98b6c3330bd8d33ba95
4
+ data.tar.gz: 56298d31eba3630e7a1f28bfd4565f655f110708
5
5
  SHA512:
6
- metadata.gz: ffe729b27d8ecb0f3c4061a06d66d8f9fcda08e441e23850f9122aba1c670dd93b2907ed6f581c487032e205689b7572ddeb8cd4bec235f88518744fec82a017
7
- data.tar.gz: 7d358609b18515b869bd028e850426a921ee1d9659eab9a3884ab267d2c29f8158e9a5bfff0209b9dae1151d778e60889f7f37632104a028ec00ae4f78f61037
6
+ metadata.gz: 0f82473b27cf53ccdd6d000b2543256fe72ae7d35322e06a4eef9b71cf2b30bf780cf2a00d169adb7bbc38b0dc8e2a771f904768e02b85ac3caa125c1c433d14
7
+ data.tar.gz: b29414b56a8faab31165d8fece59f57b2a5bc568aae8ce9809ad8cfadd14149d98befab78b7fa8d388d58ad58ef0ab55bfcdf798af0783a1a63e5a776b6eeebd
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Andrew Pariser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Wedding Registry Scraper
2
+
3
+ ## Why?
4
+
5
+ Want to consolidate multiple registries onto one website?
6
+
7
+ Want to regularly update your fulfillment counts?
8
+
9
+ This (work in progress) is for you!
10
+
11
+ ## Which Registries?
12
+
13
+ Right now, works with:
14
+
15
+ * Zola — https://www.zola.com
16
+ * REI — http://www.rei.com
17
+ * Heath Ceramics — http://www.heathceramics.com
18
+ * Crate & Barrel — http://www.crateandbarrel.com
19
+ * Williams Sonoma — http://www.williams-sonoma.com
20
+
21
+ I intend to get this working with:
22
+
23
+ * Sur la Table
24
+ * Pottery Barn
25
+
26
+ ## Usage
27
+
28
+ Add to your Gemfile
29
+
30
+ gem 'wedding-registry-scraper'
31
+
32
+ Use `WeddingRegistryScraper#scrape`:
33
+
34
+ require 'wedding_registry_scraper'
35
+
36
+ WeddingRegistryScraper.scrape([
37
+ "http://www.heathceramics.com/giftregistry/view/index/id/YOUR_UNIQUE_REGISTRY_ID",
38
+ "http://www.crateandbarrel.com/Gift-Registry/YOUR_NAMES/YOUR_UNIQUE_REGISTRY_ID",
39
+ "https://www.zola.com/registry/YOUR_REGISTRY_SLUG",
40
+ "http://www.rei.com/GiftRegistryDetails/YOUR_UNIQUE_REGISTRY_ID",
41
+ "https://secure.williams-sonoma.com/registry/YOUR_UNIQUE_REGISTRY_ID/registry-list.html",
42
+ ])
43
+
44
+
45
+ ## Want to Help?
46
+
47
+ It should be simple to add another registry by cloning a `Regsitry` inside `lib/wedding_registry_scraper/registries`.
48
+
49
+ ## Like this project and planning on registering?
50
+
51
+ Why don't you register for Zola using my invite code: https://www.zola.com/invite/pariser
52
+
53
+ Thanks!
@@ -1,3 +1,6 @@
1
+ require 'active_support/all'
2
+ require_relative 'wedding_registry_scraper/version'
3
+
1
4
  module WeddingRegistryScraper
2
5
  autoload :Registry, 'wedding_registry_scraper/registry'
3
6
  autoload :Registries, 'wedding_registry_scraper/registries'
@@ -0,0 +1,28 @@
1
+ module WeddingRegistryScraper::Registries
2
+ class << self
3
+ def registry_classes
4
+ @registry_classes ||= begin
5
+ self.constants.map do |const|
6
+ obj = self.const_get(const)
7
+ obj if obj.is_a?(Class) && obj < WeddingRegistryScraper::Registry
8
+ end.compact
9
+ end
10
+ @registry_classes
11
+ end
12
+
13
+ def registry_class_from_url(url)
14
+ registry_classes.detect do |klass|
15
+ /^https?:\/\/[^\/]*#{klass.domain}/.match(url)
16
+ end
17
+ end
18
+
19
+ def initialize_registry(url, options={})
20
+ klass = registry_class_from_url(url)
21
+ klass ? klass.new(url, options) : nil
22
+ end
23
+ end
24
+ end
25
+
26
+ # Load all registries
27
+
28
+ Dir[File.dirname(__FILE__) + '/registries/*.rb'].each { |file| require file }
@@ -0,0 +1,45 @@
1
+ class WeddingRegistryScraper::Registries::CrateAndBarrel < WeddingRegistryScraper::Registry
2
+ @display_name = "Crate & Barrel"
3
+ @domain = "crateandbarrel.com"
4
+ private
5
+ def get_products(doc)
6
+ doc.css('.jsItemRow:not(.emptyCategoryRow)')
7
+ end
8
+
9
+ def get_name(product)
10
+ product.css('.itemTitle').text.strip
11
+ end
12
+
13
+ def get_sku(product)
14
+ sku = product.css('.skuNum').text.strip.match(/SKU (\S+)/)[1]
15
+ "c&b:#{sku}"
16
+ end
17
+
18
+ def get_url(product)
19
+ # TODO pop up modal? set proper anchor (they're not unique!)
20
+ @url
21
+ end
22
+
23
+ def get_image_url(product)
24
+ thumb_src = product.css('img')[0]['src']
25
+ thumb_src.sub(/\$web_itembasket\$/, '&$web_popup_zoom$&wid=379&hei=379')
26
+ end
27
+
28
+ def get_remaining(product)
29
+ desired = product.css('td')[4].css('.itemHas').text.strip.to_i
30
+ fulfilled = product.css('td')[5].css('.itemHas').text.strip.to_i
31
+ desired - fulfilled
32
+ end
33
+
34
+ def get_desired(product)
35
+ product.css('td')[4].css('.itemHas').text.strip.to_i
36
+ end
37
+
38
+ def get_price(product)
39
+ if (sale_price = product.css('.salePrice')).any?
40
+ /\$?(\d+(\.\d+)?)/.match(sale_price.text) { |match| match[1].to_f }
41
+ elsif (regular_price = product.css('.regPrice')).any?
42
+ /\$?(\d+(\.\d+)?)/.match(regular_price.text) { |match| match[1].to_f }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,90 @@
1
+ require 'uri'
2
+
3
+ ActiveSupport::Inflector.inflections(:en) do |inflector|
4
+ inflector.uncountable 'heath_ceramics'
5
+ end
6
+
7
+ class WeddingRegistryScraper::Registries::HeathCeramics < WeddingRegistryScraper::Registry
8
+ @display_name = "Heath Ceramics"
9
+ @domain = "heathceramics.com"
10
+ private
11
+ def get_products(doc)
12
+ doc.css('table#shopping-cart-table tbody tr')
13
+ end
14
+
15
+ def get_name(product)
16
+ product.css('.attentionText').text.strip
17
+ end
18
+
19
+ def get_sku(product)
20
+ text = product.css('.ctxProductCol .tinyText').map(&:text).detect { |t| t =~ /SKU/ }
21
+ sku = text.match(/SKU:\s+(\S+)/)[1]
22
+ "heath:#{sku}"
23
+ end
24
+
25
+ def get_url(product)
26
+ @url
27
+ end
28
+
29
+ def get_image_url(product)
30
+ details_url = product.css('[data-url]')[0]['data-url']
31
+
32
+ request_params = {
33
+ :headers => {
34
+ 'X-Requested-With' => 'XMLHttpRequest'
35
+ }
36
+ }
37
+
38
+ result = with_lax_heath_uri_parsing do
39
+ puts "GET #{details_url.inspect} with params #{request_params.to_json}" if @debug
40
+ Unirest.get(details_url, request_params)
41
+ end
42
+
43
+ doc = Nokogiri::HTML(result.body)
44
+
45
+ if doc.css('ul.mixMatchList').any?
46
+ doc.css('ul.mixMatchList li:last-child img[src]')[0]['src']
47
+ else
48
+ doc.css('.imagesScrollable img[src]')[0]['src']
49
+ end
50
+ end
51
+
52
+ def get_remaining(product)
53
+ fulfillment = product.css('.fulfilled').text.strip
54
+ fulfillment.match(/^(\d+)/)[0].to_i
55
+ end
56
+
57
+ def get_desired(product)
58
+ fulfillment = product.css('.fulfilled').text.strip
59
+ fulfillment.match(/(\d+)$/)[0].to_i
60
+ end
61
+
62
+ def get_price(product)
63
+ product.css('.price').text.strip.gsub(/[^\d\.]/, '').to_f
64
+ end
65
+
66
+ def with_lax_heath_uri_parsing(&block)
67
+ URI::RFC3986_Parser.class_eval do
68
+ alias_method :old_split, :split
69
+
70
+ def split(uri)
71
+ # scheme, userinfo, host, port, registry, path, opaque, query, fragment
72
+ [ "http", nil, "www.heathceramics.com", nil, nil, uri.sub(/^https?:\/\/www.heathceramics.com/, ''), nil, nil, nil ]
73
+ end
74
+ end
75
+
76
+ exception = nil
77
+ begin
78
+ result = yield
79
+ rescue Exception => e
80
+ exception = e
81
+ ensure
82
+ URI::RFC3986_Parser.class_eval do
83
+ alias_method :split, :old_split
84
+ end
85
+ end
86
+
87
+ raise exception if exception
88
+ result
89
+ end
90
+ end
@@ -0,0 +1,49 @@
1
+ class WeddingRegistryScraper::Registries::Rei < WeddingRegistryScraper::Registry
2
+ @display_name = "REI"
3
+ @domain = "rei.com"
4
+ private
5
+ def get_product_details_url(product)
6
+ href = product.css('a[name=prod]')[0]['href'].sub(/^\/?/, '')
7
+ "https://www.rei.com/#{href.sub(/^\//, '')}"
8
+ end
9
+
10
+ def get_products(doc)
11
+ doc.css('table.registryList').first.css('tr.tr0')
12
+ end
13
+
14
+ def get_name(product)
15
+ product.css('a[name=prod]').text.strip
16
+ end
17
+
18
+ def get_sku(product)
19
+ sku = get_product_details_url(product).match(/\/product\/(\d+)/)[1].to_i
20
+ "rei-#{sku}"
21
+ end
22
+
23
+ def get_url(product)
24
+ @url
25
+ end
26
+
27
+ def get_image_url(product)
28
+ details_url = get_product_details_url(product)
29
+
30
+ puts "GET #{details_url.inspect}" if @debug
31
+ result = Unirest.get(details_url)
32
+ doc = Nokogiri::HTML(result.body)
33
+
34
+ image_url = doc.css('#zoomLink')[0]['href']
35
+ "https://www.rei.com/#{image_url.sub(/^\//,'')}"
36
+ end
37
+
38
+ def get_remaining(product)
39
+ product.css('td')[5].text.strip.to_i
40
+ end
41
+
42
+ def get_desired(product)
43
+ product.css('td')[4].text.strip.to_i
44
+ end
45
+
46
+ def get_price(product)
47
+ product.css('td')[3].text.strip.sub('$','').to_f
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ class WeddingRegistryScraper::Registries::WilliamsSonoma < WeddingRegistryScraper::Registry
2
+ @display_name = "Williams-Sonoma"
3
+ @domain = "williams-sonoma.com"
4
+ private
5
+ def get_products(doc)
6
+ doc.css('table.registry-category-list tbody tr')
7
+ end
8
+
9
+ def get_name(product)
10
+ product.css('.product-detail .product-info .title a').text.strip
11
+ end
12
+
13
+ def get_sku(product)
14
+ sku = product.css('.product-detail .product-info .item-number').text.strip.match(/: (\d+)/)[1]
15
+ "williams-sonoma:#{sku}"
16
+ end
17
+
18
+ def get_url(product)
19
+ # product.css('.product-detail .product-info .title a')[0]['href']
20
+ @url
21
+ end
22
+
23
+ def get_image_url(product)
24
+ product.css('img')[0]['src'].sub(/f\.jpg$/, 'c.jpg')
25
+ end
26
+
27
+ def get_remaining(product)
28
+ product.css('td.still-needs').text.strip.to_i
29
+ end
30
+
31
+ def get_desired(product)
32
+ product.css('td.requested').text.strip.to_i
33
+ end
34
+
35
+ def get_price(product)
36
+ if (sale_price = product.css('td.price .price-state.price-special')).any?
37
+ sale_price.css('.currencyUSD .price-amount').text.strip.to_f
38
+ else
39
+ product.css('td.price .price-state.price-standard .currencyUSD .price-amount').text.strip.to_f
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ class WeddingRegistryScraper::Registries::Zola < WeddingRegistryScraper::Registry
2
+ @display_name = "Zola"
3
+ @domain = "zola.com"
4
+ private
5
+ def get_products(doc)
6
+ doc.css('#all-panel .product-tile')
7
+ end
8
+
9
+ def get_name(product)
10
+ product.css('.single-product-name').text.strip
11
+ end
12
+
13
+ def get_sku(product)
14
+ sku = product.css('.single-product')[0]['id']
15
+ "zola:#{sku}"
16
+ end
17
+
18
+ def get_url(product)
19
+ href = product.css('.content a')[0]['href'].sub(/^\/?/, '')
20
+ "https://www.zola.com/#{href}"
21
+ end
22
+
23
+ def get_image_url(product)
24
+ product.css('[data-image-url]')[0]['data-image-url']
25
+ end
26
+
27
+ def get_remaining(product)
28
+ product.css('.needed').text.strip.gsub(/[^\d]+/, '').to_i
29
+ end
30
+
31
+ def get_desired(product)
32
+ end
33
+
34
+ def get_price(product)
35
+ product.css('[data-price]')[0]['data-price'].gsub(/[$,]/, '').to_f
36
+ end
37
+
38
+ def price_type(product)
39
+ product_price = product.css('.product-price')
40
+
41
+ if product_price.length > 0 && product_price[0].text.strip == 'Contribute what you wish'
42
+ WeddingRegistryScraper::Registry::VARIABLE_PRICE
43
+ else
44
+ WeddingRegistryScraper::Registry::FIXED_PRICE
45
+ end
46
+ end
47
+
48
+ def fulfilled?(product)
49
+ if price_type(product) == WeddingRegistryScraper::Registry::VARIABLE_PRICE
50
+ get_price(product) <= 0
51
+ else
52
+ get_remaining(product) <= 0
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,120 @@
1
+ require 'unirest'
2
+ require 'nokogiri'
3
+
4
+ class WeddingRegistryScraper::Registry
5
+ PRICE_TYPES = [
6
+ FIXED_PRICE = 'Fixed price',
7
+ VARIABLE_PRICE = 'Variable price',
8
+ ]
9
+
10
+ class << self
11
+ attr_reader :display_name
12
+ attr_reader :domain
13
+ end
14
+
15
+ def initialize(url, params={})
16
+ params.symbolize_keys!
17
+ @url = url
18
+ @debug = params[:debug] == true
19
+ end
20
+
21
+ def get_items
22
+ doc = get_registry
23
+
24
+ get_products(doc).reduce({}) do |products, product|
25
+ sku = get_sku(product)
26
+
27
+ details = {
28
+ :name => get_name(product),
29
+ :remaining => get_remaining(product),
30
+ :desired => get_desired(product),
31
+ :url => get_url(product),
32
+ :image_url => get_image_url(product),
33
+ :registry_name => self.class.display_name,
34
+ :fulfilled => fulfilled?(product),
35
+ :price_type => price_type(product),
36
+ :price_value => get_price(product),
37
+ }
38
+
39
+ products.merge! sku => details
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def get_products(doc) ; raise NotImplementedError; end
46
+
47
+ def get_name(product) ; raise NotImplementedError; end
48
+ def get_sku(product) ; raise NotImplementedError; end
49
+ def get_url(product) ; raise NotImplementedError; end
50
+ def get_image_url(product) ; raise NotImplementedError; end
51
+ def get_remaining(product) ; raise NotImplementedError; end
52
+ def get_desired(product) ; raise NotImplementedError; end
53
+ def get_price(product) ; raise NotImplementedError; end
54
+
55
+ def price_type(product)
56
+ FIXED_PRICE
57
+ end
58
+
59
+ def fulfilled?(product)
60
+ get_remaining(product) == 0
61
+ end
62
+
63
+ def get_registry
64
+ result, _ = make_request(:get, @url)
65
+ Nokogiri::HTML(result.body)
66
+ end
67
+
68
+ def make_request(method, url, params={})
69
+ json = params.delete(:json)
70
+ request_params = {}
71
+
72
+ if cookies = params.delete(:cookies)
73
+ request_params[:headers] = {
74
+ 'Cookie' => dump_cookies(cookies)
75
+ }
76
+ end
77
+
78
+ if json
79
+ request_params[:headers] = {
80
+ 'Accept' => "application/json"
81
+ }
82
+ end
83
+
84
+ request_params[:parameters] = json ? params.to_json : params
85
+
86
+ puts "#{method.to_s.upcase} #{url} with params #{request_params.to_json}" if @debug
87
+ result = Unirest.send(method, url, request_params)
88
+
89
+ puts "RESULT #{result.code} with headers #{result.headers.inspect}" if @debug
90
+
91
+ open_result_in_browser(result) if @debug
92
+
93
+ cookies = load_cookies(result.headers[:set_cookie])
94
+
95
+ [ result, cookies ]
96
+ end
97
+
98
+ def open_result_in_browser(result)
99
+ file = Tempfile.new(['weddding-scraper','.html'])
100
+ file << result.body
101
+ file.close
102
+
103
+ `open "#{file.path}"`
104
+
105
+ sleep 1
106
+ file.unlink
107
+ end
108
+
109
+ def dump_cookies(cookies)
110
+ cookies.map { |k, v| "#{k}=#{v}" }.join("; ")
111
+ end
112
+
113
+ def load_cookies(set_cookie)
114
+ set_cookie ||= []
115
+ set_cookie.reduce({}) do |cookies, cookie|
116
+ key, value = cookie.split(';')[0].split('=')
117
+ cookies.merge!(key => value || "")
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,21 @@
1
+ class WeddingRegistryScraper::RegistryScraper
2
+ class << self
3
+ def scrape(registry_urls=[], options={})
4
+ all_products = {}
5
+
6
+ registry_urls.each do |url|
7
+ unless registry = WeddingRegistryScraper::Registries.initialize_registry(url)
8
+ raise "Could not initialize registry from url #{url.inspect}"
9
+ end
10
+
11
+ puts "* Loading items from #{registry.class.name.demodulize}..." if options[:log_messages]
12
+ products = registry.get_items
13
+ puts " Loaded %d items\n" % products.count if options[:log_messages]
14
+
15
+ all_products.merge!(products)
16
+ end
17
+
18
+ all_products
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module WeddingRegistryScraper
2
+ VERSION = Gem::Version.new("0.0.2")
3
+
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wedding_registry_scraper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Pariser
@@ -44,48 +44,20 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.6'
47
- - !ruby/object:Gem::Dependency
48
- name: colored
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.2'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '1.2'
61
- - !ruby/object:Gem::Dependency
62
- name: safe_yaml
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '1.0'
68
- type: :runtime
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '1.0'
75
47
  - !ruby/object:Gem::Dependency
76
48
  name: activesupport
77
49
  requirement: !ruby/object:Gem::Requirement
78
50
  requirements:
79
51
  - - "~>"
80
52
  - !ruby/object:Gem::Version
81
- version: '4.0'
53
+ version: '4.2'
82
54
  type: :runtime
83
55
  prerelease: false
84
56
  version_requirements: !ruby/object:Gem::Requirement
85
57
  requirements:
86
58
  - - "~>"
87
59
  - !ruby/object:Gem::Version
88
- version: '4.0'
60
+ version: '4.2'
89
61
  description: Look at a bunch of various retailers' wedding registries and consolidate
90
62
  information in one place.
91
63
  email: pariser@gmail.com
@@ -93,7 +65,18 @@ executables: []
93
65
  extensions: []
94
66
  extra_rdoc_files: []
95
67
  files:
68
+ - LICENSE
69
+ - README.md
96
70
  - lib/wedding_registry_scraper.rb
71
+ - lib/wedding_registry_scraper/registries.rb
72
+ - lib/wedding_registry_scraper/registries/crate_and_barrel.rb
73
+ - lib/wedding_registry_scraper/registries/heath_ceramics.rb
74
+ - lib/wedding_registry_scraper/registries/rei.rb
75
+ - lib/wedding_registry_scraper/registries/williams_sonoma.rb
76
+ - lib/wedding_registry_scraper/registries/zola.rb
77
+ - lib/wedding_registry_scraper/registry.rb
78
+ - lib/wedding_registry_scraper/registry_scraper.rb
79
+ - lib/wedding_registry_scraper/version.rb
97
80
  homepage: https://github.com/pariser/wedding-registry-scraper
98
81
  licenses:
99
82
  - MIT
@@ -106,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
89
  requirements:
107
90
  - - ">="
108
91
  - !ruby/object:Gem::Version
109
- version: '0'
92
+ version: '2.2'
110
93
  required_rubygems_version: !ruby/object:Gem::Requirement
111
94
  requirements:
112
95
  - - ">="