rumble_bundle 0.1.1 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e0cbcb6413466b3ddec4ca3814ceb129224bee6
4
- data.tar.gz: 14e09388a179ac90ff1d7df78f261875e5ad618c
3
+ metadata.gz: ab912ac3d3ab1fa15544e0d5dde4421d1a9fb30c
4
+ data.tar.gz: '0479427cfff6e9e85a85d04917aaa9718f93c863'
5
5
  SHA512:
6
- metadata.gz: d89e2671e1b6577fbe99bec9951534b548852e381c97e29ccfed36e39067f1dcd4107b1775b0d15fdc9b609a7c1eecdb69b2e46df6b72379d11dfa5baabcc119
7
- data.tar.gz: 88ee8a990f923acf5826737c2eea0d44520ee42a01d96b0fb08d1fb636e396132ce4f104377910801712341dfe1566bc1edc608d3fa7f620e592228d2b5a488e
6
+ metadata.gz: 9675a0ac23f75668bbf7ec75f61d13635b7d224d4cd240f93240c129f9c3ddb7033bf8eb7d1b6218c75d513a72f8f12bf157b95002d32718262b99918aaa1941
7
+ data.tar.gz: 54f374f52c02574af06e8e06ea3649d5dc82173f68e9b8e3213168d8677d6ca6a789c0210e24003a782607a2141c28bbd53cbfee66a7533efa37681a7fec2820
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in rumble_bundle.gemspec
4
3
  gemspec
data/README.md CHANGED
@@ -27,3 +27,9 @@ Then follow the on-screen prompts. :)
27
27
  ## Contributing
28
28
 
29
29
  Bug reports and pull requests are welcome on GitHub at https://github.com/repromancer/rumble_bundle.
30
+
31
+ ## License
32
+
33
+ This is free and unencumbered software released into the public domain.
34
+
35
+ For more information, please refer to the included UNLICENSE.md or <https://unlicense.org>
data/bin/setup CHANGED
@@ -3,6 +3,4 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
6
+ bundle install
@@ -4,7 +4,8 @@ require 'nokogiri'
4
4
 
5
5
  require 'rumble_bundle'
6
6
 
7
- require_relative '../lib/rumble_bundle/cli'
8
- require_relative '../lib/rumble_bundle/scraper'
9
7
  require_relative '../lib/rumble_bundle/bundle'
10
- require_relative '../lib/rumble_bundle/product'
8
+ require_relative '../lib/rumble_bundle/tier'
9
+ require_relative '../lib/rumble_bundle/product'
10
+ require_relative '../lib/rumble_bundle/scraper'
11
+ require_relative '../lib/rumble_bundle/cli'
File without changes
@@ -1,6 +1,6 @@
1
1
  class RumbleBundle::Bundle
2
2
 
3
- attr_accessor :name, :tiers, :products, :charities, :total_msrp, :url
3
+ attr_accessor :name, :url, :tiers, :charities, :total_msrp
4
4
 
5
5
  @@all = []
6
6
 
@@ -8,22 +8,13 @@ class RumbleBundle::Bundle
8
8
  @@all
9
9
  end
10
10
 
11
- def initialize(data_hash)
12
- data_hash.each{|key, val| self.send("#{key}=", val)}
11
+ def initialize
13
12
  self.class.all << self
14
13
  end
15
14
 
16
-
17
-
18
- def products=(array)
19
-
20
- #associate incoming products with Bundle
21
- @products = array.tap do |products|
22
- products.each do |product|
23
- product.bundle = self
24
- end
25
- end
26
-
15
+ def products
16
+ # Leverage Bundle#tiers to expose a flat array of a Bundle's Products.
17
+ self.tiers.collect{|t| t.products}.flatten
27
18
  end
28
19
 
29
20
  end
@@ -1,5 +1,6 @@
1
1
  class RumbleBundle::CLI
2
2
 
3
+ # Scrape site and allow user to begin browsing.
3
4
  def start
4
5
  puts ""
5
6
  puts "Fetching data from HumbleBundle.com..."
@@ -12,6 +13,7 @@ class RumbleBundle::CLI
12
13
  end
13
14
 
14
15
 
16
+
15
17
  def query
16
18
 
17
19
  bundles = RumbleBundle::Bundle.all
@@ -21,22 +23,28 @@ class RumbleBundle::CLI
21
23
  puts "Enter 'quit' to leave the program."
22
24
  puts ""
23
25
 
