auto_service_cli 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 355c1cc539e58fa1e332f92898aa552df877911f
4
- data.tar.gz: 5ece5d2c31b308bc8679059beb6e22069f9f317a
3
+ metadata.gz: 460899cc76d351b7302046fd7208b3e0a8e96250
4
+ data.tar.gz: 5c1cade52f7afc886541c85e4bf5701aec5af1e1
5
5
  SHA512:
6
- metadata.gz: 26feeebf485c5edd172f49769f1e71e04621f71e44219ff9b32bb626a68d909c653ba9fb23c26aed4db5f7dd016c256c76365a7197fc33d1c523a6c88b7826f3
7
- data.tar.gz: ac20eca2ca3f7894b75cb722bc8fa99ad9af6a146c8ed97eda6b441f739b92842124b9b042c500faea12dd94804dd2b0f753045b34c9e6663a1da2b77b63e208
6
+ metadata.gz: c90f28f29bc86d03fcc0f5b90fb8dbb044a5b72980069e0ddcf8b8647ca375ca6cce43be8dc54178b93c2e4ecea590302b2d99068bdaa10fc761691cbd971ff1
7
+ data.tar.gz: 03dc7dbecb932690a5b4443b6574dbbe4e45f6bc5dcd13dc586c1ab2b2388906d61af4eef89c7da25de3d800569a66d97291029eed2c489b6304d1937356c354
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ["Aleksandr Rogachev"]
11
11
  spec.email = ["aleksandr.rogachev1994@gmail.com"]
12
12
 
13
- spec.summary = %q{CLI for searching of auto service centers near your location.}
14
- spec.description = %q{This CLI allows to find service centers near desired zip code (with sorting) and see details about each center.}
13
+ spec.summary = %q{CLI for searching auto service centers near your location}
14
+ spec.description = %q{This CLI allows you to find auto service centers near desired zip code, sort them, and fetch details about each of them}
15
15
  spec.homepage = "https://github.com/AleksandrRogachev94/auto_service_cli"
16
16
  spec.license = "MIT"
17
17
 
@@ -22,11 +22,11 @@ Gem::Specification.new do |spec|
22
22
  spec.executables << "auto_service"
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_dependency "nokogiri", "~> 1.7"
25
+ spec.add_dependency "nokogiri", "~> 1.8"
26
26
  spec.add_dependency "colorize", "~> 0.8"
27
27
 
28
- spec.add_development_dependency "bundler", "~> 1.13"
29
- spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "bundler", "~> 1.16"
29
+ spec.add_development_dependency "rake", "~> 10.5"
30
30
  spec.add_development_dependency "rspec", "~> 3.0"
