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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea9ec6ccaa984aec55a381edbdb9bf8998b2d2e60274ae4440f6a6ee7476730e
4
- data.tar.gz: 787a788d8d62665887f49dd8ae34e6f9ff6d0f0d3f31b507602e719a4d826e95
3
+ metadata.gz: e1781d8409650e418c66768205aaa73dd3b95d10c22cfdb58996c4b7d0f813c5
4
+ data.tar.gz: 4c9cc3f1d3f3ff55cac8fa8442faea727f3ac885f3224f33e225de9b802ec246
5
5
  SHA512:
6
- metadata.gz: 9e68c9af8d7bef159d0a0c920fefe68dcdfa2bc3ac87bbf81217bc7f9a42e7520d8a154135a2e05b52530becf577b6ce9adeae53c15cee18306098f5b2affb6e
7
- data.tar.gz: ad330358eb510641de1d7f6c94580895730ff443074d38babf561f0625fc093d220630c56a6ba3a4a19c47d53507c887a1db399d6df8ddaf3e438d30ae03511b
6
+ metadata.gz: b5d64f586345d591466b8b50dacf95baf86c71bbbf2d02f967b88e5768c48e528b5602f2617e10b9f62006d62c4bdcd8ea7f8c485d30b5b0cac85390406f77d4
7
+ data.tar.gz: 04356e1ca5ae5fb07435d39e7eb44837146f0b3f2f2b13d373aaf3caeb327f15104b99a6467098c29f622da3e891c54764c67fa05616b271dcd2ecbce433e592
@@ -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: lines.compact.reject(&:empty?).join(' '),
57
- city: data['city'],
58
- state: data['stateName'],
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
@@ -31,7 +31,7 @@ module CubeSmart
31
31
  private
32
32
 
33
33
  def crawl
34
- CubeSmart::Facility.crawl
34
+ Crawl.run
35
35
  exit(Code::OK)
36
36
  end
37
37
 
@@ -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
@@ -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]
@@ -39,11 +39,18 @@ module CubeSmart
39
39
  "#{format('%g', @width)}' × #{format('%g', @depth)}' (#{@sqft} sqft)"
40
40
  end
41
41
 
42
- # @param data [Hash]
42
+ # @param element [Nokogiri::XML::Element]
43
43
  #
44
44
  # @return [Dimensions]
45
- def self.parse(data:)
46
- new(depth: data['depth'], width: data['width'], sqft: data['squareFoot'])
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
@@ -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
@@ -39,14 +39,15 @@ module CubeSmart
39
39
  "#{@id} | #{@dimensions.text} | #{@rates.text}"
40
40
  end
41
41
 
42
- # @param data [Hash]
42
+ # @param element [Nokogiri::XML::Element]
43
43
  #
44
44
  # @return [Price]
45
- def self.parse(data:)
46
- dimensions = Dimensions.parse(data: data['dimensions'])
47
- rates = Rates.parse(data: data['rates'])
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: data['uid'],
50
+ id:,
50
51
  dimensions: dimensions,
51
52
  rates: rates
52
53
  )
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CubeSmart
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
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.1.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.22
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: []