cubesmart 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cubesmart/address.rb +3 -5
- data/lib/cubesmart/cli.rb +1 -1
- data/lib/cubesmart/crawl.rb +37 -0
- data/lib/cubesmart/crawler.rb +0 -9
- data/lib/cubesmart/dimensions.rb +10 -3
- data/lib/cubesmart/facility.rb +5 -16
- data/lib/cubesmart/fetch_error.rb +12 -0
- data/lib/cubesmart/price.rb +6 -5
- data/lib/cubesmart/rates.rb +24 -10
- data/lib/cubesmart/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1781d8409650e418c66768205aaa73dd3b95d10c22cfdb58996c4b7d0f813c5
|
4
|
+
data.tar.gz: 4c9cc3f1d3f3ff55cac8fa8442faea727f3ac885f3224f33e225de9b802ec246
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5d64f586345d591466b8b50dacf95baf86c71bbbf2d02f967b88e5768c48e528b5602f2617e10b9f62006d62c4bdcd8ea7f8c485d30b5b0cac85390406f77d4
|
7
|
+
data.tar.gz: 04356e1ca5ae5fb07435d39e7eb44837146f0b3f2f2b13d373aaf3caeb327f15104b99a6467098c29f622da3e891c54764c67fa05616b271dcd2ecbce433e592
|
data/lib/cubesmart/address.rb
CHANGED
@@ -50,12 +50,10 @@ module CubeSmart
|
|
50
50
|
#
|
51
51
|
# @return [Address]
|
52
52
|
def self.parse(data:)
|
53
|
-
lines = %w[line1 line2 line3 line4].map { |key| data[key] }
|
54
|
-
|
55
53
|
new(
|
56
|
-
street:
|
57
|
-
city: data['
|
58
|
-
state: data['
|
54
|
+
street: data['streetAddress'],
|
55
|
+
city: data['addressLocality'],
|
56
|
+
state: data['addressRegion'],
|
59
57
|
zip: data['postalCode']
|
60
58
|
)
|
61
59
|
end
|
data/lib/cubesmart/cli.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CubeSmart
|
4
|
+
# Handles the crawl command via CLI.
|
5
|
+
class Crawl
|
6
|
+
def self.run(...)
|
7
|
+
new(...).run
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param stdout [IO] optional
|
11
|
+
# @param stderr [IO] optional
|
12
|
+
# @param options [Hash] optional
|
13
|
+
def initialize(stdout: $stdout, stderr: $stderr, options: {})
|
14
|
+
@stdout = stdout
|
15
|
+
@stderr = stderr
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
sitemap = Facility.sitemap
|
21
|
+
@stdout.puts("count=#{sitemap.links.count}")
|
22
|
+
@stdout.puts
|
23
|
+
|
24
|
+
sitemap.links.each { |link| process(url: link.loc) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def process(url:)
|
28
|
+
@stdout.puts(url)
|
29
|
+
facility = Facility.fetch(url: url)
|
30
|
+
@stdout.puts(facility.text)
|
31
|
+
facility.prices.each { |price| @stdout.puts(price.text) }
|
32
|
+
@stdout.puts
|
33
|
+
rescue FetchError => e
|
34
|
+
@stderr.puts("url=#{url} error=#{e.message}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/cubesmart/crawler.rb
CHANGED
@@ -5,15 +5,6 @@ module CubeSmart
|
|
5
5
|
class Crawler
|
6
6
|
HOST = 'https://www.cubesmart.com'
|
7
7
|
|
8
|
-
# Raised for unexpected HTTP responses.
|
9
|
-
class FetchError < StandardError
|
10
|
-
# @param url [String]
|
11
|
-
# @param response [HTTP::Response]
|
12
|
-
def initialize(url:, response:)
|
13
|
-
super("url=#{url} status=#{response.status.inspect} body=#{String(response.body).inspect}")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
8
|
# @param url [String]
|
18
9
|
# @raise [FetchError]
|
19
10
|
# @return [Nokogiri::HTML::Document]
|
data/lib/cubesmart/dimensions.rb
CHANGED
@@ -39,11 +39,18 @@ module CubeSmart
|
|
39
39
|
"#{format('%g', @width)}' × #{format('%g', @depth)}' (#{@sqft} sqft)"
|
40
40
|
end
|
41
41
|
|
42
|
-
# @param
|
42
|
+
# @param element [Nokogiri::XML::Element]
|
43
43
|
#
|
44
44
|
# @return [Dimensions]
|
45
|
-
def self.parse(
|
46
|
-
|
45
|
+
def self.parse(element:)
|
46
|
+
text = element.text
|
47
|
+
match = text.match(/(?<width>[\d\.]+)'x(?<depth>[\d\.]+)'/)
|
48
|
+
raise text.inspect if match.nil?
|
49
|
+
|
50
|
+
width = Float(match[:width])
|
51
|
+
depth = Float(match[:depth])
|
52
|
+
sqft = Integer(width * depth)
|
53
|
+
new(depth:, width:, sqft:)
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
data/lib/cubesmart/facility.rb
CHANGED
@@ -9,6 +9,10 @@ module CubeSmart
|
|
9
9
|
|
10
10
|
SITEMAP_URL = 'https://www.cubesmart.com/sitemap-facility.xml'
|
11
11
|
|
12
|
+
PRICE_SELECTOR = %w[small medium large].map do |group|
|
13
|
+
".#{group}-group ul.csStorageSizeDimension li.csStorageSizeDimension"
|
14
|
+
end.join(', ')
|
15
|
+
|
12
16
|
ID_REGEX = /(?<id>\d+)\.html/
|
13
17
|
|
14
18
|
# @attribute [rw] id
|
@@ -54,7 +58,7 @@ module CubeSmart
|
|
54
58
|
name = data['name']
|
55
59
|
address = Address.parse(data: data['address'])
|
56
60
|
geocode = Geocode.parse(data: data['geo'])
|
57
|
-
prices =
|
61
|
+
prices = document.css(PRICE_SELECTOR).map { |element| Price.parse(element: element) }
|
58
62
|
|
59
63
|
new(id:, name:, address:, geocode:, prices:)
|
60
64
|
end
|
@@ -69,21 +73,6 @@ module CubeSmart
|
|
69
73
|
graph.find { |entry| entry['@type'] == 'SelfStorage' } || raise(ParseError, 'missing @graph')
|
70
74
|
end
|
71
75
|
|
72
|
-
def self.crawl
|
73
|
-
sitemap.links.each do |link|
|
74
|
-
url = link.loc
|
75
|
-
|
76
|
-
facility = fetch(url:)
|
77
|
-
puts facility.text
|
78
|
-
|
79
|
-
facility.prices.each do |price|
|
80
|
-
puts price.text
|
81
|
-
end
|
82
|
-
|
83
|
-
puts
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
76
|
# @param id [String]
|
88
77
|
# @param name [String]
|
89
78
|
# @param address [Address]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CubeSmart
|
4
|
+
# Raised for unexpected HTTP responses.
|
5
|
+
class FetchError < Error
|
6
|
+
# @param url [String]
|
7
|
+
# @param response [HTTP::Response]
|
8
|
+
def initialize(url:, response:)
|
9
|
+
super("url=#{url} status=#{response.status.inspect} body=#{String(response.body).inspect}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/cubesmart/price.rb
CHANGED
@@ -39,14 +39,15 @@ module CubeSmart
|
|
39
39
|
"#{@id} | #{@dimensions.text} | #{@rates.text}"
|
40
40
|
end
|
41
41
|
|
42
|
-
# @param
|
42
|
+
# @param element [Nokogiri::XML::Element]
|
43
43
|
#
|
44
44
|
# @return [Price]
|
45
|
-
def self.parse(
|
46
|
-
|
47
|
-
|
45
|
+
def self.parse(element:)
|
46
|
+
id = element.attr('id')
|
47
|
+
dimensions = Dimensions.parse(element:)
|
48
|
+
rates = Rates.parse(element:)
|
48
49
|
new(
|
49
|
-
id
|
50
|
+
id:,
|
50
51
|
dimensions: dimensions,
|
51
52
|
rates: rates
|
52
53
|
)
|
data/lib/cubesmart/rates.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
module CubeSmart
|
4
4
|
# The rates (street + web) for a facility
|
5
5
|
class Rates
|
6
|
+
STREET_SELECTOR = '.ptOriginalPriceSpan'
|
7
|
+
WEB_SELECTOR = '.ptDiscountPriceSpan'
|
8
|
+
VALUE_REGEX = /(?<value>[\d\.]+)/
|
9
|
+
|
6
10
|
# @attribute [rw] street
|
7
11
|
# @return [Integer]
|
8
12
|
attr_accessor :street
|
@@ -11,6 +15,26 @@ module CubeSmart
|
|
11
15
|
# @return [Integer]
|
12
16
|
attr_accessor :web
|
13
17
|
|
18
|
+
# @param element [Nokogiri::XML::Element]
|
19
|
+
#
|
20
|
+
# @return [Rates]
|
21
|
+
def self.parse(element:)
|
22
|
+
street = parse_value(element: element.at_css(STREET_SELECTOR))
|
23
|
+
web = parse_value(element: element.at_css(WEB_SELECTOR))
|
24
|
+
|
25
|
+
new(street: street || web, web: web || street)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param element [Nokogiri::XML::Element]
|
29
|
+
#
|
30
|
+
# @return [Float, nil]
|
31
|
+
def self.parse_value(element:)
|
32
|
+
return if element.nil?
|
33
|
+
|
34
|
+
match = VALUE_REGEX.match(element.text)
|
35
|
+
Float(match[:value]) if match
|
36
|
+
end
|
37
|
+
|
14
38
|
# @param street [Integer]
|
15
39
|
# @param web [Integer]
|
16
40
|
def initialize(street:, web:)
|
@@ -31,15 +55,5 @@ module CubeSmart
|
|
31
55
|
def text
|
32
56
|
"$#{@street} (street) | $#{@web} (web)"
|
33
57
|
end
|
34
|
-
|
35
|
-
# @param data [Hash]
|
36
|
-
#
|
37
|
-
# @return [Rates]
|
38
|
-
def self.parse(data:)
|
39
|
-
new(
|
40
|
-
street: data['street'],
|
41
|
-
web: data['web']
|
42
|
-
)
|
43
|
-
end
|
44
58
|
end
|
45
59
|
end
|
data/lib/cubesmart/version.rb
CHANGED
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cubesmart
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Sylvestre
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2024-12-03 00:00:00.000000000 Z
|
@@ -97,9 +97,11 @@ files:
|
|
97
97
|
- lib/cubesmart/address.rb
|
98
98
|
- lib/cubesmart/cli.rb
|
99
99
|
- lib/cubesmart/config.rb
|
100
|
+
- lib/cubesmart/crawl.rb
|
100
101
|
- lib/cubesmart/crawler.rb
|
101
102
|
- lib/cubesmart/dimensions.rb
|
102
103
|
- lib/cubesmart/facility.rb
|
104
|
+
- lib/cubesmart/fetch_error.rb
|
103
105
|
- lib/cubesmart/geocode.rb
|
104
106
|
- lib/cubesmart/link.rb
|
105
107
|
- lib/cubesmart/price.rb
|
@@ -114,7 +116,7 @@ metadata:
|
|
114
116
|
homepage_uri: https://github.com/ksylvest/cubesmart
|
115
117
|
source_code_uri: https://github.com/ksylvest/cubesmart
|
116
118
|
changelog_uri: https://github.com/ksylvest/cubesmart
|
117
|
-
post_install_message:
|
119
|
+
post_install_message:
|
118
120
|
rdoc_options: []
|
119
121
|
require_paths:
|
120
122
|
- lib
|
@@ -129,8 +131,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
131
|
- !ruby/object:Gem::Version
|
130
132
|
version: '0'
|
131
133
|
requirements: []
|
132
|
-
rubygems_version: 3.5.
|
133
|
-
signing_key:
|
134
|
+
rubygems_version: 3.5.23
|
135
|
+
signing_key:
|
134
136
|
specification_version: 4
|
135
137
|
summary: A crawler for CubeSmart.
|
136
138
|
test_files: []
|