uhaul 0.1.0 → 1.0.3

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: 70a37ead64b21ccff91595e73dfedf11b378c2e5ea14acdd0d18e9d45735e3a5
4
- data.tar.gz: d355639b46eb03ce4ce4be447520f05551ecfc8f23f7ebacc8260a6bb5d71203
3
+ metadata.gz: 4acc4dc138e904232902f124d9ee0c78923dc74c609c42515b97e83cdeea3fb2
4
+ data.tar.gz: 9e98885feb0367400d6a444f191b859d2e54abf646c9dda26938545f0d2bf8c6
5
5
  SHA512:
6
- metadata.gz: b73ef2ae65031b627177dd1b8c5fa647ce6273b3f09efb8975a42182d6be90e05d6f6e27c5c50dfae3c836f81cd48e1211fd57bf2918081cc6664999b3daef24
7
- data.tar.gz: bec905b2070dd9a60d0d32b1f3f3de1fbe184d27f495152524911f90d9eff9c1b3c2b6b833b1e3f3cb26960de25a606396d3ca0b9dcf9292f42a1498bed1fc98
6
+ metadata.gz: 3ecf98d2ab243fe983f6ef926c6fd425d988676173892a92990845b489561de61525ea30653d66c1b9d5956842006901064847c46aba4c5b9894c8f1a2846940
7
+ data.tar.gz: dde69a5ed918065d156872112b37b6d67b6f04a462f18f492da53ca7ecd56224ad38d1659f9e0b2bfa86f7e2f79684a0f7cdf391e1dbbece994e80dfec480e40
data/README.md CHANGED
@@ -18,9 +18,9 @@ gem install uhaul
18
18
  require 'uhaul'
19
19
 
20
20
  UHaul.configure do |config|
21
- config.user_agent = '../..' # ENV['NSASTORAGE_USER_AGENT']
22
- config.timeout = 30 # ENV['NSASTORAGE_TIMEOUT']
23
- config.proxy_url = 'http://user:pass@superproxy.zenrows.com:1337' # ENV['NSASTORAGE_PROXY_URL']
21
+ config.user_agent = '../..' # ENV['UHAUL_USER_AGENT']
22
+ config.timeout = 30 # ENV['UHAUL_TIMEOUT']
23
+ config.proxy_url = 'http://user:pass@superproxy.zenrows.com:1337' # ENV['UHAUL_PROXY_URL']
24
24
  end
