berkeley_library-util 0.1.4 → 0.1.5

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: 57ff7fc89570ad61496f1587934e40dc18752df9cd99d766f591c4fe03fcde51
4
- data.tar.gz: e0bc0403c067c04801683e12de79a569721332ac23c770a05a0a0fde41842214
3
+ metadata.gz: 9eac060e7b0956b965455bddb8cc6c9721d5af784ac421766c2d791d99632927
4
+ data.tar.gz: a68fecb0b685ddffa3cb0fff6a46180e377efeb71b1cc5d23a389117c27be72d
5
5
  SHA512:
6
- metadata.gz: 6f7786f78fd64266dab9e424f61c03a95dc5a7eb294990bfef1d124699d5e0f6db7b53eaa2d3ca84c77eea1e096b11a5d227c679f5994699973b6274e3d6afe0
7
- data.tar.gz: a8a61db572a00d9d0ca6d41f23d2c76490196c14a6fdd8202b88d1f924fd5f9a32ebbea0e7811a4fba2a49a6fcec4ac40242cd79dfbaec8f37deaff046650436
6
+ metadata.gz: 052c93d1c09d569479d4bec70ffa9413e3ab0404f19482955b89552891c7058ab76ec11191456aa79016e1e2c98403eb355c89982ffe31f31d37f650517970b2
7
+ data.tar.gz: 530542deae99ffbd089cfc294b614d9560e2298ed4f7a20161970185935155a620b578bd99066a219e7d0410305d525e354c9b811162adbefdf3dda35c904427
data/.idea/util.iml CHANGED
@@ -8,9 +8,9 @@
8
8
  <content url="file://$MODULE_DIR$" />
9
9
  <orderEntry type="jdk" jdkName="RVM: ruby-2.7.4" jdkType="RUBY_SDK" />
10
10
  <orderEntry type="sourceFolder" forTests="false" />
11
- <orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
12
- <orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
13
- <orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
14
14
  <orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
15
15
  <orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.4.0, RVM: ruby-2.7.4) [gem]" level="application" />
16
16
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -35,13 +35,13 @@
35
35
  <orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.0.4, RVM: ruby-2.7.4) [gem]" level="application" />
36
36
  <orderEntry type="library" scope="PROVIDED" name="i18n (v1.10.0, RVM: ruby-2.7.4) [gem]" level="application" />
37
37
  <orderEntry type="library" scope="PROVIDED" name="lograge (v0.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
38
- <orderEntry type="library" scope="PROVIDED" name="loofah (v2.17.0, RVM: ruby-2.7.4) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="loofah (v2.18.0, RVM: ruby-2.7.4) [gem]" level="application" />
39
39
  <orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
40
40
  <orderEntry type="library" scope="PROVIDED" name="mime-types (v3.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
41
41
  <orderEntry type="library" scope="PROVIDED" name="mime-types-data (v3.2022.0105, RVM: ruby-2.7.4) [gem]" level="application" />
42
42
  <orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.4) [gem]" level="application" />
43
43
  <orderEntry type="library" scope="PROVIDED" name="netrc (v0.11.0, RVM: ruby-2.7.4) [gem]" level="application" />
44
- <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.5, RVM: ruby-2.7.4) [gem]" level="application" />
44
+ <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.6, RVM: ruby-2.7.4) [gem]" level="application" />
45
45
  <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.4) [gem]" level="application" />
46
46
  <orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.4) [gem]" level="application" />
47
47
  <orderEntry type="library" scope="PROVIDED" name="parallel (v1.22.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -52,7 +52,7 @@
52
52
  <orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
53
53
  <orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
54
54
  <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
55
- <orderEntry type="library" scope="PROVIDED" name="railties (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
55
+ <orderEntry type="library" scope="PROVIDED" name="railties (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
56
56
  <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.4) [gem]" level="application" />
57
57
  <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.4) [gem]" level="application" />
58
58
  <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.3.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -98,7 +98,7 @@
98
98
  </RakeTaskImpl>
99
99
  <RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
100
100
  <RakeTaskImpl description="Run tests, check test coverage, check code style, check for vulnerabilities, build gem" fullCommand="default" id="default" />
101
- <RakeTaskImpl description="Build berkeley_library-util.gemspec as berkeley_library-util-0.1.1.gem" fullCommand="gem" id="gem" />
101
+ <RakeTaskImpl description="Build berkeley_library-util.gemspec as berkeley_library-util-0.1.4.gem" fullCommand="gem" id="gem" />
102
102
  <RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
103
103
  <RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
104
104
  <RakeTaskImpl id="rubocop">
