format_parser 2.0.0.pre → 2.0.0.pre.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: d7c965b7783ecaea4802f7e585861b4400b2210fee4cb90388757530880fa074
4
- data.tar.gz: fc8b7cc3f00825fa054c948a7ae817b1eee6457ffaec9e5a6b5bdd9a0b92d126
3
+ metadata.gz: 01730fd580c14a2c35b87be1f62384b06f9b2683bd07ba01ed21d21a4211b1f5
4
+ data.tar.gz: c27ba63f09c8251ccaf237040cb6f01c495686f6a025cdb7207aa0bfb2fe0098
5
5
  SHA512:
6
- metadata.gz: 73f774ebe540dfd54e87f89cedecfc0fabf4a97f4e2ef72afcd94edc5e0fbc344c7c67b365942e3bb915dfe76f94f038072671c259c2d366a69d64a73cbde960
7
- data.tar.gz: bc1405329d521487ec4d0738c258fb12c3acdb37b6b8ecebf7451a866d5f1072cfc23774e2ecc3d7d297095ff280320756fb4cd9000de3eac447a105cf87028b
6
+ metadata.gz: 427c1f3652e33592a7bd3a63c0db676a865390b1947732e452905d0fe64f54df82af2b9a339dfdf962d9586c73fe7ac8a2b19da099c502a187efd8215993ddbf
7
+ data.tar.gz: 3652ca2c1ae42ddb5e29811dcae13e8bbb0fcf964c4f47547638e992d21612235bdf489d908b5c4bdf3aa2859768eb0dc98c6fc58527ce5031396ee341207354
@@ -8,7 +8,7 @@ env:
8
8
  jobs:
9
9
  lint:
10
10
  name: Code Style
11
- runs-on: ubuntu-18.04
11
+ runs-on: ubuntu-latest
12
12
  if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
13
13
  strategy:
14
14
  matrix:
@@ -54,7 +54,7 @@ jobs:
54
54
  run: bundle exec rubocop
55
55
  test:
56
56
  name: Specs
57
- runs-on: ubuntu-18.04
57
+ runs-on: ubuntu-latest
58
58
  if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
59
59
  strategy:
60
60
  matrix:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 2.0.0.pre (Prerelease)
1
+ ## 2.0.0.pre.2 (Prerelease)
2
2
  * Drop support for Ruby `<2.7`.
3
3
  * Drop faraday dependencies.
4
4
  * Loosen version constraints on other dependencies.
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency 'rake'
40
40
  spec.add_development_dependency 'rspec'
41
41
  spec.add_development_dependency 'simplecov'
42
+ spec.add_development_dependency 'webmock'
42
43
  spec.add_development_dependency 'wetransfer_style', '1.0.0'
43
44
  spec.add_development_dependency 'yard'
44
45
  end
@@ -1,3 +1,3 @@
1
1
  module FormatParser
2
- VERSION = '2.0.0.pre'
2
+ VERSION = '2.0.0.pre.2'
3
3
  end
data/lib/remote_io.rb CHANGED
@@ -122,7 +122,7 @@ class FormatParser::RemoteIO
122
122
  error_message = [
123
123
  "We requested #{requested_range_size} bytes, but the server sent us more",
124
124
  "(#{response_size} bytes) - it likely has no `Range:` support.",
125
- "The error occurred when talking to #{uri})"
125
+ "The error occurred when talking to #{uri}"
126
126
  ]
127
127
  raise InvalidRequest.new(response.code, error_message.join("\n"))
128
128
  end
@@ -141,10 +141,13 @@ class FormatParser::RemoteIO
141
141
  # to be 206
142
142
  [size, response.body]
143
143
  when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther, Net::HTTPTemporaryRedirect, Net::HTTPPermanentRedirect
144
- raise RedirectLimitReached(uri) if redirects == 0
144
+ raise RedirectLimitReached, uri if redirects == 0
145
145
  location = response['location']
146
146
  if location
147
- request_range(range, redirect_uri(location, uri), redirects - 1)
147
+ new_uri = redirect_uri(location, uri)
148
+ # Clear the Authorization header if the new URI has a different host.
149
+ @headers.delete('Authorization') unless [@uri.scheme, @uri.host, @uri.port] == [new_uri.scheme, new_uri.host, new_uri.port]
150
+ request_range(range, new_uri, redirects - 1)
148
151
  else
149
152
  raise InvalidRequest.new(response.code, "Server at #{uri} replied with a #{response.code}, indicating redirection; however, the location header was empty.")
150
153
  end
@@ -3,187 +3,270 @@ require 'spec_helper'
3
3
  describe FormatParser::RemoteIO do
