publicstorage 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2bf23e38fa3f9d3a9aebab0d6283a5647df197c987cbd5c79b57a59d7838efd
4
- data.tar.gz: 771bdb24ed0f74864fbec98eb4f0ed5c337e256d9b2f934c09df480d5ab205cb
3
+ metadata.gz: fc55c7ceabfcca39423c75b3315ded193267c2d02da648056b472c4f6e5ca64a
4
+ data.tar.gz: ed7aaea3035fe7aaa876febfa809a90cf9559080e61aeca1fdffb8ed338bba61
5
5
  SHA512:
6
- metadata.gz: 94ae28707de256932033d3dea9e0a9492de8086e88fb96dcebdac744e1a27727bd57f696a877a89c631243065b25fd9a6f0c19813a846059d319c2c0ebd6073c
7
- data.tar.gz: e1496da21aa0fccf557e5fa7c8a1e6e4ed76df02ae4a6c1f6387a1ebc07e04259d15bfefdb6a40c1b9e9b310ac99680d8337094cc390d4c2ee32ad1b1b1f563e
6
+ metadata.gz: b4d628a25a0af8d0754c98868d3a7cd18c2a8b7d6d9dc6f66fbd1a1a83454aea72c108806f3e25a4311d5aa80862cb4a7798cd5645e08484b4f44179139f1cd3
7
+ data.tar.gz: 46684da48469510751828b452b5f00381caf94b460da89383675429ed8974878355e03cb5e2fdc879db59b5a8e591a83a557b77f131558a4ad91fafb84dc05d9
data/README.md CHANGED
@@ -23,8 +23,7 @@ sitemap.links.each do |link|
23
23
 
24
24
  facility = PublicStorage::Facility.fetch(url:)
25
25
 
26
- puts "Line 1: #{facility.address.line1}"
27
- puts "Line 2: #{facility.address.line2}"
26
+ puts "Street: #{facility.address.street}"
28
27
  puts "City: #{facility.address.city}"
29
28
  puts "State: #{facility.address.state}"
30
29
  puts "ZIP: #{facility.address.zip}"
@@ -33,8 +32,10 @@ sitemap.links.each do |link|
33
32
  puts
34
33
 
35
34
  facility.prices.each do |price|
36
- puts "UID: #{price.uid}"
37
- puts "Dimensions: #{price.dimensions.display}"
35
+ puts "ID: #{price.id}"
36
+ puts "Width: #{price.dimensions.width}"
37
+ puts "Depth: #{price.dimensions.depth}"
38
+ puts "SQFT: #{price.dimensions.sqft}"
38
39
  puts "Rates: $#{price.rates.street} (street) / $#{price.rates.web} (web)"
39
40
  puts
40
41
  end
@@ -3,13 +3,9 @@
3
3
  module PublicStorage
4
4
  # Represents an address associated with a facility.
5
5
  class Address
6
- # @attribute [rw] line1
6
+ # @attribute [rw] street
7
7
  # @return [String]
8
- attr_accessor :line1
9
-
10
- # @attribute [rw] line2
11
- # @return [String]
12
- attr_accessor :line2
8
+ attr_accessor :street
13
9
 
14
10
  # @attribute [rw] city
15
11
  # @return [String]
@@ -23,14 +19,12 @@ module PublicStorage
23
19
  # @return [String]
24
20
  attr_accessor :zip
25
21
 
26
- # @param line1 [String]
27
- # @param line2 [String]
22
+ # @param street [String]
28
23
  # @param city [String]
29
24
  # @param state [String]
30
25
  # @param zip [String]
31
- def initialize(line1:, line2:, city:, state:, zip:)
32
- @line1 = line1
33
- @line2 = line2
26
+ def initialize(street:, city:, state:, zip:)
27
+ @street = street
34
28
  @city = city
35
29
  @state = state
36
30
  @zip = zip
@@ -39,8 +33,7 @@ module PublicStorage
39
33
  # @return [String]
40
34
  def inspect