25
25
  ```
26
26
 
data/lib/uhaul/config.rb CHANGED
@@ -20,10 +20,10 @@ module UHaul
20
20
  attr_accessor :proxy_url
21
21
 
22
22
  def initialize
23
- @accept_language = ENV.fetch('NSASTORAGE_ACCEPT_LANGUAGE', 'en-US,en;q=0.9')
24
- @user_agent = ENV.fetch('NSASTORAGE_USER_AGENT', "uhaul.rb/#{VERSION}")
25
- @timeout = Integer(ENV.fetch('NSASTORAGE_TIMEOUT', 60))
26
- @proxy_url = ENV.fetch('NSASTORAGE_PROXY_URL', nil)
23
+ @accept_language = ENV.fetch('UHAUL_ACCEPT_LANGUAGE', 'en-US,en;q=0.9')
24
+ @user_agent = ENV.fetch('UHAUL_USER_AGENT', "uhaul.rb/#{VERSION}")
25
+ @timeout = Integer(ENV.fetch('UHAUL_TIMEOUT', 60))
26
+ @proxy_url = ENV.fetch('UHAUL_PROXY_URL', nil)
27
27
  end
28
28
 
29
29
  # @return [Boolean]
data/lib/uhaul/crawler.rb CHANGED
@@ -43,7 +43,7 @@ module UHaul
43
43
  # @param url [String]
44
44
  # @return [HTTP::Response]
45
45
  def fetch(url:)
46
- response = connection.get(url)
46
+ response = connection.follow.get(url)
47
47
  raise FetchError.new(url:, response: response.flush) unless response.status.ok?
48
48
 
49
49
  response
@@ -3,11 +3,9 @@
3
3
  module UHaul
4
4
  # The dimensions (width + depth + sqft) of a price.
5
5
  class Dimensions
6
- DEFAULT_WIDTH = 5.0 # feet
7
- DEFAULT_DEPTH = 5.0 # feet
8
- DEFAULT_HEIGHT = 8.0 # feet
6
+ class ParseError < StandardError; end
9
7
 
10
- DIMENSIONS_REGEX = /(?<width>[\d\.]+) x (?<depth>[\d\.]+)/
8
+ DIMENSIONS_REGEX = /(?<width>[\d\.]+)'\s*x\s*(?<depth>[\d\.]+)'\s*x\s*(?<height>[\d\.]+)'/
11
9
 
12
10
  # @attribute [rw] depth
13
11
  # @return [Float]
@@ -40,11 +38,6 @@ module UHaul
40
38
  "#<#{self.class.name} #{props.join(' ')}>"
41
39
  end
42
40
 
43
- # @return [String] e.g. "5×5"
44
- def id
45
- "#{format('%g', @width)}×#{format('%g', @depth)}"
46
- end
47
-
48
41
  # @return [Integer]
49
42
  def sqft
50
43
  Integer(@width * @depth)
@@ -60,16 +53,17 @@ module UHaul
60
53
  "#{format('%g', @width)}' × #{format('%g', @depth)}' (#{sqft} sqft)"
61
54
  end
62
55
 
63
- # @param element [Nokogiri::XML::Element]
56
+ # @param text [String]
64
57
  #
65
58
  # @return [Dimensions]
66
- def self.parse(element:)
67
- text = element.at_css('.unit-select-item-detail').text
59
+ def self.parse(text:)
68
60
  match = DIMENSIONS_REGEX.match(text)
61
+ raise ParseError, "unknown length / width / height for #{text}" unless match
69
62
 
70
- width = match ? Float(match[:width]) : DEFAULT_WIDTH
71
- depth = match ? Float(match[:depth]) : DEFAULT_DEPTH
72
- new(depth:, width:, height: DEFAULT_HEIGHT)
63
+ width = Float(match[:width])
64
+ depth = Float(match[:depth])
65
+ height = Float(match[:height])
66
+ new(depth:, width:, height:)
73
67
  end
74
68
  end
75
69
  end
@@ -7,6 +7,8 @@ module UHaul
7
7
  class Facility
8
8
  class ParseError < StandardError; end
9
9
 
10
+ PRICES_SELECTOR = '#roomTypes > ul:not([id*="VehicleStorage"]) > li'
11
+
10
12
  SITEMAP_URLS = %w[
11
13
  https://www.uhaul.com/Locations/Sitemaps/Sitemap-for-Storage-in-AL.ashx
12
14
  https://www.uhaul.com/Locations/Sitemaps/Sitemap-for-Storage-in-AK.ashx
@@ -62,8 +64,7 @@ module UHaul
62
64
  ].freeze
63
65
 
64
66
  DEFAULT_EMAIL = 'service@uhaul.com'
65
- DEFAULT_PHONE = '+1-800-468-4285
66
- '
67
+ DEFAULT_PHONE = '+1-800-468-4285'
67
68
 
68
69
  # @attribute [rw] id
69
70
  # @return [String]
@@ -130,7 +131,7 @@ module UHaul
130
131
 
131
132
  geocode = Geocode.parse(data: data['geo'])
132
133
  address = Address.parse(data: data['address'])
133
- prices = [] # TODO
134
+ prices = document.css(PRICES_SELECTOR).map { |element| Price.parse(element:) }.compact
134
135
 
135
136
  new(id:, url:, name:, address:, geocode:, prices:)
136
137
  end
@@ -3,15 +3,13 @@
3
3
  module UHaul
4
4
  # The features (e.g. climate-controlled, inside-drive-up-access, outside-drive-up-access, etc) of a price.
5
5
  class Features
6
- # @param element [Nokogiri::XML::Element]
6
+ # @param text [String]
7
7
  #
8
8
  # @return [Features]
9
- def self.parse(element:)
10
- text = element.text
11
-
9
+ def self.parse(text:)
12
10
  new(
13
- climate_controlled: text.include?('Heated and Cooled'),
14
- drive_up_access: text.include?('Drive Up Access'),
11
+ climate_controlled: text.include?('Climate') && !text.include?('No Climate'),
12
+ drive_up_access: text.include?('Drive Up'),
15
13
  first_floor_access: text.include?('1st Floor')
16
14
  )
17
15
  end
@@ -36,15 +34,6 @@ module UHaul
36
34
  "#<#{self.class.name} #{props.join(' ')}>"
37
35
  end
38
36
 
39
- # @return [String] e.g. ""
40
- def id
41
- [].tap do |ids|
42
- ids << 'cc' if climate_controlled?
43
- ids << 'dua' if drive_up_access?
44
- ids << 'ffa' if first_floor_access?
45
- end.join('-')
46
- end
47
-
48
37
  # @return [String] e.g. "Climate Controlled + First Floor Access"
49
38
  def text
50
39
  amenities.join(' + ')
data/lib/uhaul/geocode.rb CHANGED
@@ -18,8 +18,8 @@ module UHaul
18
18
  #
19
19
  # @return [Geocode]
20
20
  def self.parse(data:)
21
- latitude = data[:latitude]
22
- longitude = data[:longitude]
21
+ latitude = Float(data['latitude'])
22
+ longitude = Float(data['longitude'])
23
23
 
24
24
  new(latitude:, longitude:)
25
25
  end
data/lib/uhaul/link.rb CHANGED
@@ -8,7 +8,6 @@ module UHaul
8
8
  attr_accessor :loc
9
9
 
10
10
  # @param loc [String]
11
- # @param lastmod [String, nil]
12
11
  def initialize(loc:)
13
12
  @loc = loc
14
13
  end
data/lib/uhaul/price.rb CHANGED
@@ -22,18 +22,6 @@ module UHaul
22
22
  # @return [Rates]
23
23
  attr_accessor :rates
24
24
 
25
- # @param facility_id [Integer]
26
- #
27
- # @return [Array<Price>]
28
- def self.fetch(facility_id:)
29
- url = "https://www.uhaul.com/facility-units/#{facility_id}"
30
- data = Crawler.json(url:)['data']
31
- return [] if data['error']
32
-
33
- html = data['html']['units']
34
- Nokogiri::HTML(html).css(PRICE_SELECTOR).map { |element| parse(element:) }
35
- end
36
-
37
25
  # @param id [String]
38
26
  # @param dimensions [Dimensions]
39
27
  # @param features [Features]
@@ -56,7 +44,7 @@ module UHaul
56
44
  "#<#{self.class.name} #{props.join(' ')}>"
57
45
  end
58
46
 
59
- # @return [String] e.g. "123 | 5' × 5' (25 sqft) | $100 (street) / $90 (web)"
47
+ # @return [String] e.g. "123 | 5' × 5' (25 sqft) | $90"
60
48
  def text
61
49
  "#{@id} | #{@dimensions.text} | #{@rates.text} | #{@features.text}"
62
50
  end
@@ -65,12 +53,15 @@ module UHaul
65
53
  #
66
54
  # @return [Price]
67
55
  def self.parse(element:)
68
- link = element.at_xpath(".//a[contains(text(), 'Rent')]|//a[contains(text(), 'Reserve')]")
69
- dimensions = Dimensions.parse(element:)
70
- features = Features.parse(element:)
71
- rates = Rates.parse(element:)
56
+ id_element = element.at_css('form input[name="RentableInventoryPk"]')
57
+ return unless id_element
58
+
59
+ id = id_element['value']
60
+ text = element.text.strip.gsub(/\s+/, ' ')
72
61
 
73
- id = link ? ID_REGEX.match(link['href'])[:id] : "#{dimensions.id}-#{features.id}"
62
+ dimensions = Dimensions.parse(text:)
63
+ features = Features.parse(text:)
64
+ rates = Rates.parse(text:)
74
65
 
75
66
  new(id:, dimensions:, features:, rates:)
76
67
  end
data/lib/uhaul/rates.rb CHANGED
@@ -3,57 +3,38 @@
3
3
  module UHaul
4
4
  # The rates (street + web) for a facility
5
5
  class Rates
6
- STREET_SELECTOR = '.part_item_old_price'
7
- WEB_SELECTOR = '.part_item_price'
8
- VALUE_REGEX = /(?<value>[\d\.]+)/
6
+ RATE_REGEX = /\$(?<price>[\d\.\,]+) Per Month/
9
7
 
10
- # @attribute [rw] street
8
+ # @attribute [rw] rate
11
9
  # @return [Integer]
12
- attr_accessor :street
10
+ attr_accessor :price
13
11
 
14
- # @attribute [rw] web
15
- # @return [Integer]
16
- attr_accessor :web
12
+ alias street price
13
+ alias web price
17
14
 
18
- # @param element [Nokogiri::XML::Element]
15
+ # @param text [String]
19
16
  #
20
17
  # @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?
18
+ def self.parse(text:)
19
+ price = Float(RATE_REGEX.match(text)[:price].gsub(',', ''))
33
20
 
34
- match = VALUE_REGEX.match(element.text)
35
- Float(match[:value]) if match
21
+ new(price:)
36
22
  end
37
23
 
38
24
  # @param street [Integer]
39
25
  # @param web [Integer]
40
- def initialize(street:, web:)
41
- @street = street
42
- @web = web
26
+ def initialize(price:)
27
+ @price = price
43
28
  end
44
29
 
45
30
  # @return [String]
46
31
  def inspect
47
- props = [
48
- "street=#{@street.inspect}",
49
- "web=#{@web.inspect}"
50
- ]
51
- "#<#{self.class.name} #{props.join(' ')}>"
32
+ "#<#{self.class.name} price=#{@price.inspect}>"
52
33
  end
53
34
 
54
- # @return [String] e.g. "$80 (street) | $60 (web)"
35
+ # @return [String] e.g. "$80"
55
36
  def text
56
- "$#{@street} (street) | $#{@web} (web)"
37
+ "$#{@price}"
57
38
  end
58
39
  end
59
40
  end
data/lib/uhaul/sitemap.rb CHANGED
@@ -16,7 +16,7 @@ module UHaul
16
16
  Link.new(loc:)
17
17
  end
18
18
 
19
- new(links: links.filter { |link| link.loc.match(%r{/Self-Storage/.*/\d+}) })
19
+ new(links: links.filter { |link| link.loc.match(%r{/Self-Storage-.*/\d+}) })
20
20
  end
21
21
 
22
22
  # @param url [String]
data/lib/uhaul/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UHaul
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uhaul
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.3
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-17 00:00:00.000000000 Z
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -117,7 +117,7 @@ metadata:
117
117
  homepage_uri: https://github.com/ksylvest/uhaul
118
118
  source_code_uri: https://github.com/ksylvest/uhaul
119
119
  changelog_uri: https://github.com/ksylvest/uhaul
120
- post_install_message:
120
+ post_install_message:
121
121
  rdoc_options: []
122
122
  require_paths:
123
123
  - lib
@@ -132,8 +132,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  - !ruby/object:Gem::Version
133
133
  version: '0'
134
134
  requirements: []
135
- rubygems_version: 3.5.22
136
- signing_key:
135
+ rubygems_version: 3.5.23
136
+ signing_key:
137
137
  specification_version: 4
138
138
  summary: A crawler for UHaul.
139
139
  test_files: []