marvel_101 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/marvel_101 +6 -0
- data/bin/setup +8 -0
- data/fixtures/avengers.html +1586 -0
- data/fixtures/defenders.html +1437 -0
- data/fixtures/featured.html +2425 -0
- data/fixtures/gambit.html +1347 -0
- data/fixtures/heroes.html +909 -0
- data/fixtures/inhumans.html +1366 -0
- data/fixtures/nova.html +1388 -0
- data/fixtures/teams.html +882 -0
- data/fixtures/thor.html +1532 -0
- data/lib/marvel_101/character.rb +36 -0
- data/lib/marvel_101/cli.rb +85 -0
- data/lib/marvel_101/list.rb +14 -0
- data/lib/marvel_101/scraper.rb +88 -0
- data/lib/marvel_101/team.rb +21 -0
- data/lib/marvel_101/topic.rb +60 -0
- data/lib/marvel_101/version.rb +3 -0
- data/lib/marvel_101.rb +11 -0
- data/marvel_101.gemspec +28 -0
- data/spec.md +11 -0
- metadata +120 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'topic'
|
2
|
+
|
3
|
+
class Marvel101::Character < Marvel101::Topic
|
4
|
+
|
5
|
+
attr_accessor :list, :team, :details
|
6
|
+
|
7
|
+
DETAIL_ORDER = [:real_name, :height, :weight, :abilities, :powers,
|
8
|
+
:group_affiliations, :first_appearance, :origin]
|
9
|
+
|
10
|
+
def display
|
11
|
+
display_description
|
12
|
+
display_details
|
13
|
+
display_links
|
14
|
+
display_empty_message if no_info?
|
15
|
+
end
|
16
|
+
|
17
|
+
def display_details
|
18
|
+
DETAIL_ORDER.each do |type|
|
19
|
+
title = type.to_s.split("_").join(" ").upcase
|
20
|
+
puts "#{title}: #{details[type]}" if details.include?(type)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def display_empty_message
|
25
|
+
puts "Sorry, Marvel doesn't seem to care about #{name}"
|
26
|
+
puts "Type 'source' to open source in browser, but don't get your hopes up"
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_info?
|
30
|
+
!description && details.empty? && urls.size <= 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_input?(input)
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Marvel101::CLI
|
2
|
+
|
3
|
+
SOURCE = "http://marvel.com/characters/"
|
4
|
+
|
5
|
+
STARTING_PAGES = [
|
6
|
+
["Popular Teams", "list/997/titanic_teams"],
|
7
|
+
["Popular Heroes", "list/994/top_marvel_heroes"],
|
8
|
+
["Popular Villains", "list/995/bring_on_the_bad_guys"],
|
9
|
+
["Featured Characters", "browse"],
|
10
|
+
["The Women of Marvel", "list/996/women_of_marvel"]
|
11
|
+
]
|
12
|
+
|
13
|
+
def call
|
14
|
+
puts "\nWelcome to Marvel 101!"
|
15
|
+
main_menu
|
16
|
+
end
|
17
|
+
|
18
|
+
def main_menu
|
19
|
+
display_main
|
20
|
+
input = gets.chomp.downcase
|
21
|
+
if input.to_i.between?(1, STARTING_PAGES.size)
|
22
|
+
name, url = STARTING_PAGES[input.to_i - 1]
|
23
|
+
topic_menu(Marvel101::List.find_or_create_by_name(name, SOURCE + url))
|
24
|
+
elsif input == "exit" || input == "e"
|
25
|
+
exit_message
|
26
|
+
else
|
27
|
+
error("main")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def topic_menu(topic)
|
32
|
+
topic.get_info unless topic.scraped
|
33
|
+
display_topic(topic)
|
34
|
+
input = gets.chomp.downcase
|
35
|
+
case input
|
36
|
+
when "101","wiki" then open_link("url_#{input}".to_sym, topic)
|
37
|
+
when "source" then open_link(:url, topic)
|
38
|
+
when "e","exit" then exit_message
|
39
|
+
when "m","main" then main_menu
|
40
|
+
when "l","list" then topic.list? ? error(topic) : topic_menu(topic.list)
|
41
|
+
when "t","team" then topic.has_team? ? topic_menu(topic.team) : error(topic)
|
42
|
+
else
|
43
|
+
output = topic.valid_input?(input.to_i)
|
44
|
+
output ? topic_menu(output) : error(topic)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def display_main
|
49
|
+
puts "\n" + "-" * 80
|
50
|
+
puts "Here are your primary options:"
|
51
|
+
STARTING_PAGES.each.with_index(1) {|page, idx| puts "#{idx}. #{page[0]}!"}
|
52
|
+
puts "-" * 80
|
53
|
+
puts "You can also enter (E)xit to... exit"
|
54
|
+
puts "Select a number from the options above and we'll get started!"
|
55
|
+
end
|
56
|
+
|
57
|
+
def display_topic(topic)
|
58
|
+
break_len = (80 - topic.name.size) / 2
|
59
|
+
puts "\n" + "-" * break_len + "#{topic.name}" + "-" * break_len
|
60
|
+
topic.display
|
61
|
+
puts "-" * (break_len * 2 + topic.name.size)
|
62
|
+
options_message(topic)
|
63
|
+
end
|
64
|
+
|
65
|
+
def open_link(url, topic)
|
66
|
+
Launchy.open(topic.urls[url]) if topic.urls.include?(url)
|
67
|
+
topic_menu(topic)
|
68
|
+
end
|
69
|
+
|
70
|
+
def exit_message
|
71
|
+
puts "\nOh ok, well have a super day!"
|
72
|
+
end
|
73
|
+
|
74
|
+
def options_message(topic)
|
75
|
+
puts "Enter an option number for more info!" if topic.takes_input?
|
76
|
+
puts "You can enter (M)ain to go back to the main menu or (E)xit to... exit"
|
77
|
+
puts "Type (L)ist to return to #{topic.list.name} menu" if !topic.list?
|
78
|
+
puts "Type (T)eam to return to #{topic.team.name} menu" if topic.has_team?
|
79
|
+
end
|
80
|
+
|
81
|
+
def error(subject)
|
82
|
+
puts "\nSorry, that wasn't a valid option. Let's try again."
|
83
|
+
subject == "main" ? main_menu : topic_menu(subject)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'topic'
|
2
|
+
|
3
|
+
class Marvel101::List < Marvel101::Topic
|
4
|
+
|
5
|
+
attr_accessor :items
|
6
|
+
|
7
|
+
def display
|
8
|
+
items.each.with_index(1) {|item, idx| puts "#{idx}. #{item.name}"}
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_input?(input)
|
12
|
+
items[input - 1] if input.between?(1, items.size)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Marvel101::Scraper
|
2
|
+
|
3
|
+
attr_accessor :topic, :doc
|
4
|
+
|
5
|
+
def initialize(topic)
|
6
|
+
@topic = topic
|
7
|
+
@url = topic.urls[:url]
|
8
|
+
end
|
9
|
+
|
10
|
+
def scrape_list
|
11
|
+
get_doc
|
12
|
+
get_items(get_item_cards)
|
13
|
+
end
|
14
|
+
|
15
|
+
def scrape_topic
|
16
|
+
get_doc
|
17
|
+
get_description
|
18
|
+
topic.team? ? get_members : get_details
|
19
|
+
get_101
|
20
|
+
get_wiki
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_doc
|
24
|
+
@doc = Nokogiri::HTML(open(@url))
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_item_cards
|
28
|
+
item_cards = doc.css("div#comicsListing div.row-item")
|
29
|
+
item_cards.empty? ? doc.css("#featured-chars div.row-item") : item_cards
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_items(item_cards)
|
33
|
+
topic.items = item_cards.css("div.row-item-text > h5 > a").collect do |link|
|
34
|
+
name, url = link.text.strip, "http:#{link.attr("href")}"
|
35
|
+
if @url.downcase.include?("team")
|
36
|
+
Marvel101::Team.find_or_create_by_name("The #{name}", url).tap do |team|
|
37
|
+
team.list = topic
|
38
|
+
end
|
39
|
+
else
|
40
|
+
Marvel101::Character.find_or_create_by_name(name, url).tap do |char|
|
41
|
+
char.list = topic
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_description
|
48
|
+
info = doc.css("div.featured-item-desc p:nth-child(2)").text
|
49
|
+
unless info.strip.empty?
|
50
|
+
topic.description = info.gsub(/\r?\n\s*([ml][oe][rs][es])?/," ").strip
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_members
|
55
|
+
members_grid = doc.css("div.grid-container").first
|
56
|
+
topic.members = members_grid.css("div.row-item").collect do |card|
|
57
|
+
name = card.css("a.meta-title").text.strip
|
58
|
+
url = "http:#{card.css("a.meta-title").attr("href").value}"
|
59
|
+
Marvel101::Character.find_or_create_by_name(name, url).tap do |member|
|
60
|
+
member.list, member.team = topic.list, topic
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_details
|
66
|
+
topic.details = {}
|
67
|
+
raw_details = doc.css("div.featured-item-meta")
|
68
|
+
raw_details.css("div div").each do |raw_detail|
|
69
|
+
detail = raw_detail.css("strong").text.downcase.strip.split(" ").join("_")
|
70
|
+
info = raw_detail.css("p:last-child span").text.strip
|
71
|
+
info = raw_detail.css("p:last-child").text if info.empty?
|
72
|
+
topic.details[detail.to_sym] = info
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_101
|
77
|
+
url_101_text = doc.css("div#MarvelVideo101 script").text
|
78
|
+
unless url_101_text.empty?
|
79
|
+
id = url_101_text.match(/videoId: .([-\w]*)./)[1]
|
80
|
+
topic.urls[:url_101] = "https://www.youtube.com/watch?v=#{id}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_wiki
|
85
|
+
wiki_link = doc.css("div.title-section a.featured-item-notice.primary")
|
86
|
+
topic.urls[:url_wiki] = wiki_link.attr("href").value unless wiki_link.empty?
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'topic'
|
2
|
+
|
3
|
+
class Marvel101::Team < Marvel101::Topic
|
4
|
+
|
5
|
+
attr_accessor :list, :members
|
6
|
+
|
7
|
+
def display
|
8
|
+
display_description
|
9
|
+
display_members
|
10
|
+
display_links
|
11
|
+
end
|
12
|
+
|
13
|
+
def display_members
|
14
|
+
puts "CORE MEMBERS:" unless members.empty?
|
15
|
+
members.each.with_index(1) {|member, idx| puts " #{idx}. #{member.name}"}
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_input?(input)
|
19
|
+
members[input - 1] if input.between?(1, members.size)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Marvel101::Topic
|
2
|
+
|
3
|
+
attr_accessor :name, :description, :urls, :scraped
|
4
|
+
|
5
|
+
@@all = []
|
6
|
+
|
7
|
+
def initialize(name, url)
|
8
|
+
@name = name
|
9
|
+
@urls = {url: url}
|
10
|
+
@scraped = false
|
11
|
+
@@all << self
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_info
|
15
|
+
scraper = Marvel101::Scraper.new(self)
|
16
|
+
self.list? ? scraper.scrape_list : scraper.scrape_topic
|
17
|
+
@scraped = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def display_description
|
21
|
+
puts "DESCRIPTION: #{description}" if description
|
22
|
+
end
|
23
|
+
|
24
|
+
def display_links
|
25
|
+
puts "" if urls.size > 1
|
26
|
+
["wiki", "101"].each do |url|
|
27
|
+
output = "Marvel #{url} page available! Type '#{url}' to open in browser"
|
28
|
+
puts output if urls.include?("url_#{url}".to_sym)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def takes_input?
|
33
|
+
list? || (team? && !members.empty?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_team?
|
37
|
+
char? && team
|
38
|
+
end
|
39
|
+
|
40
|
+
def list?
|
41
|
+
self.is_a?(Marvel101::List)
|
42
|
+
end
|
43
|
+
|
44
|
+
def team?
|
45
|
+
self.is_a?(Marvel101::Team)
|
46
|
+
end
|
47
|
+
|
48
|
+
def char?
|
49
|
+
self.is_a?(Marvel101::Character)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find_or_create_by_name(name, url)
|
53
|
+
search = @@all.detect {|topic| topic.name == name}
|
54
|
+
search ? search : self.new(name, url)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.all
|
58
|
+
@@all
|
59
|
+
end
|
60
|
+
end
|
data/lib/marvel_101.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "marvel_101/version"
|
2
|
+
require "marvel_101/cli"
|
3
|
+
require "marvel_101/team"
|
4
|
+
require "marvel_101/list"
|
5
|
+
require "marvel_101/character"
|
6
|
+
require "marvel_101/scraper"
|
7
|
+
require "marvel_101/topic"
|
8
|
+
require "nokogiri"
|
9
|
+
require "open-uri"
|
10
|
+
require "pry"
|
11
|
+
require "launchy"
|
data/marvel_101.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "marvel_101/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "marvel_101"
|
8
|
+
spec.version = Marvel101::VERSION
|
9
|
+
spec.authors = ["Tyler Buchheim"]
|
10
|
+
spec.email = ["tbuchhei@alumni.nd.edu"]
|
11
|
+
|
12
|
+
spec.summary = "Marvel.com scraper CLI"
|
13
|
+
spec.description = "A CLI that scrapes marvel.com for info on popular Marvel characters and teams."
|
14
|
+
spec.homepage = "https://github.com/buchheimt/marvel_101"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.executables << "marvel_101"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = "bin"
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
end
|
data/spec.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Specifications for the CLI Assessment
|
2
|
+
|
3
|
+
Specs:
|
4
|
+
|
5
|
+
[X] Have a CLI for interfacing with the application
|
6
|
+
- CLI class takes care of all user interfacing
|
7
|
+
[X] Pull data from an external source
|
8
|
+
- Program scrapes multiple pages of Marvel.com
|
9
|
+
[X] Implement both list and detail views
|
10
|
+
- Lists are a list or teams and characters, teams are both
|
11
|
+
a detail view and another list, and characters are detail views
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: marvel_101
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Buchheim
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: A CLI that scrapes marvel.com for info on popular Marvel characters and
|
56
|
+
teams.
|
57
|
+
email:
|
58
|
+
- tbuchhei@alumni.nd.edu
|
59
|
+
executables:
|
60
|
+
- console
|
61
|
+
- marvel_101
|
62
|
+
- setup
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".rspec"
|
68
|
+
- ".travis.yml"
|
69
|
+
- CODE_OF_CONDUCT.md
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- bin/console
|
75
|
+
- bin/marvel_101
|
76
|
+
- bin/setup
|
77
|
+
- fixtures/avengers.html
|
78
|
+
- fixtures/defenders.html
|
79
|
+
- fixtures/featured.html
|
80
|
+
- fixtures/gambit.html
|
81
|
+
- fixtures/heroes.html
|
82
|
+
- fixtures/inhumans.html
|
83
|
+
- fixtures/nova.html
|
84
|
+
- fixtures/teams.html
|
85
|
+
- fixtures/thor.html
|
86
|
+
- lib/marvel_101.rb
|
87
|
+
- lib/marvel_101/character.rb
|
88
|
+
- lib/marvel_101/cli.rb
|
89
|
+
- lib/marvel_101/list.rb
|
90
|
+
- lib/marvel_101/scraper.rb
|
91
|
+
- lib/marvel_101/team.rb
|
92
|
+
- lib/marvel_101/topic.rb
|
93
|
+
- lib/marvel_101/version.rb
|
94
|
+
- marvel_101.gemspec
|
95
|
+
- spec.md
|
96
|
+
homepage: https://github.com/buchheimt/marvel_101
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.4.8
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Marvel.com scraper CLI
|
120
|
+
test_files: []
|