4
4
  it_behaves_like 'an IO object compatible with IOConstraint'
5
5
 
6
- it 'returns the partial content when the server supplies a 206 status' do
7
- url = 'https://images.invalid/img.jpg'
8
- response = Net::HTTPPartialContent.new('2', '206', 'Partial Content')
9
- response['Content-Range'] = '10-109/2577'
10
- allow(response).to receive(:body).and_return('Response body')
6
+ # 2XX
7
+
8
+ context 'when the response code is 200 (OK)' do
9
+ context 'when the response size does not exceed the requested range' do
10
+ it 'returns the entire response body' do
11
+ url = 'https://images.invalid/img.jpg'
12
+ body = 'response body'
13
+
14
+ stub = stub_request(:get, url)
15
+ .with(headers: { 'range' => 'bytes=10-109' })
16
+ .to_return(body: body, status: 200)
17
+
18
+ rio = described_class.new(url)
19
+ rio.seek(10)
20
+ read_result = rio.read(100)
21
+
22
+ expect(read_result).to eq(body)
23
+ expect(stub).to have_been_requested
24
+ end
25
+ end
26
+
27
+ context 'when the response size exceeds the requested range' do
28
+ it 'raises an error' do
29
+ url = 'https://images.invalid/img.jpg'
30
+ body = 'This response is way longer than 10 bytes.'
31
+
32
+ stub = stub_request(:get, url)
33
+ .with(headers: { 'range' => 'bytes=10-19' })
34
+ .to_return(body: body, status: 200)
35
+
36
+ rio = described_class.new(url)
37
+ rio.seek(10)
38
+
39
+ expect { rio.read(10) }.to raise_error(
40
+ "We requested 10 bytes, but the server sent us more\n"\
41
+ "(42 bytes) - it likely has no `Range:` support.\n"\
42
+ "The error occurred when talking to #{url}"
43
+ )
44
+ expect(stub).to have_been_requested
45
+ end
46
+ end
47
+ end
11
48
 
12
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
13
- allow(Net::HTTP).to receive(:request_get).and_return(response)
49
+ context 'when the response status code is 206 (Partial Content)' do
50
+ context 'when the Content-Range header is present' do
51
+ it 'returns the response body' do
52
+ url = 'https://images.invalid/img.jpg'
53
+ body = 'response body'
14
54
 
15
- expect(Net::HTTP).to receive(:request_get).with(
16
- an_object_satisfying { |uri| URI::HTTPS === uri && uri.to_s == url },
17
- a_hash_including('range' => 'bytes=10-109')
18
- )
55
+ stub = stub_request(:get, url)
56
+ .with(headers: { 'range' => 'bytes=10-109' })
57
+ .to_return(body: body, headers: { 'Content-Range' => '10-109/2577' }, status: 206)
19
58
 
20
- rio = described_class.new(url)
21
- rio.seek(10)
22
- read_result = rio.read(100)
59
+ rio = described_class.new(url)
60
+ rio.seek(10)
61
+ read_result = rio.read(100)
23
62
 
24
- expect(read_result).to eq(response.body)
25
- end
63
+ expect(read_result).to eq(body)
64
+ expect(stub).to have_been_requested
65
+ end
26
66
 
27
- it 'returns the entire content when the server supplies the Content-Range response but sends a 200 status' do
28
- url = 'https://images.invalid/img.jpg'
29
- response = Net::HTTPOK.new('2', '200', 'OK')
30
- allow(response).to receive(:body).and_return('Response body')
67
+ it 'maintains and exposes pos' do
68
+ url = 'https://images.invalid/img.jpg'
31
69
 
32
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
33
- allow(Net::HTTP).to receive(:request_get).and_return(response)
70
+ stub = stub_request(:get, url)
71
+ .with(headers: { 'range' => 'bytes=0-0' })
72
+ .to_return(body: 'a', headers: { 'Content-Range' => '0-0/13' }, status: 206)
34
73
 
35
- expect(Net::HTTP).to receive(:request_get).with(
36
- an_object_satisfying { |uri| URI::HTTPS === uri && uri.to_s == url },
37
- a_hash_including('range' => 'bytes=10-109')
38
- )
74
+ rio = described_class.new(url)
39
75
 
40
- rio = described_class.new(url)
41
- rio.seek(10)
42
- read_result = rio.read(100)
76
+ expect(rio.pos).to eq(0)
43
77
 
44
- expect(read_result).to eq(response.body)
45
- end
78
+ rio.read(1)
46
79
 
