cubesmart 0.1.0 → 0.3.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: 3e26402813c2f507a7a4902766a26821194de2f1c9c2d31ef633b2138c45e2f5
4
+ data.tar.gz: 91628c2ca652fc5be1788d4616652b82483863ab3b0bc3107cf7f3b9861cac51
5
5
  SHA512:
6
- metadata.gz: 9e68c9af8d7bef159d0a0c920fefe68dcdfa2bc3ac87bbf81217bc7f9a42e7520d8a154135a2e05b52530becf577b6ce9adeae53c15cee18306098f5b2affb6e
7
- data.tar.gz: ad330358eb510641de1d7f6c94580895730ff443074d38babf561f0625fc093d220630c56a6ba3a4a19c47d53507c887a1db399d6df8ddaf3e438d30ae03511b
6
+ metadata.gz: e1f88ebc04f150dc9cbf0d590e01f05245be3a3a323d31526b6f5cdd6f445b38ad66c2cf7a0f77847ba20c62c364b58a1fd9db1bb87ee2fd8d610f0fc30f53cc
7
+ data.tar.gz: 04f620cbfe554d8ecaeb77e363ad7a31c506a954c86deec4f6ad597af182c39c42340667f202fe9a7880740d4c4525c657368813d8a9bd6d6751aea8398db3ca
@@ -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
@@ -7,8 +7,15 @@ module CubeSmart
7
7
  class Facility
8
8
  class ParseError < StandardError; end
9
9
 
10
+ DEFAULT_EMAIL = 'webleads@cubesmart.com'
11
+ DEFAULT_PHONE = '1-877-279-7585'
12
+
10
13
  SITEMAP_URL = 'https://www.cubesmart.com/sitemap-facility.xml'
11
14
 
15
+ PRICE_SELECTOR = %w[small medium large].map do |group|
16
+ ".#{group}-group ul.csStorageSizeDimension li.csStorageSizeDimension"
17
+ end.join(', ')
18
+
12
19
  ID_REGEX = /(?<id>\d+)\.html/
13
20
 
14
21
  # @attribute [rw] id
@@ -19,6 +26,14 @@ module CubeSmart
19
26
  # @return [String]
20
27
  attr_accessor :name
21
28
 
29
+ # @attribute [rw] phone
30
+ # @return [String]
31
+ attr_accessor :phone
32
+
33
+ # @attribute [rw] email
34
+ # @return [String]
35
+ attr_accessor :email
36
+
22
37
  # @attribute [rw] address
23
38
  # @return [Address]
24
39
  attr_accessor :address
@@ -54,7 +69,7 @@ module CubeSmart
54
69
  name = data['name']
55
70
  address = Address.parse(data: data['address'])
56
71
  geocode = Geocode.parse(data: data['geo'])
57
- prices = []
72
+ prices = document.css(PRICE_SELECTOR).map { |element| Price.parse(element: element) }
58
73
 
59
74
  new(id:, name:, address:, geocode:, prices:)
60
75
  end
@@ -69,31 +84,20 @@ module CubeSmart
69
84
  graph.find { |entry| entry['@type'] == 'SelfStorage' } || raise(ParseError, 'missing @graph')
70
85
  end
71
86
 
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
87
  # @param id [String]
88
88
  # @param name [String]
89
89
  # @param address [Address]
90
90
  # @param geocode [Geocode]
91
+ # @param phone [String]
92
+ # @param email [String]
91
93
  # @param prices [Array<Price>]
92
- def initialize(id:, name:, address:, geocode:, prices:)
94
+ def initialize(id:, name:, address:, geocode:, phone: DEFAULT_PHONE, email: DEFAULT_EMAIL, prices: [])
93
95
  @id = id
94
96
  @name = name
95
97
  @address = address
96
98
  @geocode = geocode
99
+ @phone = phone
100
+ @email = email
97
101
  @prices = prices
98
102
  end
99
103
 
@@ -103,6 +107,8 @@ module CubeSmart
103
107
  "id=#{@id.inspect}",
104
108
  "address=#{@address.inspect}",
105
109
  "geocode=#{@geocode.inspect}",
110
+ "phone=#{@phone.inspect}",
111
+ "email=#{@email.inspect}",
106
112
  "prices=#{@prices.inspect}"
107
113
  ]
108
114
  "#<#{self.class.name} #{props.join(' ')}>"
@@ -110,7 +116,7 @@ module CubeSmart
110
116
 
111
117
  # @return [String]
112
118
  def text
113
- "#{@id} | #{@name} | #{@address.text} | #{@geocode.text}"
119
+ "#{@id} | #{@name} | #{@phone} | #{@email} | #{@address.text} | #{@geocode.text}"
114
120
  end
115
121
  end
116
122
  end
@@ -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.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
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.3.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
- date: 2024-12-03 00:00:00.000000000 Z
11
+ date: 2024-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -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: []