41
35
  props = [
42
- "line1=#{@line1.inspect}",
43
- "line2=#{@line2.inspect}",
36
+ "street=#{@street.inspect}",
44
37
  "city=#{@city.inspect}",
45
38
  "state=#{@state.inspect}",
46
39
  "zip=#{@zip.inspect}"
@@ -53,10 +46,9 @@ module PublicStorage
53
46
  # @return [Address]
54
47
  def self.parse(data:)
55
48
  new(
56
- line1: data['line1'],
57
- line2: data['line2'],
58
- city: data['city'],
59
- state: data['stateName'],
49
+ street: data['streetAddress'],
50
+ city: data['addressLocality'],
51
+ state: data['addressRegion'],
60
52
  zip: data['postalCode']
61
53
  )
62
54
  end
@@ -3,6 +3,8 @@
3
3
  module PublicStorage
4
4
  # The dimensions associated with a price.
5
5
  class Dimensions
6
+ SELECTOR = '.unit-size'
7
+
6
8
  # @attribute [rw] depth
7
9
  # @return [Integer]
8
10
  attr_accessor :depth
@@ -15,19 +17,13 @@ module PublicStorage
15
17
  # @return [Integer]
16
18
  attr_accessor :sqft
17
19
 
18
- # @attribute [rw] display
19
- # @return [String]
20
- attr_accessor :display
21
-
22
- # @param depth [Integer]
23
- # @param width [Integer]
20
+ # @param depth [Float]
21
+ # @param width [Float]
24
22
  # @param sqft [Integer]
25
- # @param display [String]
26
- def initialize(depth:, width:, sqft:, display:)
23
+ def initialize(depth:, width:, sqft:)
27
24
  @depth = depth
28
25
  @width = width
29
26
  @sqft = sqft
30
- @display = display
31
27
  end
32
28
 
33
29
  # @return [String]
@@ -35,17 +31,21 @@ module PublicStorage
35
31
  props = [
36
32
  "depth=#{@depth.inspect}",
37
33
  "width=#{@width.inspect}",
38
- "sqft=#{@sqft.inspect}",
39
- "display=#{@display.inspect}"
34
+ "sqft=#{@sqft.inspect}"
40
35
  ]
41
36
  "#<#{self.class.name} #{props.join(' ')}>"
42
37
  end
43
38
 
44
- # @param data [Hash]
39
+ # @param element [Nokogiri::XML::Element]
45
40
  #
46
41
  # @return [Dimensions]
47
- def self.parse(data:)
48
- new(depth: data['depth'], width: data['width'], sqft: data['squareFoot'], display: data['display'])
42
+ def self.parse(element:)
43
+ match = element.at(SELECTOR).text.match(/(?<depth>[\d\.]+)'x(?<width>[\d\.]+)'/)
44
+ depth = Float(match[:depth])
45
+ width = Float(match[:width])
46
+ sqft = Integer(depth * width)
47
+
48
+ new(depth:, width:, sqft:)
49
49
  end
50
50
  end
51
51
  end
@@ -5,6 +5,8 @@ module PublicStorage
5
5
  class Facility
6
6
  SITEMAP_URL = 'https://www.publicstorage.com/sitemap_0-product.xml'
7
7
 
8
+ PRICE_SELECTOR = '.units-results-section .unit-list-group .unit-list-item'
9
+
8
10
  # @attribute [rw] address
9
11
  # @return [Address]
10
12
  attr_accessor :address
@@ -46,21 +48,19 @@ module PublicStorage
46
48
  # @return [Facility]
47
49
  def self.fetch(url:)
48
50
  document = Crawler.html(url:)
49
- data = JSON.parse(document.at('#__NEXT_DATA__').text)
50
- parse(data:)
51
+ parse(document:)
51
52
  end
52
53
 
53
- # @param data [Hash]
54
+ # @param document [NokoGiri::XML::Document]
54
55
  #
55
56
  # @return [Facility]
56
- def self.parse(data:)
57
- page_data = data.dig('props', 'pageProps', 'pageData', 'data')
58
- facility_data = page_data.dig('facilityData', 'data')
59
- unit_classes = page_data.dig('unitClasses', 'data', 'unitClasses')
57
+ def self.parse(document:)
58
+ data = JSON.parse(document.at_css('script[type="application/ld+json"]').text)
59
+ item = data.find { |entry| entry['@type'] == 'SelfStorage' }
60
+ address = Address.parse(data: item['address'])
61
+ geocode = Geocode.parse(data: item['geo'])
60
62
 
61
- address = Address.parse(data: facility_data['store']['address'])
62
- geocode = Geocode.parse(data: facility_data['store']['geocode'])
63
- prices = unit_classes.map { |price_data| Price.parse(data: price_data) }
63
+ prices = document.css(PRICE_SELECTOR).map { |element| Price.parse(element:) }
64
64
 
65
65
  new(address:, geocode:, prices:)
66
66
  end
@@ -3,13 +3,9 @@
3
3
  module PublicStorage
4
4
  # A price associated with a unit.
5
5
  class Price
6
- # @attribute [rw] uid
6
+ # @attribute [rw] id
7
7
  # @return [String]
8
- attr_accessor :uid
9
-
10
- # @attribute [rw] availability
11
- # @return [Availability]
12
- attr_accessor :availability
8
+ attr_accessor :id
13
9
 
14
10
  # @attribute [rw] dimensions
15
11
  # @return [Dimensions]
@@ -19,13 +15,11 @@ module PublicStorage
19
15
  # @return [Rates]
20
16
  attr_accessor :rates
21
17
 
22
- # @param uid [String]
23
- # @param availability [Availability]
18
+ # @param id [String]
24
19
  # @param dimensions [Dimensions]
25
20
  # @param rates [Rates]
26
- def initialize(uid:, availability:, dimensions:, rates:)
27
- @uid = uid
28
- @availability = availability
21
+ def initialize(id:, dimensions:, rates:)
22
+ @id = id
29
23
  @dimensions = dimensions
30
24
  @rates = rates
31
25
  end
@@ -33,24 +27,22 @@ module PublicStorage
33
27
  # @return [String]
34
28
  def inspect
35
29
  props = [
36
- "uid=#{@uid.inspect}",
37
- "availability=#{@availability.inspect}",
30
+ "id=#{@id.inspect}",
38
31
  "dimensions=#{@dimensions.inspect}",
39
32
  "rates=#{@rates.inspect}"
40
33
  ]
41
34
  "#<#{self.class.name} #{props.join(' ')}>"
42
35
  end
43
36
 
44
- # @param data [Hash]
37
+ # @param element [Nokogiri::XML::Element]
45
38
  #
46
39
  # @return [Price]
47
- def self.parse(data:)
48
- availability = Availability.parse(data: data['availability'])
49
- dimensions = Dimensions.parse(data: data['dimensions'])
50
- rates = Rates.parse(data: data['rates'])
40
+ def self.parse(element:)
41
+ rates = Rates.parse(element:)
42
+ dimensions = Dimensions.parse(element:)
43
+
51
44
  new(
52
- uid: data['uid'],
53
- availability: availability,
45
+ id: element.attr('data-unitid'),
54
46
  dimensions: dimensions,
55
47
  rates: rates
56
48
  )
@@ -3,9 +3,8 @@
3
3
  module PublicStorage
4
4
  # The rates associated with a price
5
5
  class Rates
6
- # @attribute [rw] nsc
7
- # @return [Integer]
8
- attr_accessor :nsc
6
+ STREET_SELECTOR = '.unit-prices .unit-pricing .unit-strike-through-price'
7
+ WEB_SELECTOR = '.unit-prices .unit-pricing .unit-price'
9
8
 
10
9
  # @attribute [rw] street
11
10
  # @return [Integer]
@@ -15,11 +14,9 @@ module PublicStorage
15
14
  # @return [Integer]
16
15
  attr_accessor :web
17
16
 
18
- # @param nsc [Integer]
19
17
  # @param street [Integer]
20
18
  # @param web [Integer]
21
- def initialize(nsc:, street:, web:)
22
- @nsc = nsc
19
+ def initialize(street:, web:)
23
20
  @street = street
24
21
  @web = web
25
22
  end
@@ -27,22 +24,19 @@ module PublicStorage
27
24
  # @return [String]
28
25
  def inspect
29
26
  props = [
30
- "nsc=#{@nsc.inspect}",
31
27
  "street=#{@street.inspect}",
32
28
  "web=#{@web.inspect}"
33
29
  ]
34
30
  "#<#{self.class.name} #{props.join(' ')}>"
35
31
  end
36
32
 
37
- # @param data [Hash]
33
+ # @param element [Nokogiri::XML::Element]
38
34
  #
39
35
  # @return [Rates]
40
- def self.parse(data:)
41
- new(
42
- nsc: data['nsc'],
43
- street: data['street'],
44
- web: data['web']
45
- )
36
+ def self.parse(element:)
37
+ street = Integer(element.at(STREET_SELECTOR).text.match(/(?<value>\d+)/)[:value])
38
+ web = Integer(element.at(WEB_SELECTOR).text.match(/(?<value>\d+)/)[:value])
39
+ new(street:, web:)
46
40
  end
47
41
  end
48
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PublicStorage
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.2'
5
5
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: publicstorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
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-11-27 00:00:00.000000000 Z
@@ -79,7 +79,6 @@ files:
79
79
  - bin/setup
80
80
  - lib/publicstorage.rb
81
81
  - lib/publicstorage/address.rb
82
- - lib/publicstorage/availability.rb
83
82
  - lib/publicstorage/crawler.rb
84
83
  - lib/publicstorage/dimensions.rb
85
84
  - lib/publicstorage/facility.rb
@@ -97,7 +96,7 @@ metadata:
97
96
  homepage_uri: https://github.com/ksylvest/publicstorage
98
97
  source_code_uri: https://github.com/ksylvest/publicstorage
99
98
  changelog_uri: https://github.com/ksylvest/publicstorage
100
- post_install_message:
99
+ post_install_message:
101
100
  rdoc_options: []
102
101
  require_paths:
103
102
  - lib
@@ -112,8 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
111
  - !ruby/object:Gem::Version
113
112
  version: '0'
114
113
  requirements: []
115
- rubygems_version: 3.5.22
116
- signing_key:
114
+ rubygems_version: 3.5.23
115
+ signing_key:
117
116
  specification_version: 4
118
117
  summary: A crawler for PublicStorage.
119
118
  test_files: []
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PublicStorage
4
- # The availability associated with a price.
5
- class Availability
6
- # @attribute [rw] available
7
- # @return [String]
8
- attr_accessor :available
9
-
10
- # @param available [String]
11
- def initialize(available:)
12
- @available = available
13
- end
14
-
15
- # @return [String]
16
- def inspect
17
- props = [
18
- "available=#{@available.inspect}"
19
- ]
20
- "#<#{self.class.name} #{props.join(' ')}>"
21
- end
22
-
23
- # @param data [Hash]
24
- #
25
- # @return [Availability]
26
- def self.parse(data:)
27
- new(available: data['available'])
28
- end
29
- end
30
- end