47
- it 'raises a specific error for all 4xx responses except 416' do
48
- url = 'https://images.invalid/img.jpg'
49
- response = Net::HTTPForbidden.new('2', '403', 'Forbidden')
80
+ expect(rio.pos).to eq(1)
81
+ expect(stub).to have_been_requested
82
+ end
83
+ end
50
84
 
51
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
52
- allow(Net::HTTP).to receive(:request_get).and_return(response)
85
+ context 'when the Content-Range header is not present' do
86
+ it 'raises an error' do
87
+ url = 'https://images.invalid/img.jpg'
53
88
 
54
- expect(Net::HTTP).to receive(:request_get).with(
55
- an_object_satisfying { |uri| uri.to_s == url },
56
- a_hash_including('range' => 'bytes=100-199')
57
- )
89
+ stub = stub_request(:get, url)
90
+ .with(headers: { 'range' => 'bytes=10-109' })
91
+ .to_return(status: 206)
58
92
 
59
- rio = described_class.new(url)
60
- rio.seek(100)
93
+ rio = described_class.new(url)
94
+ rio.seek(10)
61
95
 
62
- expect { rio.read(100) }.to raise_error(/replied with a 403 and refused/)
96
+ expect { rio.read(100) }.to raise_error("The server replied with 206 status but no Content-Range at #{url}")
97
+ expect(stub).to have_been_requested
98
+ end
99
+ end
63
100
  end
64
101
 
65
- it 'returns nil on a 416 response' do
66
- url = 'https://images.invalid/img.jpg'
67
- response = Net::HTTPRangeNotSatisfiable.new('2', '416', 'Range Not Satisfiable')
102
+ # 3XX
103
+
104
+ [301, 302, 303, 307, 308].each do |code|
105
+ context "when the response code is #{code}" do
106
+ context 'when the location header is present and the redirect limit is not exceeded' do
107
+ context 'when the location is absolute' do
108
+ it 'redirects to the specified location, without the Authorization header' do
109
+ redirecting_url = 'https://my_images.invalid/my_image'
110
+ destination_url = 'https://images.invalid/img.jpg'
111
+ body = 'response body'
112
+
113
+ redirect_stub = stub_request(:get, redirecting_url)
114
+ .with(headers: { 'Authorization' => 'token', 'range' => 'bytes=10-109' })
115
+ .to_return(headers: { 'location' => destination_url }, status: code)
116
+ destination_stub = stub_request(:get, destination_url)
117
+ .with { |request| request.headers['Range'] == 'bytes=10-109' && !request.headers.key?('Authorization') }
118
+ .to_return(body: body, status: 200)
119
+
120
+ rio = described_class.new(redirecting_url, headers: { 'Authorization' => 'token' })
121
+ rio.seek(10)
122
+ read_result = rio.read(100)
123
+
124
+ expect(read_result).to eq(body)
125
+ expect(redirect_stub).to have_been_requested
126
+ expect(destination_stub).to have_been_requested
127
+ end
128
+ end
129
+
130
+ context 'when the location is relative' do
131
+ it 'redirects to the specified location under the same host, with the same Authorization header' do
132
+ host = 'https://images.invalid'
133
+ redirecting_path = '/my_image'
134
+ redirecting_url = host + redirecting_path
135
+ destination_path = '/img.jpg'
136
+ destination_url = host + destination_path
137
+ body = 'response body'
138
+
139
+ redirect_stub = stub_request(:get, redirecting_url)
140
+ .with(headers: { 'Authorization' => 'token', 'range' => 'bytes=10-109' })
141
+ .to_return(headers: { 'location' => destination_path }, status: code)
142
+ destination_stub = stub_request(:get, destination_url)
143
+ .with(headers: { 'Authorization' => 'token', 'range' => 'bytes=10-109' })
144
+ .to_return(body: body, status: 200)
145
+
146
+ rio = described_class.new(redirecting_url, headers: { 'Authorization' => 'token' })
147
+ rio.seek(10)
148
+ read_result = rio.read(100)
149
+
150
+ expect(read_result).to eq(body)
151
+ expect(redirect_stub).to have_been_requested
152
+ expect(destination_stub).to have_been_requested
153
+ end
154
+ end
155
+ end
156
+
157
+ context 'when the location header is not present' do
158
+ it 'raises an error' do
159
+ url = 'https://images.invalid/my_image'
160
+
161
+ stub = stub_request(:get, url)
162
+ .with(headers: { 'range' => 'bytes=10-109' })
163
+ .to_return(status: code)
164
+
165
+ rio = described_class.new(url)
166
+ rio.seek(10)
167
+
168
+ expect { rio.read(100) }.to raise_error("Server at #{url} replied with a #{code}, indicating redirection; however, the location header was empty.")
169
+ expect(stub).to have_been_requested
170
+ end
171
+ end
172
+
173
+ context 'when the redirect limit is exceeded' do
174
+ it 'raises an error' do
175
+ redirecting_url = 'https://images.invalid/my_image'
176
+ destination_url = 'https://images.invalid/img.jpg'
177
+
178
+ stub = stub_request(:get, /https:\/\/images\.invalid.*/)
179
+ .with(headers: { 'range' => 'bytes=10-109' })
180
+ .to_return(headers: { 'location' => destination_url }, status: code)
181
+
182
+ rio = described_class.new(redirecting_url)
183
+ rio.seek(10)
184
+
185
+ expect { rio.read(100) }.to raise_error("Too many redirects; last one to: #{destination_url}")
186
+ expect(stub).to have_been_requested.times(4)
187
+ end
188
+ end
189
+ end
190
+ end
68
191
 
