tcp-client 0.10.1 → 0.11.0

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: 02440fe17a1480ba18bec8b6918cea6420a065d997c423a6602e165e0f316564
4
- data.tar.gz: 73e315f2c80a2f0f1cf0cb57b24f2f2cd45e10caffebd83dd47510ea85de1f25
3
+ metadata.gz: 91bdfe4046f3e4baeefe4a140d7a2bdb0cac02d3c069102e9c574fe02e5ca1c8
4
+ data.tar.gz: e26cc5ce187d50aba43d06403730445f3605667a4cffc5d4c7105dd549ca4c9a
5
5
  SHA512:
6
- metadata.gz: bb22bb974aacd8cfc9d4a1ebcc66393172ec949db8038ecbc37c1a4187bb8a1d703206625f25fb7dba5e336217d2de31475220aa4147bc20df26a5deff7bd7b7
7
- data.tar.gz: 0fd048dda8d10ba76659276cd7f5c09e1fba73560d482a5cbbb35ba6db034192137552531dcd294d45a50a10a211839b318015d0546ded7df28c8f55246fe7fd
6
+ metadata.gz: dfc647093949536fa3c040f9140ec5039a37934e1b77ccc3150f9c16a8de824123c7e8b374faa9479c5572a84f7cb9e08a051e356fe76e79d9245e10efd61bf4
7
+ data.tar.gz: 6046435c921660e284838c0e33559540f81de5f8b4c150ab63bf37ee72fc7605bc128d5c2b79d8ac7cb6cf657267bc445a4bef36103508ffaed4d6b0961d023c
data/README.md CHANGED
@@ -61,7 +61,7 @@ To install the gem globally use:
61
61
  gem install tcp-client
62
62
  ```
63
63
 
64
- After that you need only a single line of code in your project to have all tools on board:
64
+ After that you need only a single line of code in your project to have on board:
65
65
 
66
66
  ```ruby
67
67
  require 'tcp-client'
@@ -30,40 +30,25 @@ class TCPClient
30
30
  #
31
31
  # @return [Configuration] the initialized configuration
32
32
  #
33
- def self.create(options = {})
33
+ def self.create(options = nil)
34
34
  configuration = new(options)
35
35
  yield(configuration) if block_given?
36
36
  configuration
37
37
  end
38
38
 
39
39
  #
40
- # Initializes the instance with given options.
40
+ # Initializes and optionally configures the instance with given options.
41
41
  #
42
- # @param options [{Symbol => Object}]
43
- # @option options [Boolean] :buffered, see {#buffered}
44
- # @option options [Boolean] :keep_alive, see {#keep_alive}
45
- # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
46
- # @option options [{Symbol => Object}] :ssl_params, see {#ssl_params}
47
- # @option options [Numeric] :connect_timeout, see {#connect_timeout}
48
- # @option options [Class<Exception>] :connect_timeout_error, see
49
- # {#connect_timeout_error}
50
- # @option options [Numeric] :read_timeout, see {#read_timeout}
51
- # @option options [Class<Exception>] :read_timeout_error, see
52
- # {#read_timeout_error}
53
- # @option options [Numeric] :write_timeout, see {#write_timeout}
54
- # @option options [Class<Exception>] :write_timeout_error, see
55
- # {#write_timeout_error}
56
- # @option options [Boolean] :normalize_network_errors, see
57
- # {#normalize_network_errors}
42
+ # @see #configure
58
43
  #
59
- def initialize(options = {})
44
+ def initialize(options = nil)
60
45
  @buffered = @keep_alive = @reverse_lookup = true
61
46
  self.timeout = @ssl_params = nil
62
47
  @connect_timeout_error = ConnectTimeoutError
63
48
  @read_timeout_error = ReadTimeoutError
64
49
  @write_timeout_error = WriteTimeoutError
65
50
  @normalize_network_errors = false
66
- options.each_pair { |attribute, value| set(attribute, value) }
51
+ configure(options) if options
67
52
  end
68
53
 