26
+ # List Bundles in numbered order
24
27
  bundles.each.with_index(1) do |bundle, i|
25
28
  puts "[#{i}] #{bundle.name}"
26
29
  end
27
30
 
31
+ # Get user input
28
32
  puts ""
29
33
  print " "
30
34
  input = gets.strip.downcase
31
35
  puts ""
32
36
 
37
+ # If input is a valid number, display the corresponding Bundle
33
38
  if input.to_i.between?(1, bundles.length)
34
39
  display_bundle(bundles[input.to_i - 1])
40
+
41
+ # If input is an invalid number, restart #query
35
42
  elsif input.to_i < 0 || input.to_i > bundles.length
36
43
  puts ""
37
44
  puts "Sorry! Please enter a valid number or command."
38
45
  query
39
46
 
47
+ # Check for 'help' or 'quit' commands, else pass input to #filter
40
48
  else
41
49
  case input
42
50
  when 'help'
@@ -50,9 +58,14 @@ class RumbleBundle::CLI
50
58
 
51
59
  end
52
60
 
61
+
62
+
53
63
  def filter(input)
64
+ # Compare input to recognized tags
54
65
  filters = %w[drm-free steam-key windows mac linux android]
55
66
  valid = filters & input.split
67
+
68
+ # If input is valid, collect an array of all Products, remove any that do not match search tags, then display results in a formatted list
56
69
  if valid.any?
57
70
  filtered = RumbleBundle::Product.all.collect{|p| p}
58
71
  valid.each do |filter|
@@ -73,8 +86,13 @@ class RumbleBundle::CLI
73
86
  filtered.collect{|p| p.bundle }.uniq.each do |bundle|
74
87
  puts "#{bundle.name} (#{bundle.url})"
75
88
  puts ""
76
- filtered.each{|p| display_product(p) if p.bundle == bundle}
77
- puts ""
89
+ bundle.tiers.each do |tier|
90
+ if filtered.detect{|p| p.tier == tier}
91
+ puts " #{tier.description}", ""
92
+ filtered.each{|p| display_product(p) if p.tier == tier}
93
+ puts ""
94
+ end
95
+ end
78
96
  end
79
97
  puts "---------------------------------------------------------------"
80
98
  else
@@ -95,25 +113,24 @@ class RumbleBundle::CLI
95
113
 
96
114
  end
97
115
 
116
+
117
+
118
+ # Display formatted information for given Bundle
98
119
  def display_bundle(bundle)
99
120
  puts "---------------------------------------------------------------"
100
121
  puts "#{bundle.name} (#{bundle.url})"
101
122
  puts "#{bundle.total_msrp}!"
102
123
  puts ""
103
- puts "Supports:"
124
+ puts " Supports:"
104
125
  bundle.charities.each do |c|
105
- print " #{c}"
126
+ print " #{c}"
106
127
  puts ","
107
128
  end
108
- puts " or a Charity of Your Choice"
129
+ puts " or a Charity of Your Choice"
109
130
  puts "", ""
110
131
  bundle.tiers.each do |tier|
111
- puts tier, ""
112
- bundle.products.each do |product|
113
- if product.tier == tier
114
- display_product(product)
115
- end
116
- end
132
+ puts " #{tier.description}", ""
133
+ tier.products.each{|p| display_product(p)}
117
134
  puts ""
118
135
  end
119
136
  puts "---------------------------------------------------------------"
@@ -122,15 +139,16 @@ class RumbleBundle::CLI
122
139
  end
123
140
 
124
141
  def display_product(product)
125
- print " #{product.name}"
142
+ print " #{product.name}"
126
143
  print " (#{product.platforms.join(", ")})" if product.platforms.any?
127
144
  print " (DRM-Free!)" if product.drm_free
128
145
  print " (w/Steam Key!)" if product.steam_key
129
146
  puts ""
130
- puts " #{product.subtitle}" if product.subtitle
147
+ puts " #{product.subtitle}" if product.subtitle
131
148
  puts ""
132
149
  end
133
150
 
151
+ # Display help
134
152
  def help
135
153
  puts <<~HEREDOC
136
154
  ---------------------------------------------------------------
@@ -153,6 +171,7 @@ class RumbleBundle::CLI
153
171
  query
154
172
  end
155
173
 
174
+ # Quit
156
175
  def quit
157
176
  exit
158
177
  end
@@ -8,8 +8,7 @@ class RumbleBundle::Product
8
8
  @@all