69
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
70
- allow(Net::HTTP).to receive(:request_get).and_return(response)
192
+ # 4XX
71
193
 
72
- expect(Net::HTTP).to receive(:request_get).with(
73
- an_object_satisfying { |uri| uri.to_s == url },
74
- a_hash_including('range' => 'bytes=100-199')
75
- )
194
+ context 'when the response status code is 416 (Range Not Satisfiable)' do
195
+ it 'returns nil' do
196
+ url = 'https://images.invalid/img.jpg'
76
197
 
77
- rio = described_class.new(url)
78
- rio.seek(100)
198
+ stub = stub_request(:get, url)
199
+ .with(headers: { 'range' => 'bytes=100-199' })
200
+ .to_return(status: 416)
79
201
 
80
- expect(rio.read(100)).to be_nil
81
- end
202
+ rio = described_class.new(url)
203
+ rio.seek(100)
82
204
 
83
- it 'sets the status_code of the exception on a 4xx response from upstream' do
84
- url = 'https://images.invalid/img.jpg'
85
- response = Net::HTTPForbidden.new('2', '403', 'Forbidden')
205
+ expect(rio.read(100)).to be_nil
206
+ expect(stub).to have_been_requested
207
+ end
86
208
 
87
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
88
- allow(Net::HTTP).to receive(:request_get).and_return(response)
209
+ it 'does not change pos or size' do
210
+ url = 'https://images.invalid/img.jpg'
89
211
 
90
- expect(Net::HTTP).to receive(:request_get).with(
91
- an_object_satisfying { |uri| uri.to_s == url },
92
- a_hash_including('range' => 'bytes=100-199')
93
- )
212
+ stub = stub_request(:get, url)
213
+ .with(headers: { 'range' => 'bytes=0-0' })
214
+ .to_return(body: 'response body', headers: { 'Content-Range' => 'bytes 0-0/13' }, status: 206)
94
215
 
95
- rio = described_class.new(url)
96
- rio.seek(100)
97
- expect { rio.read(100) }.to(raise_error { |e| expect(e.status_code).to eq(403) })
98
- end
216
+ rio = described_class.new(url)
217
+ rio.read(1)
99
218
 
100
- it 'returns a nil when the range cannot be satisfied and the response is 416' do
101
- url = 'https://images.invalid/img.jpg'
102
- response = Net::HTTPRangeNotSatisfiable.new('2', '416', 'Range Not Satisfiable')
219
+ expect(rio.size).to eq(13)
220
+ expect(stub).to have_been_requested
103
221
 
104
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
105
- allow(Net::HTTP).to receive(:request_get).and_return(response)
222
+ stub = stub_request(:get, url)
223
+ .with(headers: { 'range' => 'bytes=100-199' })
224
+ .to_return(status: 416)
106
225
 
107
- expect(Net::HTTP).to receive(:request_get).with(
108
- an_object_satisfying { |uri| uri.to_s == url },
109
- a_hash_including('range' => 'bytes=100-199')
110
- )
226
+ rio.seek(100)
227
+ rio.read(100)
111
228
 