data/.rubocop.yml CHANGED
@@ -55,6 +55,10 @@ Style/CommentedKeyword:
55
55
  Style/Documentation:
56
56
  Enabled: false
57
57
 
58
+ # You say "cryptic", I say "compact"
59
+ Style/FormatString:
60
+ EnforcedStyle: percent
61
+
58
62
  # Adding more line noise to format strings will not improve them
59
63
  Style/FormatStringToken:
60
64
  Enabled: false
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.1.5 (2022-09-16)
2
+
3
+ - Adds `URIs#path_escape` to escape URL path segments
4
+ - Adds `URIs#head` and `URIs#head_response`, to make a HEAD request and return the HTTP status code
5
+ and raw response object, respectively
6
+ - Clarifies documentation for `URIs#get` and `URIs#get_response` regarding unsuccessful requests
7
+
1
8
  # 0.1.4 (2022-07-20)
2
9
 
3
10
  - Adds `URIs#safe_parse_uri`, which returns `nil` for invalid URLs (unlike `URIs#uri_or_nil`, which
@@ -7,7 +7,7 @@ module BerkeleyLibrary
7
7
  SUMMARY = 'Miscellaneous Ruby utilities for the UC Berkeley Library'.freeze
8
8
  DESCRIPTION = 'A collection of miscellaneous Ruby routines for the UC Berkeley Library.'.freeze
9
9
  LICENSE = 'MIT'.freeze
10
- VERSION = '0.1.4'.freeze
10
+ VERSION = '0.1.5'.freeze
11
11
  HOMEPAGE = 'https://github.com/BerkeleyLibrary/util'.freeze
12
12
  end
13
13
  end
@@ -16,29 +16,56 @@ module BerkeleyLibrary
16
16
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
17
17
  # @param headers [Hash] the request headers.
18
18
  # @return [String] the body as a string.
19
- # @raise [RestClient::Exception] in the event of an error.
19
+ # @raise [RestClient::Exception] in the event of an unsuccessful request.
20
20
  def get(uri, params: {}, headers: {})
21
- resp = make_get_request(uri, params, headers)
21
+ resp = make_request(:get, uri, params, headers)
22
22
  resp.body
23
23
  end
24
24
 
25
- # Performs a GET request and returns the response.
25
+ # Performs a HEAD request and returns the response status as an integer.
26
+ # Note that unlike {Requester#get}, this does not raise an error in the
27
+ # event of an unsuccessful request.
28
+ #
29
+ # @param uri [URI, String] the URI to HEAD
30
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
31
+ # @param headers [Hash] the request headers.
32
+ # @return [Integer] the response code as an integer.
33
+ def head(uri, params: {}, headers: {})
34
+ head_response(uri, params: params, headers: headers).code
35
+ end
36
+
37
+ # Performs a GET request and returns the response, even in the event of
38
+ # a failed request.
26
39
  #
27
40
  # @param uri [URI, String] the URI to GET
28
41
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
29
42
  # @param headers [Hash] the request headers.
30
43
  # @return [RestClient::Response] the body as a string.
31
44
  def get_response(uri, params: {}, headers: {})
32
- make_get_request(uri, params, headers)
45
+ make_request(:get, uri, params, headers)
46
+ rescue RestClient::Exception => e
47
+ e.response
48
+ end
49
+
50
+ # Performs a HEAD request and returns the response, even in the event of
51
+ # a failed request.
52
+ #
53
+ # @param uri [URI, String] the URI to HEAD
54
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
55
+ # @param headers [Hash] the request headers.
56
+ # @return [RestClient::Response] the body as a string.
57
+ def head_response(uri, params: {}, headers: {})
58
+ make_request(:head, uri, params, headers)
33
59
  rescue RestClient::Exception => e
34
60
  e.response
35
61
  end
36
62
 
37
63
  private
38
64
 
39
- def make_get_request(uri, params, headers)
65
+ # @return [RestClient::Response]
66
+ def make_request(method, uri, params, headers)
40
67
  url_str = url_str_with_params(uri, params)
41
- get_or_raise(url_str, headers)
68
+ req_resp_or_raise(method, url_str, headers)
42
69
  end
43
70
 
44
71
  def url_str_with_params(uri, params)
@@ -57,15 +84,15 @@ module BerkeleyLibrary
57
84
  end
58
85
 
59
86
  # @return [RestClient::Response]
60
- def get_or_raise(url_str, headers)
61
- resp = RestClient.get(url_str, headers)
87
+ def req_resp_or_raise(method, url_str, headers)
88
+ resp = RestClient::Request.execute(method: method, url: url_str, headers: headers)
62
89
  begin
63
90
  return resp if (status = resp.code) == 200
64
91
 
65
92
  raise(exception_for(resp, status))
66
93
  ensure
67
94
  # noinspection RubyMismatchedReturnType
68
- logger.info("GET #{url_str} returned #{status}")
95
+ logger.info("#{method.to_s.upcase} #{url_str} returned #{status}")
69
96
  end
70
97
  end
71
98
 
@@ -8,6 +8,8 @@ module BerkeleyLibrary
8
8
  module URIs
9
9
  include BerkeleyLibrary::Logging
10
10
 
11
+ UTF_8 = Encoding::UTF_8
12
+
11
13
  class << self
12
14
  include URIs
13
15
  end
@@ -24,18 +26,31 @@ module BerkeleyLibrary
24
26
  Appender.new(uri, *elements).to_uri
25
27
  end
26
28
 
27
- # Performs a GET request.
29
+ # Performs a GET request and returns the response body as a string.
28
30
  #
29
31
  # @param uri [URI, String] the URI to GET
30
32
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
31
33
  # @param headers [Hash] the request headers.
32
34
  # @return [String] the body as a string.
33
- # @raise [RestClient::Exception] in the event of an error.
35
+ # @raise [RestClient::Exception] in the event of an unsuccessful request.
34
36
  def get(uri, params: {}, headers: {})
35
37
  Requester.get(uri, params: params, headers: headers)
36
38
  end
37
39
 
38
- # Performs a GET request and returns the response.
40
+ # Performs a HEAD request and returns the response status as an integer.
41
+ # Note that unlike {Requester#get}, this does not raise an error in the
42
+ # event of an unsuccessful request.
43
+ #
44
+ # @param uri [URI, String] the URI to HEAD
45
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
46
+ # @param headers [Hash] the request headers.
47
+ # @return [Integer] the response code as an integer.
48
+ def head(uri, params: {}, headers: {})
49
+ Requester.head(uri, params: params, headers: headers)
50
+ end
51
+
52
+ # Performs a GET request and returns the response, even in the event of
53
+ # a failed request.
39
54
  #
40
55
  # @param uri [URI, String] the URI to GET
41
56
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
@@ -45,6 +60,17 @@ module BerkeleyLibrary
45
60
  Requester.get_response(uri, params: params, headers: headers)
46
61
  end
47
62
 
63
+ # Performs a HEAD request and returns the response, even in the event of
64
+ # a failed request.
65
+ #
66
+ # @param uri [URI, String] the URI to HEAD
67
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
68
+ # @param headers [Hash] the request headers.
69
+ # @return [RestClient::Response] the body as a string.
70
+ def head_response(uri, params: {}, headers: {})
71
+ Requester.head_response(uri, params: params, headers: headers)
72
+ end
73
+
48
74
  # Returns the specified URL as a URI, or `nil` if the URL is `nil`.
49
75
  # @param url [String, URI, nil] the URL.
50
76
  # @return [URI] the URI, or `nil`.
@@ -54,6 +80,19 @@ module BerkeleyLibrary
54
80
  Validator.uri_or_nil(url)
55
81
  end
56
82
 
83
+ # Escapes the specified string so that it can be used as a URL path segment,
84
+ # replacing disallowed characters (including /) with percent-encodings as needed.
85
+ def path_escape(s)
86
+ raise ArgumentError, "Can't escape #{s.inspect}: not a string" unless s.respond_to?(:encoding)
87
+ raise ArgumentError, "Can't escape #{s.inspect}: expected #{UTF_8}, was #{s.encoding}" unless s.encoding == UTF_8
88
+
89
+ ''.tap do |escaped|
90
+ s.bytes.each do |b|
91
+ escaped << (should_escape?(b, :path_segment) ? '%%%02X' % b : b.chr)
92
+ end
93
+ end
94
+ end
95
+
57
96
  # Returns the specified URL as a URI, or `nil` if the URL cannot
58
97
  # be parsed.
59
98
  # @param url [Object, nil] the URL.
@@ -65,6 +104,32 @@ module BerkeleyLibrary
65
104
  logger.warn("Error parsing URL #{url.inspect}", e)
66
105
  nil
67
106
  end
107
+
108
+ private
109
+
110
+ # TODO: extend to cover other modes - host, zone, path, password, query, fragment
111
+ # cf. https://github.com/golang/go/blob/master/src/net/url/url.go
112
+ ALLOWED_BYTES_BY_MODE = {
113
+ path_segment: [0x24, 0x26, 0x2b, 0x3a, 0x3d, 0x40] # @ & = + $
114
+ }.freeze
115
+
116
+ def should_escape?(b, mode)
117
+ return false if unreserved?(b)
118
+ return false if ALLOWED_BYTES_BY_MODE[mode]&.include?(b)
119
+
120
+ true
121
+ end
122
+
123
+ # rubocop:disable Metrics/CyclomaticComplexity
124
+ def unreserved?(byte)
125
+ return true if byte >= 0x41 && byte <= 0x5a # A-Z
126
+ return true if byte >= 0x61 && byte <= 0x7a # a-z
127
+ return true if byte >= 0x30 && byte <= 0x39 # 0-9
128
+ return true if [0x2d, 0x2e, 0x5f, 0x7e].include?(byte) # - . _ ~
129
+
130
+ false
131
+ end
132
+ # rubocop:enable Metrics/CyclomaticComplexity
68
133
  end
69
134
  end
70
135
  end
@@ -66,6 +66,70 @@ module BerkeleyLibrary
66
66
  expect(result).to eq(expected_body)
67
67
  end
68
68
  end
69
+
70
+ describe :head do
71
+ it 'returns an HTTP status code for a URL string' do
72
+ url = 'https://example.org/'
73
+ expected_status = 203
74
+ stub_request(:head, url).to_return(status: expected_status)
75
+
76
+ result = Requester.head(url)
77
+ expect(result).to eq(expected_status)
78
+ end
79
+
80
+ it 'returns an HTTP response body for a URI' do
81
+ uri = URI.parse('https://example.org/')
82
+ expected_status = 203
83
+ stub_request(:head, uri).to_return(status: expected_status)
84
+
85
+ result = Requester.head(uri)
86
+ expect(result).to eq(expected_status)
87
+ end
88
+
89
+ it 'appends query parameters' do
90
+ url = 'https://example.org/'
91
+ params = { p1: 1, p2: 2 }
92
+ url_with_query = "#{url}?#{URI.encode_www_form(params)}"
93
+ expected_status = 203
94
+ stub_request(:head, url_with_query).to_return(status: expected_status)
95
+
96
+ result = Requester.head(url, params: params)
97
+ expect(result).to eq(expected_status)
98
+ end
99
+
100
+ it 'sends request headers' do
101
+ url = 'https://example.org/'
102
+ headers = { 'X-help' => 'I am trapped in a unit test' }
103
+ expected_status = 203
104
+ stub_request(:head, url).with(headers: headers).to_return(status: expected_status)
105
+
106
+ result = Requester.head(url, headers: headers)
107
+ expect(result).to eq(expected_status)
108
+ end
109
+
110
+ it 'returns the status even for unsuccessful requests' do
111
+ aggregate_failures 'responses' do
112
+ [207, 400, 401, 403, 404, 405, 418, 451, 500, 503].each do |expected_status|
113
+ url = "http://example.edu/#{expected_status}"
114
+ stub_request(:head, url).to_return(status: expected_status)
115
+
116
+ result = Requester.head(url)
117
+ expect(result).to eq(expected_status)
118
+ end
119
+ end
120
+ end
121
+
122
+ it 'handles redirects' do
123
+ url1 = 'https://example.org/'
124
+ url2 = 'https://example.edu/'
125
+ stub_request(:head, url1).to_return(status: 302, headers: { 'Location' => url2 })
126
+ expected_status = 203
127
+ stub_request(:head, url2).to_return(status: expected_status)
128
+
129
+ result = Requester.head(url1)
130
+ expect(result).to eq(expected_status)
131
+ end
132
+ end
69
133
  end
70
134
  end
71
135
  end
@@ -16,16 +16,14 @@ module BerkeleyLibrary::Util
16
16
  expect(new_uri).to eq(expected_uri)
17
17
  end
18
18
 
19
- # TODO: make this work
20
- xit "doesn't append to a bare URI when there's nothing to append" do
19
+ it 'returns a bare URI when there\'s nothing to append' do
21
20
  original_url = 'https://example.org'
22
21
  new_uri = URIs.append(original_url)
23
22
  expected_uri = URI(original_url)
24
23
  expect(new_uri).to eq(expected_uri)
25
24
  end
26
25
 
27
- # TODO: make this work
28
- xit "doesn't append to a bare URI when there's only a query string" do
26
+ it 'appends to a bare URI even when there\'s only a query string' do
29
27
  original_url = 'https://example.org'
30
28
  new_uri = URIs.append(original_url, '?foo=bar')
31
29
  expected_uri = URI("#{original_url}?foo=bar")
@@ -136,6 +134,19 @@ module BerkeleyLibrary::Util
136
134
  new_uri = URIs.append(original_uri, '?qux=corge', '&grault=plugh#xyzzy')
137
135
  expect(new_uri).to eq(URI('https://example.org/foo/bar?qux=corge&grault=plugh#xyzzy'))
138
136
  end
137
+
138
+ it 'rejects invalid characters' do
139
+ original_uri = URI('https://example.org/')
140
+ expect { URIs.append(original_uri, '精力善用') }.to raise_error(URI::InvalidComponentError)
141
+ end
142
+
143
+ it 'accepts percent-encoded path segments' do
144
+ original_uri = URI('https://example.org/')
145
+ encoded_segment = URIs.path_escape('精力善用')
146
+ new_uri = URIs.append(original_uri, encoded_segment, 'foo.html')
147
+ expected_url = "https://example.org/#{encoded_segment}/foo.html"
148
+ expect(new_uri).to eq(URI(expected_url))
149
+ end
139
150
  end
140
151
 
141
152
  describe 'requests' do
@@ -177,6 +188,42 @@ module BerkeleyLibrary::Util
177
188
  expect(response.code).to eq(404)
178
189
  end
179
190
  end
191
+
192
+ describe :head do
193
+ it 'makes a HEAD request' do
194
+ expected_status = 200
195
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: expected_status)
196
+
197
+ result = URIs.head(url, params: params, headers: headers)
198
+ expect(result).to eq(expected_status)
199
+ end
200
+
201
+ it 'returns the status even for unsuccessful requests' do
202
+ expected_status = 404
203
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: expected_status)
204
+
205
+ result = URIs.head(url, params: params, headers: headers)
206
+ expect(result).to eq(expected_status)
207
+ end
208
+ end
209
+
210
+ describe :head_response do
211
+ it 'makes a HEAD request' do
212
+ stub_request(:head, url_with_query).with(headers: headers).to_return(body: expected_body)
213
+
214
+ response = URIs.head_response(url, params: params, headers: headers)
215
+ expect(response.body).to eq(expected_body)
216
+ expect(response.code).to eq(200)
217
+ end
218
+
219
+ it 'returns the response even for errors' do
220
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: 404, body: expected_body)
221
+
222
+ response = URIs.head_response(url, params: params, headers: headers)
223
+ expect(response.body).to eq(expected_body)
224
+ expect(response.code).to eq(404)
225
+ end
226
+ end
180
227
  end
