publicstorage 0.1.0 → 0.1.2

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: 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