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 +4 -4
- data/auto_service_cli-0.1.1.gem +0 -0
- data/auto_service_cli.gemspec +6 -6
- data/bin/auto_service +6 -6
- data/lib/auto_service_cli/cli.rb +150 -162
- data/lib/auto_service_cli/constants.rb +4 -4
- data/lib/auto_service_cli/scraper.rb +190 -163
- data/lib/auto_service_cli/service_center.rb +77 -68
- data/lib/auto_service_cli/version.rb +1 -1
- data/spec.md +12 -12
- metadata +14 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 460899cc76d351b7302046fd7208b3e0a8e96250
|
4
|
+
data.tar.gz: 5c1cade52f7afc886541c85e4bf5701aec5af1e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c90f28f29bc86d03fcc0f5b90fb8dbb044a5b72980069e0ddcf8b8647ca375ca6cce43be8dc54178b93c2e4ecea590302b2d99068bdaa10fc761691cbd971ff1
|
7
|
+
data.tar.gz: 03dc7dbecb932690a5b4443b6574dbbe4e45f6bc5dcd13dc586c1ab2b2388906d61af4eef89c7da25de3d800569a66d97291029eed2c489b6304d1937356c354
|
Binary file
|
data/auto_service_cli.gemspec
CHANGED
@@ -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
|
14
|
-
spec.description = %q{This CLI allows to find service centers near desired zip code
|
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.
|
25
|
+
spec.add_dependency "nokogiri", "~> 1.8"
|
26
26
|
spec.add_dependency "colorize", "~> 0.8"
|
27
27
|
|
28
|
-
spec.add_development_dependency "bundler", "~> 1.
|
29
|
-
spec.add_development_dependency "rake", "~> 10.
|
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.
|
31
|
+
spec.add_development_dependency "pry", "~> 0.11"
|
32
32
|
end
|
data/bin/auto_service
CHANGED
@@ -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
|
data/lib/auto_service_cli/cli.rb
CHANGED
@@ -1,162 +1,150 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
center
|
95
|
-
unless center.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
puts "
|
149
|
-
|
150
|
-
|
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 = "
|
3
|
-
URL_TEMPLATE = "
|
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 :
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
obj_center.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
97
|
-
description: This CLI allows to find service centers near desired zip code
|
98
|
-
and
|
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.
|
145
|
+
rubygems_version: 2.4.8
|
145
146
|
signing_key:
|
146
147
|
specification_version: 4
|
147
|
-
summary: CLI for searching
|
148
|
+
summary: CLI for searching auto service centers near your location
|
148
149
|
test_files: []
|