cubesmart 0.1.0 → 0.2.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.
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: []