69
54
  # @!group Instance Attributes Socket Level
@@ -257,6 +242,8 @@ class TCPClient
257
242
 
258
243
  # @!endgroup
259
244
 
245
+ # @!group Other Instance Attributes
246
+
260
247
  #
261
248
  # Enables/disables if network exceptions should be raised as {NetworkError}.
262
249
  #
@@ -274,10 +261,12 @@ class TCPClient
274
261
  @normalize_network_errors = value ? true : false
275
262
  end
276
263
 
264
+ # @!endgroup
265
+
277
266
  #
278
267
  # @return [{Symbol => Object}] Hash containing all attributes
279
268
  #
280
- # @see #initialize
269
+ # @see #configure
281
270
  #
282
271
  def to_h
283
272
  {
@@ -295,6 +284,33 @@ class TCPClient
295
284
  }
296
285
  end
297
286
 
287
+ #
288
+ # Configures the instance with given options Hash.
289
+ #
290
+ # @param options [{Symbol => Object}]
291
+ # @option options [Boolean] :buffered, see {#buffered}
292
+ # @option options [Boolean] :keep_alive, see {#keep_alive}
293
+ # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
294
+ # @option options [{Symbol => Object}] :ssl_params, see {#ssl_params}
295
+ # @option options [Numeric] :connect_timeout, see {#connect_timeout}
296
+ # @option options [Class<Exception>] :connect_timeout_error, see
297
+ # {#connect_timeout_error}
298
+ # @option options [Numeric] :read_timeout, see {#read_timeout}
299
+ # @option options [Class<Exception>] :read_timeout_error, see
300
+ # {#read_timeout_error}
301
+ # @option options [Numeric] :write_timeout, see {#write_timeout}
302
+ # @option options [Class<Exception>] :write_timeout_error, see
303
+ # {#write_timeout_error}
304
+ # @option options [Boolean] :normalize_network_errors, see
305
+ # {#normalize_network_errors}
306
+ #
307
+ # @return [Configuration] self
308
+ #
309
+ def configure(options)
310
+ options.each_pair { |attribute, value| set(attribute, value) }
311
+ self
312
+ end
313
+
298
314
  # @!visibility private
299
315
  def freeze
300
316
  @ssl_params.freeze
@@ -25,13 +25,13 @@ class TCPClient
25
25
  # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
26
26
  # end
27
27
  #
28
- # @param options [Hash] see {Configuration#initialize} for details
28
+ # @param options [Hash] see {Configuration#configure} for details
29
29
  #
30
30
  # @yieldparam cfg {Configuration} the new configuration
31
31
  #
32
32
  # @return [Configuration] the new default configuration
33
33
  #
34
- def configure(options = {}, &block)
34
+ def configure(options = nil, &block)
35
35
  @default_configuration = Configuration.create(options, &block)
36
36
  end
37
37
  end
@@ -4,40 +4,35 @@ class TCPClient
4
4
  module IOWithDeadlineMixin
5
5
  def self.included(mod)
6
6
  methods = mod.instance_methods
7
- if methods.index(:wait_writable) && methods.index(:wait_readable)
8
- mod.include(ViaWaitMethod)
9
- elsif methods.index(:to_io)
10
- mod.include(ViaIOWaitMethod)
11
- else
12
- mod.include(ViaSelect)
13
- end
7
+ return if methods.index(:wait_writable) && methods.index(:wait_readable)
8
+ mod.include(methods.index(:to_io) ? WaitWithIO : WaitWithSelect)
14
9
  end
15
10
 
16
11
  def read_with_deadline(nbytes, deadline, exception)
17
12
  raise(exception) unless deadline.remaining_time
18
13
  return fetch_avail(deadline, exception) if nbytes.nil?
19
14
  return ''.b if nbytes.zero?
20
- @buf ||= ''.b
21
- while @buf.bytesize < nbytes
22
- read = fetch_next(deadline, exception) and next @buf << read
15
+ @read_buffer ||= ''.b
16
+ while @read_buffer.bytesize < nbytes
17
+ read = fetch_next(deadline, exception) and next @read_buffer << read
23
18
  close