181
228
 
182
229
  describe :safe_parse_uri do
@@ -210,5 +257,49 @@ module BerkeleyLibrary::Util
210
257
  end
211
258
  end
212
259
  end
260
+
261
+ describe :path_escape do
262
+ let(:in_out) do
263
+ {
264
+ '' => '',
265
+ 'corge' => 'corge',
266
+ 'foo+bar' => 'foo+bar',
267
+ 'qux/quux' => 'qux%2Fquux',
268
+ 'foo bar baz' => 'foo%20bar%20baz',
269
+ '25%' => '25%25',
270
+ "\t !\"#$%&'()*+,/:;<=>?@[\\]^`{|}☺" => '%09%20%21%22%23$%25&%27%28%29%2A+%2C%2F:%3B%3C=%3E%3F@%5B%5C%5D%5E%60%7B%7C%7D%E2%98%BA',
271
+ '精力善用' => '%E7%B2%BE%E5%8A%9B%E5%96%84%E7%94%A8'
272
+ }
273
+ end
274
+
275
+ it 'escapes a path segment' do
276
+ aggregate_failures do
277
+ in_out.each do |in_str, out_str|
278
+ expect(URIs.path_escape(in_str)).to eq(out_str)
279
+ end
280
+ end
281
+ end
282
+
283
+ it 'rejects non-strings' do
284
+ str = in_out.keys.last
285
+ expect { URIs.path_escape(str.bytes) }.to raise_error(ArgumentError)
286
+ end
287
+
288
+ it 'rejects non-UTF-8 strings' do
289
+ str = in_out.keys.last
290
+ expect { URIs.path_escape(str.encode(Encoding::Shift_JIS)) }.to raise_error(ArgumentError)
291
+ end
292
+
293
+ it 'accepts non-UTF-8 strings converted to UTF-8' do
294
+ in_str = in_out.keys.last
295
+ out_str = in_out[in_str]
296
+
297
+ # OK, we're really just testing String#encode here, but
298
+ # it's useful for documentation
299
+ in_str_sjis = in_str.encode(Encoding::Shift_JIS)
300
+ in_str_utf8 = in_str_sjis.encode(Encoding::UTF_8)
301
+ expect(URIs.path_escape(in_str_utf8)).to eq(out_str)
302
+ end
303
+ end
213
304
  end
214
305
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berkeley_library-util
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Moles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2022-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: berkeley_library-logging