uhaul 0.1.0 → 1.0.3

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