24
19
  break
25
20
  end
26
- fetch_buffer_slice(nbytes)
21
+ fetch_slice(nbytes)
27
22
  end
28
23
 
29
- def readto_with_deadline(sep, deadline, exception)
24
+ def read_to_with_deadline(sep, deadline, exception)
30
25
  raise(exception) unless deadline.remaining_time
31
- @buf ||= ''.b
32
- while (index = @buf.index(sep)).nil?
33
- read = fetch_next(deadline, exception) and next @buf << read
26
+ @read_buffer ||= ''.b
27
+ while @read_buffer.index(sep).nil?
28
+ read = fetch_next(deadline, exception) and next @read_buffer << read
34
29
  close
35
30
  break
36
31
  end
37
- index = @buf.index(sep)
38
- return fetch_buffer_slice(index + sep.bytesize) if index
39
- result = @buf
40
- @buf = nil
32
+ index = @read_buffer.index(sep)
33
+ return fetch_slice(index + sep.bytesize) if index
34
+ result = @read_buffer
35
+ @read_buffer = nil
41
36
  result
42
37
  end
43
38
 
@@ -58,20 +53,18 @@ class TCPClient
58
53
  private
59
54
 
60
55
  def fetch_avail(deadline, exception)
61
- if @buf.nil?
62
- result = fetch_next(deadline, exception) and return result
56
+ if (result = @read_buffer || fetch_next(deadline, exception)).nil?
63
57
  close
64
58
  return ''.b
65
59
  end
66
- result = @buf
67
- @buf = nil
60
+ @read_buffer = nil
68
61
  result
69
62
  end
70
63
 
71
- def fetch_buffer_slice(size)
72
- result = @buf.byteslice(0, size)
73
- rest = @buf.bytesize - result.bytesize
74
- @buf = rest.zero? ? nil : @buf.byteslice(size, rest)
64
+ def fetch_slice(size)
65
+ result = @read_buffer.byteslice(0, size)
66
+ rest = @read_buffer.bytesize - result.bytesize
67
+ @read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
75
68
  result
76
69
  end
77
70
 
@@ -81,68 +74,44 @@ class TCPClient
81
74
  end
82
75
  end
83
76
 
84
- module ViaWaitMethod
85
- private def with_deadline(deadline, exception)
86
- loop do
87
- case ret = yield
88
- when :wait_writable
89
- remaining_time = deadline.remaining_time or raise(exception)
90
- raise(exception) if wait_writable(remaining_time).nil?
91
- when :wait_readable
92
- remaining_time = deadline.remaining_time or raise(exception)
93
- raise(exception) if wait_readable(remaining_time).nil?
94
- else
95
- return ret
96
- end
77
+ def with_deadline(deadline, exception)
78
+ loop do
79
+ case ret = yield
80
+ when :wait_writable
81
+ remaining_time = deadline.remaining_time or raise(exception)
82
+ raise(exception) if wait_writable(remaining_time).nil?
83
+ when :wait_readable
84
+ remaining_time = deadline.remaining_time or raise(exception)
85
+ raise(exception) if wait_readable(remaining_time).nil?
86
+ else
87
+ return ret
97
88
  end
98
- rescue Errno::ETIMEDOUT
99
- raise(exception)
100
89
  end
90
+ rescue Errno::ETIMEDOUT
91
+ raise(exception)
101
92
  end
102
93
 
103
- module ViaIOWaitMethod
104
- private def with_deadline(deadline, exception)
105
- loop do
106
- case ret = yield
107
- when :wait_writable
108
- remaining_time = deadline.remaining_time or raise(exception)
109
- raise(exception) if to_io.wait_writable(remaining_time).nil?
110
- when :wait_readable
111
- remaining_time = deadline.remaining_time or raise(exception)
112
- raise(exception) if to_io.wait_readable(remaining_time).nil?
113
- else
114
- return ret
115
- end
116
- end
117
- rescue Errno::ETIMEDOUT
118
- raise(exception)
94
+ module WaitWithIO
95
+ def wait_writable(remaining_time)
96
+ to_io.wait_writable(remaining_time)
97
+ end
98
+
99
+ def wait_readable(remaining_time)
100
+ to_io.wait_readable(remaining_time)
119
101
  end