112
- rio = described_class.new(url)
113
- rio.seek(100)
114
-
115
- expect(rio.read(100)).to be_nil
116
- end
117
-
118
- it 'does not overwrite size when the range cannot be satisfied and the response is 416' do
119
- url = 'https://images.invalid/img.jpg'
120
- response_1 = Net::HTTPPartialContent.new('2', '206', 'Partial Content')
121
- response_1['Content-Range'] = 'bytes 0-0/13'
122
- allow(response_1).to receive(:body).and_return('Response body')
123
- response_2 = Net::HTTPRangeNotSatisfiable.new('2', '416', 'Range Not Satisfiable')
124
-
125
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response_1, response_2)
126
- allow(Net::HTTP).to receive(:request_get).and_return(response_1, response_2)
127
-
128
- expect(Net::HTTP).to receive(:request_get)
129
- .with(
130
- an_object_satisfying { |uri| uri.to_s == url },
131
- a_hash_including('range' => 'bytes=0-0')
132
- )
133
- .ordered
134
- expect(Net::HTTP).to receive(:request_get)
135
- .with(
136
- an_object_satisfying { |uri| uri.to_s == url },
137
- a_hash_including('range' => 'bytes=100-199')
138
- )
139
- .ordered
140
-
141
- rio = described_class.new(url)
142
- rio.read(1)
143
-
144
- expect(rio.size).to eq(13)
145
-
146
- rio.seek(100)
147
-
148
- expect(rio.read(100)).to be_nil
149
- expect(rio.size).to eq(13)
229
+ expect(rio.pos).to eq(100)
230
+ expect(rio.size).to eq(13)
231
+ expect(stub).to have_been_requested
232
+ end
150
233
  end
151
234
 
152
- it 'raises a specific error for all 5xx responses' do
153
- url = 'https://images.invalid/img.jpg'
154
- response = Net::HTTPBadGateway.new('2', '502', 'Bad Gateway')
235
+ [*400..415, *417..499].each do |code|
236
+ context "when the response status code is #{code}" do
237
+ it 'raises an error' do
238
+ url = 'https://images.invalid/img.jpg'
155
239
 
156
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
157
- allow(Net::HTTP).to receive(:request_get).and_return(response)
240
+ stub = stub_request(:get, url)
241
+ .with(headers: { 'range' => 'bytes=100-199' })
242
+ .to_return(status: code)
158
243
 
159
- expect(Net::HTTP).to receive(:request_get).with(
160
- an_object_satisfying { |uri| uri.to_s == url },
161
- a_hash_including('range' => 'bytes=100-199')
162
- )
244
+ rio = described_class.new(url)
245
+ rio.seek(100)
163
246
 
164
- rio = described_class.new(url)
165
- rio.seek(100)
166
-
167
- expect { rio.read(100) }.to raise_error(/replied with a 502 and we might want to retry/)
247
+ expect { rio.read(100) }.to raise_error("Server at #{url} replied with a #{code} and refused our request")
248
+ expect(stub).to have_been_requested
249
+ end
250
+ end
168
251
  end
169
252
 
170
- it 'maintains and exposes #pos' do
171
- url = 'https://images.invalid/img.jpg'
172
- response = Net::HTTPPartialContent.new('2', '206', 'Partial Content')
173
- response['Content-Range'] = 'bytes 0-0/13'
174
- allow(response).to receive(:body).and_return('a')
253
+ # 5XX
254
+
255
+ (500..599).each do |code|
256
+ context "when the response status code is #{code}" do
257
+ it 'raises an error' do
258
+ url = 'https://images.invalid/img.jpg'
175
259
 
176
- allow(Net::HTTP).to receive(:start).and_yield(Net::HTTP).and_return(response)
177
- allow(Net::HTTP).to receive(:request_get).and_return(response)
260
+ stub = stub_request(:get, url)
261
+ .with(headers: { 'range' => 'bytes=100-199' })
262
+ .to_return(status: code)
178
263
 
179
- expect(Net::HTTP).to receive(:request_get).with(
180
- an_object_satisfying { |uri| uri.to_s == url },
181
- a_hash_including('range' => 'bytes=0-0')
182
- )
264
+ rio = described_class.new(url)
265
+ rio.seek(100)
183
266
 
184
- rio = described_class.new(url)
185
- expect(rio.pos).to eq(0)
186
- rio.read(1)
187
- expect(rio.pos).to eq(1)
267
+ expect { rio.read(100) }.to raise_error("Server at #{url} replied with a #{code} and we might want to retry")
268
+ expect(stub).to have_been_requested
269
+ end
270
+ end
188
271
  end
189
272
  end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,10 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
8
8
 
9
9
  require 'rspec'
10
+ require 'webmock/rspec'
11
+
12
+ WebMock.disable_net_connect!(allow_localhost: true)
13
+
10
14
  require 'format_parser'
11
15
 
12
16
  module SpecHelpers
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre
4
+ version: 2.0.0.pre.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-10-13 00:00:00.000000000 Z
12
+ date: 2022-10-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: exifr
@@ -123,6 +123,20 @@ dependencies:
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: webmock
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
126
140
  - !ruby/object:Gem::Dependency
127
141
  name: wetransfer_style
128
142
  requirement: !ruby/object:Gem::Requirement