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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Marvel101
2
+ VERSION = "0.1.0"
3
+ 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"
@@ -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: []