120
102
  end
121
103
 
122
- module ViaSelect
123
- private def with_deadline(deadline, exception)
124
- loop do
125
- case ret = yield
126
- when :wait_writable
127
- remaining_time = deadline.remaining_time or raise(exception)
128
- if ::IO.select(nil, [self], nil, remaining_time).nil?
129
- raise(exception)
130
- end
131
- when :wait_readable
132
- remaining_time = deadline.remaining_time or raise(exception)
133
- if ::IO.select([self], nil, nil, remaining_time).nil?
134
- raise(exception)
135
- end
136
- else
137
- return ret
138
- end
139
- end
140
- rescue Errno::ETIMEDOUT
141
- raise(exception)
104
+ module WaitWithSelect
105
+ def wait_writable(remaining_time)
106
+ ::IO.select(nil, [self], nil, remaining_time)
107
+ end
108
+
109
+ def wait_readable(remaining_time)
110
+ ::IO.select([self], nil, nil, remaining_time)
142
111
  end
143
112
  end
144
113
 
145
- private_constant(:ViaWaitMethod, :ViaIOWaitMethod, :ViaSelect)
114
+ private_constant(:WaitWithIO, :WaitWithSelect)
146
115
  end
147
116
 
148
117
  private_constant(:IOWithDeadlineMixin)
@@ -2,5 +2,5 @@
2
2
 
3
3
  class TCPClient
4
4
  # The current version number.
5
- VERSION = '0.10.1'
5
+ VERSION = '0.11.0'
6
6
  end
data/lib/tcp-client.rb CHANGED
@@ -231,7 +231,7 @@ class TCPClient
231
231
  exception ||= configuration.read_timeout_error
232
232
  line =
233
233
  stem_errors(exception) do
234
- @socket.readto_with_deadline(separator, deadline, exception)
234
+ @socket.read_to_with_deadline(separator, deadline, exception)
235
235
  end
236
236
  chomp ? line.chomp : line
237
237
  end
@@ -344,7 +344,8 @@ class TCPClient
344
344
  IOError,
345
345
  SocketError
346
346
  ].tap do |errors|
347
- errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
348
- end.freeze
347
+ errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
348
+ end
349
+ .freeze
349
350
  private_constant(:NETWORK_ERRORS)
350
351
  end
data/rakefile.rb CHANGED
@@ -7,12 +7,10 @@ require 'yard'
7
7
 
8
8
  $stdout.sync = $stderr.sync = true
9
9
 
10
- CLEAN << 'prj' << 'doc'
11
-
12
- CLOBBER << '.yardoc'
10
+ CLEAN << '.yardoc'
11
+ CLOBBER << 'prj' << 'doc'
13
12
 
14
13
  task(:default) { exec('rake --tasks') }
15
-
14
+ task(test: :spec)
16
15
  RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
17
-
18
16
  YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
data/sample/google_ssl.rb CHANGED
@@ -22,7 +22,7 @@ cfg =
22
22
  response =
23
23
  TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
24
24
  client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
25
- client.readline("\r\n\r\n") #=> see response
25
+ client.readline("\r\n\r\n") #=> header, see response
26
26
  end
27
27
 
28
28
  puts(response)
@@ -60,20 +60,34 @@ RSpec.describe TCPClient::Configuration do
60
60
  end
61
61
  end
62
62
 