9
9
  end
10
10
 
11
- def initialize(data_hash)
12
- data_hash.each{|key, val| self.send("#{key}=", val)}
11
+ def initialize
13
12
  self.class.all << self
14
13
  end
15
14
 
@@ -1,6 +1,7 @@
1
1
  class RumbleBundle::Scraper
2
2
 
3
3
  def initialize
4
+ # Set URLs used as starting points for scraping
4
5
  @base_url = 'https://www.humblebundle.com'
5
6
  @main_pages = [
6
7
  'https://www.humblebundle.com/',
@@ -11,15 +12,18 @@ class RumbleBundle::Scraper
11
12
 
12
13
 
13
14
  def crawl_site
14
-
15
+ # For each main page
15
16
  @main_pages.each do |page|
17
+ # scrape the bundle from that page
16
18
  first_page = Nokogiri::HTML(open(page))
17
19
  scrape_bundle(first_page, page)
18
20
 
21
+ # and if there are any sub-pages
19
22
  if first_page.at_css(".js-highlight.subtab-button")
20
23
  tab_bar = first_page.css(".js-highlight.subtab-button")
21
24
  links = tab_bar.collect{|a| @base_url + a.attr("href") }
22
25
 
26
+ # scrape them for bundles too
23
27
  links.each do |link|
24
28
  scrape_bundle(Nokogiri::HTML(open(link)), link)
25
29
  end
@@ -32,96 +36,96 @@ class RumbleBundle::Scraper
32
36
 
33
37
  def scrape_bundle(html, url)
34
38
 
35
- bundle = {
36
- 'name' => '',
37
- 'tiers' => [],
38
- 'products' => [],
39
- 'charities' => [],
40
- 'total_msrp' => '',
41
- 'url' => url
42
- }
39
+ RumbleBundle::Bundle.new.tap do |bundle|
43
40
 
44
- bundle['name'] = html.css("title").text.chomp("(pay what you want and help charity)").strip
41
+ bundle.url = url
45
42
 
46
- bundle['charities'] = html.css(".charity-image-wrapper img").collect{|img| img.attr("alt")}
43
+ # Scrape Bundle metadata
47
44
 
48
- #for each tier in bundle
49
- html.css(".main-content-row").each do |tier|
45
+ bundle.name = html.css("title").text.chomp("(pay what you want and help charity)").strip
50
46
 
51
- #add tier to Bundle @tiers array
52
- tier_name = tier.css(".dd-header-headline").text.strip
53
- if tier_name.downcase.include?("charity")
54
- break
55
- end
47
+ bundle.charities = html.css(".charity-image-wrapper img").collect{|img| img.attr("alt")}
56
48
 
57
- bundle['tiers'] << tier_name
49
+ bundle.total_msrp = html.css('.hr-tagline-text').detect{|e| e.text.include?("worth")}.text.strip
58
50
 
59
- #and instantiate products from tier
60
- tier.css(".game-boxes").each do |box|
61
- scrape_product(box, tier_name).tap do |product|
62
- bundle['products'] << product
63
- end
64
- end
65
51
 
66
- end
52
+ bundle.tiers = Array.new.tap do |bundle_tiers|
53
+
54
+ # For each 'main content row' on the page, instantiate a Tier with its Products and add that Tier to the Bundle's @tiers array
55
+ html.css(".main-content-row").each do |row|
56
+
57
+ row_title = row.css(".dd-header-headline").text.strip
67
58
 
68
- bundle['total_msrp'] = html.css('.hr-tagline-text').detect{|e| e.text.include?("worth")}.text.strip
59
+ # Stop iterating if scraper has reached a row which is not a Tier
60
+ if row_title.downcase.include?("charity")
61
+ break
62
+ end
69
63
 
70
- RumbleBundle::Bundle.new(bundle)
64
+ bundle_tiers << RumbleBundle::Tier.new.tap do |tier|
65
+ tier.description = row_title
66
+ tier.bundle = bundle
67
+ tier.products = Array.new.tap do |tier_products|
68
+ row.css(".game-boxes").each do |box|
69
+ scrape_product(box, bundle, tier).tap do |product|
70
+ tier_products << product
71
+ end
72
+ end
73
+ end
74
+ end
71
75
 
76
+ end
77
+
78
+ end
79
+
80
+ end
72
81
  end
73
82
 
74
83
 
84
+ # Self-explanatory
85
+ def scrape_product(box, bundle, tier)
75
86
 
76
- def scrape_product(box, tier)
87
+ RumbleBundle::Product.new.tap do |product|
77
88
 
78
- product = {
79
- 'name' => '',
80
- 'subtitle' => '',
81
- 'bundle' => '',
82
- 'tier' => '',
83
- 'platforms' => [],
84
- 'drm_free' => nil,
85
- 'steam_key' => nil
86
- }
89
+ product.bundle = bundle
90
+ product.tier = tier
87
91
 
88
- product['name'] = box.css(".dd-image-box-caption").text.strip
89
- product['tier'] = tier
90
- product['subtitle'] = if box.at_css(".subtitle")
91
- box.css(".subtitle .callout-msrp").remove
92
- if box.css(".subtitle").text.strip != ""
93
- box.css(".subtitle").text.strip
94
- else
95
- nil
96
- end
97
- end
92
+ product.name = box.css(".dd-image-box-caption").text.strip
98
93
 
99
- product['platforms'] = Array.new.tap do |platforms|
100
- if box.at_css(".dd-availability-icon > i.hb-android")
101
- platforms << 'Android'
94
+ product.subtitle = if box.at_css(".subtitle")
95
+ box.css(".subtitle .callout-msrp").remove
96
+ if box.css(".subtitle").text.strip != ""
97
+ box.css(".subtitle").text.strip
98
+ else
99
+ nil
100
+ end
102
101
  end
103
102
 
104
- if box.at_css(".dd-availability-icon > i.hb-linux")
105
- platforms << 'Linux'
106
- end
103
+ product.platforms = Array.new.tap do |platforms|
104
+ if box.at_css(".dd-availability-icon > i.hb-android")
105
+ platforms << 'Android'
106
+ end
107
107
 
108
- if box.at_css(".dd-availability-icon > i.hb-windows")
109
- platforms << 'Windows'
110
- end
108
+ if box.at_css(".dd-availability-icon > i.hb-linux")
109
+ platforms << 'Linux'
110
+ end
111
111
 
112
- if box.at_css(".dd-availability-icon > i.hb-osx")
113
- platforms << 'Mac'
114
- end
112
+ if box.at_css(".dd-availability-icon > i.hb-windows")
113
+ platforms << 'Windows'
114
+ end
115
115
 
116
- end
116
+ if box.at_css(".dd-availability-icon > i.hb-osx")
117
+ platforms << 'Mac'
118
+ end
117
119
 
118
- product['drm_free'] = box.at_css(".dd-availability-icon > i.hb-drmfree") ?
119
- true : false
120
+ end
121
+
122
+ product.drm_free = box.at_css(".dd-availability-icon > i.hb-drmfree") ?
123
+ true : false
120
124
 
121
- product['steam_key'] = box.at_css(".dd-availability-icon > i.hb-steam") ?
122
- true : false
125
+ product.steam_key = box.at_css(".dd-availability-icon > i.hb-steam") ?
126
+ true : false
123
127
 
124
- RumbleBundle::Product.new(product)
128
+ end
125
129
 
126
130
  end
127
131
 
@@ -0,0 +1,3 @@
1
+ class RumbleBundle::Tier
2
+ attr_accessor :bundle, :products, :description
3
+ end
@@ -1,3 +1,3 @@
1
1
  module RumbleBundle
2
- VERSION = "0.1.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
25
  f.match(%r{^(test|spec|features)/})
26
26
  end
27
- spec.bindir = "bin"
27
+ spec.bindir = "exe"
28
28
  spec.executables << 'rumble_bundle'
29
29
  spec.require_paths = ["lib"]
30
30
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rumble_bundle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Summers
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-30 00:00:00.000000000 Z
11
+ date: 2017-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -88,14 +88,15 @@ files:
88
88
  - Rakefile
89
89
  - UNLICENSE.md
90
90
  - bin/console
91
- - bin/rumble_bundle
92
91
  - bin/setup
93
92
  - config/environment.rb
93
+ - exe/rumble_bundle
94
94
  - lib/rumble_bundle.rb
95
95
  - lib/rumble_bundle/bundle.rb
96
96
  - lib/rumble_bundle/cli.rb
97
97
  - lib/rumble_bundle/product.rb
98
98
  - lib/rumble_bundle/scraper.rb
99
+ - lib/rumble_bundle/tier.rb
99
100
  - lib/rumble_bundle/version.rb
100
101
  - rumble_bundle.gemspec
101
102
  - spec.md