31
- spec.add_development_dependency "pry", "~> 0.10"
31
+ spec.add_development_dependency "pry", "~> 0.11"
32
32
  end
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require_relative "../lib/auto_service_cli"
5
-
6
- AutoServiceCLI::CLI.new.call
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require_relative "../lib/auto_service_cli"
5
+
6
+ AutoServiceCLI::CLI.new.call
@@ -1,162 +1,150 @@
1
- class AutoServiceCLI::CLI
2
-
3
- attr_reader :zip, :cur_sort_type
4
- attr_reader :scraper
5
-
6
- def initialize
7
- self.cur_sort_type = "default"
8
- end
9
-
10
- def call
11
- puts "\n\tWelcome to auto service centers searching CLI!".green
12
- prompt_zip
13
- scrape_main_page
14
- list_centers
15
- menu
16
- end
17
-
18
- def prompt_zip
19
- begin
20
- puts "\tEnter you zip code:".magenta
21
- zip = gets.strip
22
- end until AutoServiceCLI::Scraper.valid_zip?(zip)
23
- self.zip = zip
24
- end
25
-
26
- # Writers and Readers
27
-
28
- def zip=(zip)
29
- @zip = zip if AutoServiceCLI::Scraper.valid_zip?(zip)
30
- end
31
-
32
- def cur_sort_type=(type)
33
- @cur_sort_type = type if AutoServiceCLI::Scraper.valid_sort_type?(type)
34
- end
35
-
36
- #-----------------------------------------------------------------------------------
37
- # Menu methods
38
-
39
- def menu
40
- loop do
41
- help_menu
42
- puts "\nMake a choise:".magenta
43
- input = gets.strip
44
- case input
45
- when "1"
46
- list_centers
47
- when "2"
48
- sort
49
- when "3"
50
- get_details
51
- when "4"
52
- scrape_main_page
53
- list_centers
54
- when "10"
55
- goodbye
56
- break
57
- end
58
- end
59
- end
60
-
61
- def list_centers
62
- puts "-----------------------------------------------------------------------------"
63
- puts "\tZIP: #{self.scraper.zip}\n\tSorting type: #{self.scraper.sort_type}\n".cyan
64
- AutoServiceCLI::ServiceCenter.all.each.with_index(1) do |center,i|
65
- print "#{i}.".cyan; print " #{center.name}"
66
- puts center.rating.nil? ? "" : ", rating: #{center.rating}"
67
- end
68
- puts "-----------------------------------------------------------------------------"
69
- end
70
-
71
- def sort
72
- help_sort
73
- puts "\n\tChoose type of sorting:".magenta
74
- input = gets.strip
75
- case input
76
- when "1"
77
- self.cur_sort_type = "default"
78
- when "2"
79
- self.cur_sort_type = "distance"
80
- when "3"
81
- self.cur_sort_type = "average_rating"
82
- when "4"
83
- self.cur_sort_type = "name"
84
- end
85
- scrape_main_page
86
- list_centers
87
- end
88
-
89
- def get_details
90
- puts "\n\tEnter the number of center:".green
91
- input = gets.strip
92
-
93
- if input.to_i >= 1 && input.to_i <= AutoServiceCLI::ServiceCenter.all.size
94
- center = AutoServiceCLI::ServiceCenter.all[input.to_i - 1]
95
- unless center.int_url.nil?
96
- puts "\nObtaining data..."
97
- self.scraper.scrape_center_details(center)
98
- puts "Done"
99
- end
100
-
101
- puts "----------------------------------------------------------------------------------------------------------------"
102
- puts "\n\t#{center.name.upcase}\n".red
103
- puts "\tRating:\n#{center.rating}\n".cyan unless center.rating.nil?
104
- puts "\tCategory:\n#{center.main_category}\n".cyan unless center.main_category.nil?
105
- puts "\tAddress:\n#{center.address}\n".cyan unless center.address.nil?
106
- puts"\tPhone number:\n#{center.phone_number}\n".cyan unless center.phone_number.nil?
107
-
108
- unless center.int_url.nil?
109
- puts "\tStatus:\n#{center.open_status}\n".cyan unless center.open_status.nil?
110
- puts "\tSlogan:\n#{center.slogan}\n".cyan unless center.slogan.nil?
111
- puts "\tWorking hours:\n#{center.working_hours}".cyan unless center.working_hours.nil?
112
- puts "\tDescription:\n#{center.description}\n".cyan unless center.description.nil?
113
- puts "\tServices:\n#{center.services}\n".cyan unless center.services.nil?
114
- puts "\tBrands:\n#{center.brands}\n".cyan unless center.brands.nil?
115
- puts "\tPayment methods:\n#{center.payment}\n".cyan unless center.payment.nil?
116
- end
117
-
118
- puts "\tSee more at:\n#{center.ext_url}\n".cyan unless center.ext_url.nil?
119
- puts "----------------------------------------------------------------------------------------------------------------"
120
- end
121
- end
122
-
123
- def scrape_main_page
124
- puts "Obtaining data..."
125
- AutoServiceCLI::ServiceCenter.reset_all!
126
- case self.cur_sort_type
127
- when "default"
128
- @scraper = AutoServiceCLI::Scraper.new(self.zip, self.cur_sort_type)
129
- when "distance"
130
- @scraper = AutoServiceCLI::Scraper.new(self.zip, self.cur_sort_type)
131
- when "average_rating"
132
- @scraper = AutoServiceCLI::Scraper.new(self.zip, self.cur_sort_type)
133
- when "name"
134
- @scraper = AutoServiceCLI::Scraper.new(self.zip, self.cur_sort_type)
135
- puts "name"
136
- end
137
- self.scraper.scrape_centers
138
- puts "Done"
139
- end
140
-
141
- #-----------------------------------------------------------------------------------
142
- # Helper methods
143
-
144
- def help_menu
145
- puts "\n1. List centers".green
146
- puts "2. Change sorting type".green
147
- puts "3. Show details about service center".green
148
- puts "4. Reload centers".green
149
- puts "10. Exit".green
150
- end
151
-
152
- def help_sort
153
- puts "\n\t1. Default".green
154
- puts "\t2. Sort by distance".green
155
- puts "\t3. Sort by rating".green
156
- puts "\t4. Sort by name".green
157
- end
158
-
159
- def goodbye
160
- puts "\n\tThank you for using this application!".blue
161
- end
162
- end
1
+ # TODO cur_sort_type string => data type
2
+
3
+
4
+ class AutoServiceCLI::CLI
5
+ attr_accessor :scraper, :scraper
6
+ private :scraper, :scraper=
7
+
8
+ def initialize
9
+ self.scraper = AutoServiceCLI::Scraper.new
10
+ end
11
+
12
+ def call
13
+ welcome
14
+ prompt_zip
15
+ scrape_main_page
16
+ list_centers
17
+ menu
18
+ end
19
+
20
+ def prompt_zip
21
+ begin
22
+ puts "\tEnter you zip code:".magenta
23
+ zip = gets.strip
24
+ end until AutoServiceCLI::Scraper.valid_zip?(zip)
25
+ scraper.zip = zip
26
+ end
27
+
28
+ #-----------------------------------------------------------------------------------
29
+ # Menu methods
30
+
31
+ def menu
32
+ loop do
33
+ help_menu
34
+ puts "\nMake a choice:".magenta
35
+ input = gets.strip
36
+ case input
37
+ when "1"
38
+ list_centers
39
+ when "2"
40
+ get_details
41
+ when "3"
42
+ sort
43
+ when "4"
44
+ scrape_main_page
45
+ list_centers
46
+ when "10"
47
+ goodbye
48
+ break
49
+ end
50
+ end
51
+ end
52
+
53
+ def list_centers
54
+ puts "-----------------------------------------------------------------------------"
55
+ puts "\tZIP: #{scraper.zip}\n\tSorting type: #{scraper.sort_type}\n".cyan
56
+ AutoServiceCLI::ServiceCenter.all.each.with_index(1) do |center,i|
57
+ print "#{i}.".cyan; print " #{center.name}"
58
+ puts center.rating.nil? ? "" : ", rating: #{center.rating}"
59
+ end
60
+ puts "-----------------------------------------------------------------------------"
61
+ end
62
+
63
+ def sort
64
+ help_sort
65
+ puts "\n\tChoose type of sorting:".magenta
66
+ input = gets.strip
67
+ case input
68
+ when "1"
69
+ scraper.sort_type = AutoServiceCLI::Scraper::SORT_TYPES[:DAFAULT]
70
+ when "2"
71
+ scraper.sort_type = AutoServiceCLI::Scraper::SORT_TYPES[:DISTANCE]
72
+ when "3"
73
+ scraper.sort_type = AutoServiceCLI::Scraper::SORT_TYPES[:AVERAGE_RATING]
74
+ when "4"
75
+ scraper.sort_type = AutoServiceCLI::Scraper::SORT_TYPES[:NAME]
76
+ end
77
+ scrape_main_page
78
+ list_centers
79
+ end
80
+
81
+ def get_details
82
+ puts "\n\tEnter the number of center:".green
83
+ input = gets.strip
84
+
85
+ if input.to_i >= 1 && input.to_i <= AutoServiceCLI::ServiceCenter.all.size
86
+ center = AutoServiceCLI::ServiceCenter.all[input.to_i - 1]
87
+ unless center.int_url.nil?
88
+ puts "\nObtaining data..."
89
+ scraper.scrape_center_details(center)
90
+ puts "Done"
91
+ end
92
+
93
+ puts "----------------------------------------------------------------------------------------------------------------"
94
+ puts "\n\t#{center.name.upcase}\n".red
95
+ puts "\tRating:\n#{center.rating}\n".cyan unless center.rating.nil?
96
+ puts "\tCategory:\n#{center.main_category}\n".cyan unless center.main_category.nil?
97
+ puts "\tAddress:\n#{center.address}\n".cyan unless center.address.nil?
98
+ puts"\tPhone number:\n#{center.phone_number}\n".cyan unless center.phone_number.nil?
99
+
100
+ unless center.int_url.nil?
101
+ puts "\tStatus:\n#{center.open_status}\n".cyan unless center.open_status.nil?
102
+ puts "\tSlogan:\n#{center.slogan}\n".cyan unless center.slogan.nil?
103
+ puts "\tWorking hours:\n#{center.working_hours}".cyan unless center.working_hours.nil?
104
+ puts "\tDescription:\n#{center.description}\n".cyan unless center.description.nil?
105
+ puts "\tServices:\n#{center.services}\n".cyan unless center.services.nil?
106
+ puts "\tBrands:\n#{center.brands}\n".cyan unless center.brands.nil?
107
+ puts "\tPayment methods:\n#{center.payment}\n".cyan unless center.payment.nil?
108
+ end
109
+
110
+ puts "\tSee more at:\n#{center.ext_url}\n".cyan unless center.ext_url.nil?
111
+ puts "----------------------------------------------------------------------------------------------------------------"
112
+ end
113
+ end
114
+
115
+ def scrape_main_page
116
+ puts "Obtaining data for you... It will take a few seconds"
117
+
118
+ # clean up all records.
119
+ AutoServiceCLI::ServiceCenter.reset_all!
120
+
121
+ scraper.scrape_centers
122
+ puts "Done"
123
+ end
124
+
125
+ #-----------------------------------------------------------------------------------
126
+ # Helper methods
127
+
128
+ def welcome
129
+ puts "\n\tWelcome to auto service centers searching CLI!\n\tby Aleksandr Rogachev © #{Time.new.year}\n".green
130
+ end
131
+
132
+ def help_menu
133
+ puts "\n1. List centers".green
134
+ puts "2. Show details about service center".green
135
+ puts "3. Change sorting type".green
136
+ puts "4. Reload centers".green
137
+ puts "10. Exit".green
138
+ end
139
+
140
+ def help_sort
141
+ puts "\n\t1. Default".green
142
+ puts "\t2. Sort by distance".green
143
+ puts "\t3. Sort by rating".green
144
+ puts "\t4. Sort by name".green
145
+ end
146
+
147
+ def goodbye
148
+ puts "\n\tThank you for using this application!".blue
149
+ end
150
+ end
@@ -1,4 +1,4 @@
1
- module AutoServiceCLI
2
- URL_BASE = "http://www.yellowpages.com"
3
- URL_TEMPLATE = "http://www.yellowpages.com/search?search_terms=auto%20service&geo_location_terms="
4
- end
1
+ module AutoServiceCLI
2
+ URL_BASE = "https://www.yellowpages.com"
3
+ URL_TEMPLATE = "https://www.yellowpages.com/search?search_terms=auto%20service&geo_location_terms="
4
+ end
@@ -1,163 +1,190 @@
1
- class AutoServiceCLI::Scraper
2
- attr_reader :doc, :sort_type, :zip
3
-
4
- # Constructors and class methods.
5
-
6
- def initialize(zip, sort_type = "default")
7
- @sort_type = sort_type
8
- @zip = zip
9
- @doc = Nokogiri::HTML(open(get_url))
10
- end
11
-
12
- def self.valid_zip?(zip)
13
- zip.to_i > 0
14
- end
15
-
16
- def self.valid_sort_type?(type)
17
- type == "default" || type == "distance" || type == "average_rating" || type == "name"
18
- end
19
-
20
- # Helper methods
21
-
22
- def get_url
23
- raise InvalidURLData, "Invalid input data: zip - #{self.zip}, sort type - #{self.sort_type}" unless valid_url_data?
24
- AutoServiceCLI::URL_TEMPLATE + "#{self.zip}&s=#{self.sort_type}"
25
- end
26
-
27
- def valid_url_data?
28
- self.class.valid_zip?(self.zip) && self.class.valid_sort_type?(self.sort_type)
29
- end
30
-
31
- def external_link?(url)
32
- url.include?("www.") || url.include?("http://") || url.include?("https://")
33
- end
34
-
35
- #------------------------------------------------------------------------------------------------
36
- #Scrape from the main page.
37
-
38
- def scrape_centers
39
- raise InvalidPage, "Invalid page!!!" if self.doc == nil
40
- centers = self.doc.css(".organic .result .info")
41
-
42
- centers.each do |center|
43
-
44
- main_details = {}
45
- obj_center = AutoServiceCLI::ServiceCenter.create(center.css(".n a").text)
46
-
47
- url = scrape_internal_url(center); main_details[:int_url] = url unless url.nil?
48
- url = scrape_external_url(center); main_details[:ext_url] = url unless url.nil?
49
- rating = scrape_rating(center); main_details[:rating] = rating unless rating.nil?
50
- address = scrape_address(center); main_details[:address] = address unless address.nil?
51
- phone = scrape_phone_number(center); main_details[:phone_number] = phone unless phone.nil?
52
- category = scrape_category(center); main_details[:main_category] = category unless category.nil?
53
-
54
- obj_center.details_from_hash(main_details) # setting all details to center.
55
- end
56
- end
57
-
58
- #------------------------------------------------------------------------------------------------
59
- # Scrape center details from the internal url (if available)
60
-
61
- def scrape_center_details(center)
62
- raise InvalidPage, "Invalid page!!!" if !center.is_a? AutoServiceCLI::ServiceCenter || center.int_url.nil?
63
- doc = Nokogiri::HTML(open(center.int_url))
64
- raise InvalidPage "Invalid page!!!" if doc == nil
65
-
66
- details = {}
67
-
68
- status = scrape_status(doc); details[:open_status] = status unless status.nil?
69
- slogan = scrape_slogan(doc); details[:slogan] = slogan unless slogan.nil?
70
- hours = scrape_hours(doc); details[:working_hours] = hours unless hours.nil?
71
- description = scrape_description(doc); details[:description] = description unless description.nil?
72
- services = scrape_services(doc); details[:services] = services unless services.nil?
73
- brands = scrape_brands(doc); details[:brands] = brands unless brands.nil?
74
- payment = scrape_payment(doc); details[:payment] = payment unless payment.nil?
75
-
76
- center.details_from_hash(details)
77
- end
78
-
79
- #---------------------------------------------------------------------------------------
80
- # Scraping helpers
81
-
82
- private
83
-
84
- def scrape_internal_url(center)
85
- url = center.css(".n a").attr("href").value
86
- (!url.empty? && !external_link?(url)) ? AutoServiceCLI::URL_BASE + url : nil
87
- end
88
-
89
- def scrape_external_url(center)
90
- url = center.css(".links a.track-visit-website")
91
- url.empty? ? nil : url.attr("href").value
92
- end
93
-
94
- def scrape_rating(center)
95
- rate = center.css(".info-primary .result-rating")
96
- unless rate.empty?
97
- attributes = rate.attr("class").value.split(" "); attributes.slice(1, attributes.size-1)
98
- else
99
- nil
100
- end
101
- end
102
-
103
- def scrape_address(center)
104
- address_full = center.css(".info-primary .adr span").collect {|el| el.text.split(",").first}
105
- address_full.flatten.empty? ? nil : address_full.join(", ")
106
- end
107
-
108
- def scrape_phone_number(center)
109
- phone_number = center.css(".info-primary .phone.primary")
110
- phone_number.empty? ? nil : phone_number.text
111
- end
112
-
113
- def scrape_category(center)
114
- category = center.css(".info-secondary .categories a")
115
- category.empty? ? nil : category.text
116
- end
117
- end
118
-
119
- def scrape_status(doc)
120
- open_status = doc.css(".business-card-wrapper .status-text")
121
- open_status.empty? ? nil : open_status.text
122
- end
123
-
124
- def scrape_slogan(doc)
125
- slogan = doc.css("#business-details .slogan")
126
- slogan.empty? ? nil : slogan.text
127
- end
128
-
129
- def scrape_hours(doc)
130
- working_hours = ""
131
- doc.css("#business-details .open-hours time").each do |time|
132
- working_hours << time.css(".day-label").text
133
- working_hours << ", " + time.css(".day-hours").text + "\n"
134
- end
135
- working_hours.empty? ? nil : working_hours
136
- end
137
-
138
- def scrape_description(doc)
139
- description = doc.css("#business-details p.description").last
140
- description = doc.css("#business-details dd.description").last if description.nil?
141
- (!description.nil? && description.css("a").empty?) ? description.text : nil #exclude case of facebook and twitter links, without decription
142
- end
143
-
144
- def scrape_services(doc)
145
- for_services = doc.css("#business-details dl")[1]
146
- unless for_services.nil?
147
- if for_services.css("dt").first.text == "Services/Products:"
148
- services = for_services.css("dd").first.text
149
- return services.empty? ? nil : services
150
- end
151
- end
152
- nil
153
- end
154
-
155
- def scrape_brands(doc)
156
- brands = doc.css("#business-details .brands")
157
- brands.empty? ? nil : brands.text
158
- end
159
-
160
- def scrape_payment(doc)
161
- payment = doc.css("#business-details .payment")
162
- payment.empty? ? nil : payment.text
163
- end
1
+ class AutoServiceCLI::Scraper
2
+ attr_reader :zip, :sort_type
3
+ attr_writer :zip, :sort_type
4
+
5
+ SORT_TYPES = {
6
+ DEFAULT: "default",
7
+ DISTANCE: "distance",
8
+ AVERAGE_RATING: "average_rating",
9
+ NAME: "name"
10
+ }
11
+
12
+ DEFAULT_SORT_TYPE = SORT_TYPES[:default]
13
+
14
+ # Constructors and class methods.
15
+
16
+ def initialize(zip = nil, sort_type = nil)
17
+ self.zip = zip if zip
18
+ self.sort_type = sort_type ? sort_type : self.class::SORT_TYPES[:DEFAULT]
19
+ end
20
+
21
+ def zip=(zip)
22
+ raise InvalidURLData unless self.class.valid_zip?(zip)
23
+ @zip = zip
24
+ end
25
+ def self.valid_zip?(zip)
26
+ zip.to_i > 0
27
+ end
28
+
29
+ def sort_type=(type)
30
+ raise InvalidURLData unless self.class.valid_sort_type?(type)
31
+ @sort_type = type
32
+ end
33
+ def self.valid_sort_type?(type)
34
+ SORT_TYPES.any? { |key, value| value == type }
35
+ end
36
+
37
+ # def valid_url_data?
38
+ # self.class.valid_zip?(self.zip) && self.class.valid_sort_type?(self.sort_type)
39
+ # end
40
+
41
+ #------------------------------------------------------------------------------------------------
42
+ # Scrape from the main page.
43
+ # Index action.
44
+
45
+ def scrape_centers
46
+ doc = Nokogiri::HTML(open(get_url))
47
+ raise InvalidPage "Invalid page!!!" if doc.nil?
48
+ centers = doc.css(".organic .result .info")
49
+
50
+ # form centers hash.
51
+ centers.each do |center|
52
+
53
+ main_details = {}
54
+ obj_center = AutoServiceCLI::ServiceCenter.create(scrape_name(center))
55
+
56
+ url = scrape_internal_url(center); main_details[:int_url] = url unless url.nil?
57
+ url = scrape_external_url(center); main_details[:ext_url] = url unless url.nil?
58
+ rating = scrape_rating(center); main_details[:rating] = rating unless rating.nil?
59
+ address = scrape_address(center); main_details[:address] = address unless address.nil?
60
+ phone = scrape_phone_number(center); main_details[:phone_number] = phone unless phone.nil?
61
+ category = scrape_category(center); main_details[:main_category] = category unless category.nil?
62
+
63
+ obj_center.details_from_hash(main_details) # setting all details to center.
64
+ end
65
+ end
66
+
67
+ #------------------------------------------------------------------------------------------------
68
+ # Scrape center details from the internal url (if available)
69
+ # Show action.
70
+
71
+ def scrape_center_details(center)
72
+ raise InvalidPage, "Invalid page!!!" if !center.is_a? AutoServiceCLI::ServiceCenter || center.int_url.nil?
73
+ doc = Nokogiri::HTML(open(center.int_url))
74
+ raise InvalidPage "Invalid page!!!" if doc.nil?
75
+
76
+ details = {}
77
+
78
+ status = scrape_status(doc); details[:open_status] = status unless status.nil?
79
+ slogan = scrape_slogan(doc); details[:slogan] = slogan unless slogan.nil?
80
+ hours = scrape_hours(doc); details[:working_hours] = hours unless hours.nil?
81
+ description = scrape_description(doc); details[:description] = description unless description.nil?
82
+ services = scrape_services(doc); details[:services] = services unless services.nil?
83
+ brands = scrape_brands(doc); details[:brands] = brands unless brands.nil?
84
+ payment = scrape_payment(doc); details[:payment] = payment unless payment.nil?
85
+
86
+ center.details_from_hash(details)
87
+ end
88
+
89
+ #---------------------------------------------------------------------------------------
90
+ # Scraping helpers
91
+
92
+ private
93
+
94
+ def get_url
95
+ raise InvalidURLData if !self.zip || !self.sort_type
96
+ AutoServiceCLI::URL_TEMPLATE + "#{self.zip}&s=#{self.sort_type}"
97
+ end
98
+
99
+ def external_link?(url)
100
+ url.include?("www.") || url.include?("http://") || url.include?("https://")
101
+ end
102
+
103
+ def scrape_name(center)
104
+ center.css(".n a").text
105
+ end
106
+
107
+ def scrape_internal_url(center)
108
+ url = center.css(".n a").attr("href").value
109
+ (!url.empty? && !external_link?(url)) ? AutoServiceCLI::URL_BASE + url : nil
110
+ end
111
+
112
+ def scrape_external_url(center)
113
+ url = center.css(".links a.track-visit-website")
114
+ url.empty? ? nil : url.attr("href").value
115
+ end
116
+
117
+ def scrape_rating(center)
118
+ rate = center.css(".info-primary .result-rating")
119
+ unless rate.empty?
120
+ attributes = rate.attr("class").value.split(" ");
121
+ attributes.slice(1, attributes.size - 1)
122
+ else
123
+ nil
124
+ end
125
+ end
126
+
127
+ def scrape_address(center)
128
+ address_full = center.css(".info-primary .adr span").collect {|el| el.text.split(",").first}
129
+ address_full.flatten.empty? ? nil : address_full.join(", ")
130
+ end
131
+
132
+ def scrape_phone_number(center)
133
+ phone_number = center.css(".info-primary .phone.primary")
134
+ phone_number.empty? ? nil : phone_number.text
135
+ end
136
+
137
+ def scrape_category(center)
138
+ category = center.css(".info-secondary .categories a")
139
+ category.empty? ? nil : category.text
140
+ end
141
+
142
+ def scrape_status(doc)
143
+ open_status = doc.css(".business-card-wrapper .status-text")
144
+ open_status.empty? ? nil : open_status.text
145
+ end
146
+
147
+ def scrape_slogan(doc)
148
+ slogan = doc.css("#business-info .slogan")
149
+ puts slogan
150
+ slogan.empty? ? nil : slogan.text
151
+ end
152
+
153
+ def scrape_hours(doc)
154
+ working_hours = ""
155
+ doc.css("#business-info .open-hours time").each do |time|
156
+ working_hours << time.css(".day-label").text
157
+ working_hours << ", " + time.css(".day-hours").text + "\n"
158
+ end
159
+ working_hours.empty? ? nil : working_hours
160
+ end
161
+
162
+
163
+ def scrape_description(doc)
164
+ description = doc.css("#business-info .general-info p").last
165
+ # description = doc.css("#business-info dd.description").last if description.nil?
166
+ (!description.nil? && description.css("a").empty?) ? description.text : nil #exclude case of facebook and twitter links, without decription
167
+ end
168
+
169
+ def scrape_services(doc)
170
+ for_services = doc.css("#business-info dl")[1]
171
+ unless for_services.nil?
172
+ if for_services.css("dt").first.text == "Services/Products:"
173
+ services = for_services.css("dd").first.text
174
+ return services.empty? ? nil : services
175
+ end
176
+ end
177
+ nil
178
+ end
179
+
180
+ def scrape_brands(doc)
181
+ brands = doc.css("#business-info .brands")
182
+ brands.empty? ? nil : brands.text
183
+ end
184
+
185
+ def scrape_payment(doc)
186
+ payment = doc.css("#business-info .payment")
187
+ payment.empty? ? nil : payment.text
188
+ end
189
+
190
+ end
@@ -1,68 +1,77 @@
1
- class AutoServiceCLI::ServiceCenter
2
- attr_accessor :name,
3
- :int_url,
4
- :ext_url,
5
- :rating,
6
- :main_category,
7
- :address,
8
- :phone_number,
9
- :open_status,
10
- :slogan,
11
- :working_hours,
12
- :description,
13
- :services,
14
- :brands,
15
- :payment
16
- @@all = []
17
-
18
- # Constructors
19
-
20
- def initialize(name)
21
- @name = name
22
- end
23
-
24
- def self.create(name)
25
- self.new(name).tap { |center| center.save }
26
- end
27
-
28
- # Instance methods.
29
-
30
- def save
31
- @@all << self
32
- end
33
-
34
- def details_from_hash(details)
35
- details.each do |detail, value|
36
- self.send("#{detail}=", value)
37
- end
38
- format_rating if details.include?(:rating)
39
- end
40
-
41
- def format_rating
42
- case self.rating.first
43
- when "one"
44
- new_rating = "1"
45
- when "two"
46
- new_rating = "2"
47
- when "three"
48
- new_rating = "3"
49
- when "four"
50
- new_rating = "4"
51
- when "five"
52
- new_rating = "5"
53
- end
54
-
55
- new_rating << ".5" if self.rating.size == 2 && self.rating.last == "half"
56
- self.rating = new_rating + " star(s)"
57
- end
58
-
59
- # Class mehods
60
-
61
- def self.reset_all!
62
- @@all.clear
63
- end
64
-
65
- def self.all
66
- @@all.dup.freeze
67
- end
68
- end
1
+ class AutoServiceCLI::ServiceCenter
2
+ attr_accessor :name,
3
+ :int_url,
4
+ :ext_url,
5
+ :rating,
6
+ :main_category,
7
+ :address,
8
+ :phone_number,
9
+ :open_status,
10
+ :slogan,
11
+ :working_hours,
12
+ :description,
13
+ :services,
14
+ :brands,
15
+ :payment
16
+ @@all = []
17
+
18
+ # Constructors
19
+
20
+ def initialize(name)
21
+ @name = name
22
+ end
23
+
24
+ def self.create(name)
25
+ self.new(name).tap { |center| center.save }
26
+ end
27
+
28
+ # Instance methods.
29
+
30
+ def save
31
+ @@all << self
32
+ end
33
+
34
+ def details_from_hash(details)
35
+ # modify hash's rating
36
+ if details.include?(:rating)
37
+ details[:rating] = format_rating(details[:rating])
38
+ end
39
+
40
+ details.each do |detail, value|
41
+ self.send("#{detail}=", value)
42
+ end
43
+ end
44
+
45
+ # ex ["two", "half"] or ["two"]
46
+ def format_rating(rating)
47
+ case rating[0]
48
+ when "one"
49
+ new_rating = "1"
50
+ when "two"
51
+ new_rating = "2"
52
+ when "three"
53
+ new_rating = "3"
54
+ when "four"
55
+ new_rating = "4"
56
+ when "five"
57
+ new_rating = "5"
58
+ else
59
+ # assume it's already formatted
60
+ return rating
61
+ end
62
+
63
+ # TODO
64
+ new_rating << ".5" if rating.size == 2 && rating.last == "half"
65
+ new_rating + " star(s)"
66
+ end
67
+
68
+ # Class mehods
69
+
70
+ def self.reset_all!
71
+ @@all.clear
72
+ end
73
+
74
+ def self.all
75
+ @@all.dup.freeze
76
+ end
77
+ end
@@ -1,3 +1,3 @@
1
1
  module AutoServiceCLI
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
data/spec.md CHANGED
@@ -1,12 +1,12 @@
1
- # Specifications for the CLI Assessment
2
-
3
- Specs:
4
- - [x] Have a CLI for interfacing with the application
5
- The CLI class was implemented to show users the interface. It has a #call method which is a starting point of CLI. A user should enter a zip code to get info about service centers near him. CLI class has menu method which gives to user an opportunity to make choices: list centers, choose sorting type, show details about center, reload centers, and exit.
6
-
7
- - [x] Pull data from an external source
8
- yellowpages.com is used to scrape the data about 30 service centers near entered zip code. Gem 'nokogiri' is used to scrape the data. The main page about service centers is used to scrape main data. When a user chooses to see the data about specific center, program scrapes data from the additional url (if available) obtained from the main page.
9
-
10
- - [x] Implement both list and detail views
11
- In CLI class #list_centers method was implemented. It shows 30 centers near entered zip code.
12
- In CLI class #get_details method was implemented. It invokes the method to scrape the data from the specific url and lists the results.
1
+ # Specifications for the CLI Assessment
2
+
3
+ Specs:
4
+ - [x] Have a CLI for interfacing with the application
5
+ The CLI class was implemented to show users the interface. It has a #call method which is a starting point of CLI. A user should enter a zip code to get info about service centers near him. CLI class has menu method which gives to user an opportunity to make choices: list centers, choose sorting type, show details about center, reload centers, and exit.
6
+
7
+ - [x] Pull data from an external source
8
+ yellowpages.com is used to scrape the data about 30 service centers near entered zip code. Gem 'nokogiri' is used to scrape the data. The main page about service centers is used to scrape main data. When a user chooses to see the data about specific center, program scrapes data from the additional url (if available) obtained from the main page.
9
+
10
+ - [x] Implement both list and detail views
11
+ In CLI class #list_centers method was implemented. It shows 30 centers near entered zip code.
12
+ In CLI class #get_details method was implemented. It invokes the method to scrape the data from the specific url and lists the results.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auto_service_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleksandr Rogachev
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '1.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '1.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: colorize
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.13'
47
+ version: '1.16'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.13'
54
+ version: '1.16'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '10.5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '10.5'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,16 +86,16 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.10'
89
+ version: '0.11'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.10'
97
- description: This CLI allows to find service centers near desired zip code (with sorting)
98
- and see details about each center.
96
+ version: '0.11'
97
+ description: This CLI allows you to find auto service centers near desired zip code,
98
+ sort them, and fetch details about each of them
99
99
  email:
100
100
  - aleksandr.rogachev1994@gmail.com
101
101
  executables:
@@ -110,6 +110,7 @@ files:
110
110
  - LICENSE.txt
111
111
  - README.md
112
112
  - Rakefile
113
+ - auto_service_cli-0.1.1.gem
113
114
  - auto_service_cli.gemspec
114
115
  - bin/auto_service
115
116
  - bin/console
@@ -141,8 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
142
  version: '0'
142
143
  requirements: []
143
144
  rubyforge_project:
144
- rubygems_version: 2.6.8
145
+ rubygems_version: 2.4.8
145
146
  signing_key:
146
147
  specification_version: 4
147
- summary: CLI for searching of auto service centers near your location.
148
+ summary: CLI for searching auto service centers near your location
148
149
  test_files: []