63
- context 'with valid options' do
64
- subject(:configuration) do
65
- TCPClient::Configuration.new(
66
- buffered: false,
67
- keep_alive: false,
68
- reverse_lookup: false,
69
- normalize_network_errors: true,
70
- ssl: true,
71
- timeout: 60,
72
- timeout_error: custom_error
73
- )
63
+ context 'when options are given' do
64
+ let(:options) { double(:options) }
65
+
66
+ it 'calls #configure with given options' do
67
+ expect_any_instance_of(TCPClient::Configuration).to receive(
68
+ :configure
69
+ ).once.with(options)
70
+
71
+ TCPClient::Configuration.new(options)
74
72
  end
75
- let(:custom_error) { Class.new(StandardError) }
73
+ end
74
+ end
75
+
76
+ describe '#configure' do
77
+ subject(:configuration) do
78
+ TCPClient::Configuration.new.configure(
79
+ buffered: false,
80
+ keep_alive: false,
81
+ reverse_lookup: false,
82
+ normalize_network_errors: true,
83
+ ssl: true,
84
+ timeout: 60,
85
+ timeout_error: custom_error
86
+ )
87
+ end
88
+ let(:custom_error) { Class.new(StandardError) }
76
89
 
90
+ context 'with valid options' do
77
91
  it 'allows to configure buffering' do
78
92
  expect(configuration.buffered).to be false
79
93
  end
@@ -107,52 +121,56 @@ RSpec.describe TCPClient::Configuration do
107
121
  end
108
122
 
109
123
  it 'allows to configure dedicated timeout values' do
110
- config =
111
- TCPClient::Configuration.new(
112
- connect_timeout: 21,
113
- read_timeout: 42,
114
- write_timeout: 84
115
- )
116
- expect(config.connect_timeout).to be 21
117
- expect(config.read_timeout).to be 42
118
- expect(config.write_timeout).to be 84
124
+ configuration.configure(
125
+ connect_timeout: 21,
126
+ read_timeout: 42,
127
+ write_timeout: 84
128
+ )
129
+ expect(configuration.connect_timeout).to be 21
130
+ expect(configuration.read_timeout).to be 42
131
+ expect(configuration.write_timeout).to be 84
119
132
  end
120
133
 
121
134
  it 'allows to configure dedicated timeout errors' do
122
135
  custom_connect = Class.new(StandardError)
123
136
  custom_read = Class.new(StandardError)
124
137
  custom_write = Class.new(StandardError)
125
- config =
126
- TCPClient::Configuration.new(
127
- connect_timeout_error: custom_connect,
128
- read_timeout_error: custom_read,
129
- write_timeout_error: custom_write
130
- )
131
- expect(config.connect_timeout_error).to be custom_connect
132
- expect(config.read_timeout_error).to be custom_read
133
- expect(config.write_timeout_error).to be custom_write
138
+ configuration.configure(
139
+ connect_timeout_error: custom_connect,
140
+ read_timeout_error: custom_read,
141
+ write_timeout_error: custom_write
142
+ )
143
+ expect(configuration.connect_timeout_error).to be custom_connect
144
+ expect(configuration.read_timeout_error).to be custom_read
145
+ expect(configuration.write_timeout_error).to be custom_write
134
146
  end
147
+ end
135
148
 
136
- it 'raises when no exception class is used to configure a timeout error' do
137
- expect do
138
- TCPClient::Configuration.new(
139
- connect_timeout_error: double(:something)
140
- )
141
- end.to raise_error(TCPClient::NotAnExceptionError)
149
+ context 'when an invalid attribute is given' do
150
+ it 'raises an error' do
151
+ expect { configuration.configure(invalid: :value) }.to raise_error(
152
+ TCPClient::UnknownAttributeError
153
+ )
154
+ end
155
+ end
156
+
157
+ context 'when no exception class is used to configure a timeout error' do
158
+ it 'raises with invalid connect_timeout_error' do
142
159
  expect do
143
- TCPClient::Configuration.new(read_timeout_error: double(:something))
160
+ configuration.configure(connect_timeout_error: double(:something))
144
161
  end.to raise_error(TCPClient::NotAnExceptionError)
162
+ end
163
+
164
+ it 'raises with invalid read_timeout_error' do
145
165
  expect do
146
- TCPClient::Configuration.new(write_timeout_error: double(:something))
166
+ configuration.configure(read_timeout_error: double(:something))
147
167
  end.to raise_error(TCPClient::NotAnExceptionError)
148
168
  end
149
- end
150
169
 
151
- context 'with invalid attribute' do
152
- it 'raises an error' do
153
- expect { TCPClient::Configuration.new(invalid: :value) }.to raise_error(
154
- TCPClient::UnknownAttributeError
155
- )
170
+ it 'raises with invalid write_timeout_error' do
171
+ expect do
172
+ configuration.configure(write_timeout_error: double(:something))
173
+ end.to raise_error(TCPClient::NotAnExceptionError)
156
174
  end
157
175
  end
158
176
  end
@@ -9,8 +9,9 @@ RSpec.describe 'TCPClient.configure' do
9
9
 
10
10
  context 'called with parameters' do
11
11
  it 'creates a new configuratiion' do
12
- expect(TCPClient::Configuration).to receive(:create).once.with(a: 1, b: 2)
13
- TCPClient.configure(a: 1, b: 2)
12
+ options = double(:options)
13
+ expect(TCPClient::Configuration).to receive(:create).once.with(options)
14
+ TCPClient.configure(options)
14
15
  end
15
16
 
16
17
  it 'returns the new configuratiion' do
@@ -130,11 +130,11 @@ RSpec.describe TCPClient do
130
130
  expect(client.configuration).to be configuration
131
131
  end
132
132
 
133
- it 'failes when read is called' do
133
+ it 'fails when read is called' do
134
134
  expect { client.read(42) }.to raise_error(TCPClient::NotConnectedError)
135
135
  end
136
136
 
137
- it 'failes when write is called' do
137
+ it 'fails when write is called' do
138
138
  expect { client.write('?!') }.to raise_error(TCPClient::NotConnectedError)
139
139
  end
140
140
 
@@ -149,12 +149,6 @@ RSpec.describe TCPClient do
149
149
  end
150
150
  end
151
151
 
152
- xdescribe '.open' do
153
- end
154
-
155
- xdescribe '.with_deadline' do
156
- end
157
-
158
152
  context 'when not using SSL' do
159
153
  describe '#connect' do
160
154
  subject(:client) { TCPClient.new }
@@ -474,7 +468,7 @@ RSpec.describe TCPClient do
474
468
  .once
475
469
  .with(instance_of(Integer), exception: false)
476
470
  .and_return(nil)
477
- expect(client.readline(timeout: 10)).to eq "Hello "
471
+ expect(client.readline(timeout: 10)).to eq 'Hello '
478
472
  end
479
473
 
480
474
  it 'is closed' do
@@ -779,7 +773,7 @@ RSpec.describe TCPClient do
779
773
  # :set_params
780
774
  # )
781
775
  # .once
782
- # .with(ssl_version: :TLSv1_2)
776
+ # .with(max_version: :TLS1_3, min_version: :TLS1_2)
783
777
  # .and_call_original
784
778
  expect_any_instance_of(::OpenSSL::SSL::SSLSocket).to receive(
785
779
  :sync_close=
data/tcp-client.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
 
10
10
  spec.author = 'Mike Blumtritt'
11
11
  spec.summary = 'A TCP client implementation with working timeout support.'
12
- spec.description = <<~description
12
+ spec.description = <<~DESCRIPTION
13
13
  This Gem implements a TCP client with (optional) SSL support.
14
14
  It is an easy to use, versatile configurable client that can correctly
15
15
  handle time limits.
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  predefined/configurable time limits for each method
18
18
  (`connect`, `read`, `write`). Deadlines for a sequence of read/write
19
19
  actions can also be monitored.
20
- description
20
+ DESCRIPTION
21
21
 
22
22
  spec.homepage = 'https://github.com/mblumtritt/tcp-client'
23
23
  spec.license = 'BSD-3-Clause'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-12 00:00:00.000000000 Z
11
+ date: 2022-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler