io-stream 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 259533ca29c9be8e0476777d9060a4aa6928fa856848396110b3f668628db636
4
+ data.tar.gz: ed074a9ff19c5f0f74223228d1bb9929ff7534f921c11e4adf34fe88c5f2824b
5
+ SHA512:
6
+ metadata.gz: f90e4bca685926a8cc28b21b65ddceea17fd8d3ba25ccefd6daf9095403ef7446c71b9afdfe57367faccf2ee46635bcf939e14f8ac36e9ebc6b9ee55bdafaada
7
+ data.tar.gz: f7aaa56fb744df8cd057412083ad63c707165e3e1c404397dfa447f2135f7d11fef6d0b855cf88c23d28346ba96c320bc9566e7395520889d89ea7b63ea4fd1c
checksums.yaml.gz.sig ADDED
Binary file
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ unless IO.method_defined?(:buffered?)
7
+ class IO
8
+ def buffered?
9
+ return !self.sync
10
+ end
11
+
12
+ def buffered=(value)
13
+ self.sync = !value
14
+ end
15
+ end
16
+ end
17
+
18
+ require 'socket'
19
+
20
+ unless BasicSocket.method_defined?(:buffered?)
21
+ class BasicSocket
22
+ # Is it likely that the socket is still connected?
23
+ # May return false positive, but won't return false negative.
24
+ def buffered?
25
+ return false unless super
26
+
27
+ case self.local_address.protocol
28
+ when IPPROTO_TCP
29
+ return !self.getsockopt(IPPROTO_TCP, TCP_NODELAY).bool
30
+ else
31
+ return true
32
+ end
33
+ end
34
+
35
+ def buffered=(value)
36
+ super
37
+
38
+ case self.local_address.socktype
39
+ when SOCK_STREAM
40
+ # When buffered is set to true, TCP_NODELAY shold be disabled.
41
+ self.setsockopt(IPPROTO_TCP, TCP_NODELAY, value ? 0 : 1)
42
+ end
43
+ rescue Errno::EINVAL
44
+ # On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.
45
+ rescue Errno::EOPNOTSUPP
46
+ # Some platforms may simply not support the operation.
47
+ end
48
+ end
49
+ end
50
+
51
+ require 'stringio'
52
+
53
+ unless StringIO.method_defined?(:buffered?)
54
+ class StringIO
55
+ def buffered?
56
+ return !self.sync
57
+ end
58
+
59
+ def buffered=(value)
60
+ self.sync = !value
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ class IO
7
+ unless method_defined?(:readable?)
8
+ def readable?
9
+ # Do not call `eof?` here as it is not concurrency-safe and it can block.
10
+ !closed?
11
+ end
12
+ end
13
+ end
14
+
15
+ require 'socket'
16
+
17
+ class BasicSocket
18
+ unless method_defined?(:readable?)
19
+ def readable?
20
+ # If we can wait for the socket to become readable, we know that the socket may still be open.
21
+ result = self.recv_nonblock(1, MSG_PEEK, exception: false)
22
+
23
+ # No data was available - newer Ruby can return nil instead of empty string:
24
+ return false if result.nil?
25
+
26
+ # Either there was some data available, or we can wait to see if there is data avaialble.
27
+ return !result.empty? || result == :wait_readable
28
+ rescue Errno::ECONNRESET, IOError
29
+ # This might be thrown by recv_nonblock.
30
+ return false
31
+ end
32
+ end
33
+ end
34
+
35
+ require 'stringio'
36
+
37
+ class StringIO
38
+ unless method_defined?(:readable?)
39
+ def readable?
40
+ !eof?
41
+ end
42
+ end
43
+ end
44
+
45
+ require 'openssl'
46
+
47
+ class OpenSSL::SSL::SSLSocket
48
+ unless method_defined?(:readable?)
49
+ def readable?
50
+ to_io.readable?
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ require_relative 'string_buffer'
7
+
8
+ require_relative '../buffered'
9
+ require_relative '../readable'
10
+
11
+ module IO::Stream
12
+ # The default block size for IO buffers. Defaults to 64KB (typical pipe buffer size).
13
+ BLOCK_SIZE = ENV.fetch('IO_STREAM_BLOCK_SIZE', 1024*64).to_i
14
+
15
+ # The maximum read size when appending to IO buffers. Defaults to 8MB.
16
+ MAXIMUM_READ_SIZE = ENV.fetch('IO_STREAM_MAXIMUM_READ_SIZE', BLOCK_SIZE * 128).to_i
17
+
18
+ class BufferedStream
19
+ def self.open(path, mode = "r+", **options)
20
+ stream = self.new(File.open(path, mode), **options)
21
+
22
+ return stream unless block_given?
23
+
24
+ begin
25
+ yield stream
26
+ ensure
27
+ stream.close
28
+ end
29
+ end
30
+
31
+ def self.wrap(io, **options)
32
+ if io.respond_to?(:buffered=)
33
+ io.buffered = false
34
+ end
35
+
36
+ self.new(io, **options)
37
+ end
38
+
39
+ def initialize(io, block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE)
40
+ @io = io
41
+ @eof = false
42
+
43
+ @writing = Thread::Mutex.new
44
+
45
+ @block_size = block_size
46
+ @maximum_read_size = maximum_read_size
47
+
48
+ @read_buffer = StringBuffer.new
49
+ @write_buffer = StringBuffer.new
50
+ @drain_buffer = StringBuffer.new
51
+
52
+ # Used as destination buffer for underlying reads.
53
+ @input_buffer = StringBuffer.new
54
+ end
55
+
56
+ attr :io
57
+
58
+ attr :block_size
59
+
60
+ # Reads `size` bytes from the stream. If size is not specified, read until end of file.
61
+ def read(size = nil)
62
+ return String.new(encoding: Encoding::BINARY) if size == 0
63
+
64
+ if size
65
+ until @eof or @read_buffer.bytesize >= size
66
+ # Compute the amount of data we need to read from the underlying stream:
67
+ read_size = size - @read_buffer.bytesize
68
+
69
+ # Don't read less than @block_size to avoid lots of small reads:
70
+ fill_read_buffer(read_size > @block_size ? read_size : @block_size)
71
+ end
72
+ else
73
+ until @eof
74
+ fill_read_buffer
75
+ end
76
+ end
77
+
78
+ return consume_read_buffer(size)
79
+ end
80
+
81
+ # Read at most `size` bytes from the stream. Will avoid reading from the underlying stream if possible.
82
+ def read_partial(size = nil)
83
+ return String.new(encoding: Encoding::BINARY) if size == 0
84
+
85
+ if !@eof and @read_buffer.empty?
86
+ fill_read_buffer
87
+ end
88
+
89
+ return consume_read_buffer(size)
90
+ end
91
+
92
+ def read_exactly(size, exception: EOFError)
93
+ if buffer = read(size)
94
+ if buffer.bytesize != size
95
+ raise exception, "could not read enough data"
96
+ end
97
+
98
+ return buffer
99
+ end
100
+
101
+ raise exception, "encountered eof while reading data"
102
+ end
103
+
104
+ # This is a compatibility shim for existing code:
105
+ def readpartial(size = nil)
106
+ read_partial(size) or raise EOFError, "Encountered eof while reading data!"
107
+ end
108
+
109
+ # Efficiently read data from the stream until encountering pattern.
110
+ # @param pattern [String] The pattern to match.
111
+ # @return [String] The contents of the stream up until the pattern, which is consumed but not returned.
112
+ def read_until(pattern, offset = 0, chomp: true)
113
+ # We don't want to split on the pattern, so we subtract the size of the pattern.
114
+ split_offset = pattern.bytesize - 1
115
+
116
+ until index = @read_buffer.index(pattern, offset)
117
+ offset = @read_buffer.bytesize - split_offset
118
+
119
+ offset = 0 if offset < 0
120
+
121
+ return unless fill_read_buffer
122
+ end
123
+
124
+ @read_buffer.freeze
125
+ matched = @read_buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize))
126
+ @read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
127
+
128
+ return matched
129
+ end
130
+
131
+ def peek(size = nil)
132
+ if size
133
+ until @eof or @read_buffer.bytesize >= size
134
+ # Compute the amount of data we need to read from the underlying stream:
135
+ read_size = size - @read_buffer.bytesize
136
+
137
+ # Don't read less than @block_size to avoid lots of small reads:
138
+ fill_read_buffer(read_size > @block_size ? read_size : @block_size)
139
+ end
140
+ return @read_buffer[..([size, @read_buffer.size].min - 1)]
141
+ end
142
+ until (block_given? && yield(@read_buffer)) or @eof
143
+ fill_read_buffer
144
+ end
145
+ return @read_buffer
146
+ end
147
+
148
+ def gets(separator = $/, **options)
149
+ read_until(separator, **options)
150
+ end
151
+
152
+ # Flushes buffered data to the stream.
153
+ def flush
154
+ return if @write_buffer.empty?
155
+
156
+ @writing.synchronize do
157
+ # Flip the write buffer and drain buffer:
158
+ @write_buffer, @drain_buffer = @drain_buffer, @write_buffer
159
+
160
+ begin
161
+ @io.syswrite(@drain_buffer)
162
+ ensure
163
+ # If the write operation fails, we still need to clear this buffer, and the data is essentially lost.
164
+ @drain_buffer.clear
165
+ end
166
+ end
167
+ end
168
+
169
+ # Writes `string` to the buffer. When the buffer is full or #sync is true the
170
+ # buffer is flushed to the underlying `io`.
171
+ # @param string the string to write to the buffer.
172
+ # @return the number of bytes appended to the buffer.
173
+ def write(string)
174
+ @write_buffer << string
175
+
176
+ if @write_buffer.bytesize >= @block_size
177
+ flush
178
+ end
179
+
180
+ return string.bytesize
181
+ end
182
+
183
+ # Writes `string` to the stream and returns self.
184
+ def <<(string)
185
+ write(string)
186
+
187
+ return self
188
+ end
189
+
190
+ def puts(*arguments, separator: $/)
191
+ arguments.each do |argument|
192
+ @write_buffer << argument << separator
193
+ end
194
+
195
+ flush
196
+ end
197
+
198
+ def connected?
199
+ @io.connected?
200
+ end
201
+
202
+ def closed?
203
+ @io.closed?
204
+ end
205
+
206
+ def close_read
207
+ @io.close_read
208
+ end
209
+
210
+ def close_write
211
+ flush
212
+ ensure
213
+ @io.close_write
214
+ end
215
+
216
+ # Best effort to flush any unwritten data, and then close the underling IO.
217
+ def close
218
+ return if @io.closed?
219
+
220
+ begin
221
+ flush
222
+ rescue
223
+ # We really can't do anything here unless we want #close to raise exceptions.
224
+ ensure
225
+ @io.close
226
+ end
227
+ end
228
+
229
+ # Determins if the stream has consumed all available data. May block if the stream is not readable.
230
+ # See {readable?} for a non-blocking alternative.
231
+ #
232
+ # @returns [Boolean] If the stream is at file which means there is no more data to be read.
233
+ def eof?
234
+ if !@read_buffer.empty?
235
+ return false
236
+ elsif @eof
237
+ return true
238
+ else
239
+ return !self.fill_read_buffer
240
+ end
241
+ end
242
+
243
+ def eof!
244
+ @read_buffer.clear
245
+ @eof = true
246
+
247
+ raise EOFError
248
+ end
249
+
250
+ # Whether there is a chance that a read operation will succeed or not.
251
+ # @returns [Boolean] If the stream is readable, i.e. a `read` operation has a chance of success.
252
+ def readable?
253
+ # If we are at the end of the file, we can't read any more data:
254
+ if @eof
255
+ return false
256
+ end
257
+
258
+ # If the read buffer is not empty, we can read more data:
259
+ if !@read_buffer.empty?
260
+ return true
261
+ end
262
+
263
+ # If the underlying stream is readable, we can read more data:
264
+ return @io.readable?
265
+ end
266
+
267
+ private
268
+
269
+ # Reads data from the underlying stream as efficiently as possible.
270
+ def sysread(size, buffer)
271
+ # Come on Ruby, why couldn't this just return `nil`? EOF is not exceptional. Every file has one.
272
+ @io.sysread(size, buffer)
273
+ rescue EOFError
274
+ return false
275
+ end
276
+
277
+ # Fills the buffer from the underlying stream.
278
+ def fill_read_buffer(size = @block_size)
279
+ # We impose a limit because the underlying `read` system call can fail if we request too much data in one go.
280
+ if size > @maximum_read_size
281
+ size = @maximum_read_size
282
+ end
283
+
284
+ # This effectively ties the input and output stream together.
285
+ flush
286
+
287
+ if @read_buffer.empty?
288
+ if sysread(size, @read_buffer)
289
+ # Console.logger.debug(self, name: "read") {@read_buffer.inspect}
290
+ return true
291
+ end
292
+ else
293
+ if chunk = sysread(size, @input_buffer)
294
+ @read_buffer << chunk
295
+ # Console.logger.debug(self, name: "read") {@read_buffer.inspect}
296
+
297
+ return true
298
+ end
299
+ end
300
+
301
+ # else for both cases above:
302
+ @eof = true
303
+ return false
304
+ end
305
+
306
+ # Consumes at most `size` bytes from the buffer.
307
+ # @param size [Integer|nil] The amount of data to consume. If nil, consume entire buffer.
308
+ def consume_read_buffer(size = nil)
309
+ # If we are at eof, and the read buffer is empty, we can't consume anything.
310
+ return nil if @eof && @read_buffer.empty?
311
+
312
+ result = nil
313
+
314
+ if size.nil? or size >= @read_buffer.bytesize
315
+ # Consume the entire read buffer:
316
+ result = @read_buffer
317
+ @read_buffer = StringBuffer.new
318
+ else
319
+ # This approach uses more memory.
320
+ # result = @read_buffer.slice!(0, size)
321
+
322
+ # We know that we are not going to reuse the original buffer.
323
+ # But byteslice will generate a hidden copy. So let's freeze it first:
324
+ @read_buffer.freeze
325
+
326
+ result = @read_buffer.byteslice(0, size)
327
+ @read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize)
328
+ end
329
+
330
+ return result
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ module IO::Stream
7
+ class StringBuffer < String
8
+ BINARY = Encoding::BINARY
9
+
10
+ def initialize
11
+ super
12
+
13
+ force_encoding(BINARY)
14
+ end
15
+
16
+ def << string
17
+ if string.encoding == BINARY
18
+ super(string)
19
+ else
20
+ super(string.b)
21
+ end
22
+
23
+ return self
24
+ end
25
+
26
+ alias concat <<
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ module IO::Stream
7
+ VERSION = "0.0.1"
8
+ end
data/lib/io/stream.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ require_relative 'stream/version'
7
+ require_relative 'stream/buffered_stream'
8
+
9
+ class IO
10
+ module Stream
11
+ end
12
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2023-2024, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,31 @@
1
+ # IO::Stream
2
+
3
+ Provide a buffered stream implementation for Ruby, independent of the underlying IO.
4
+
5
+ [![Development Status](https://github.com/socketry/io-stream/workflows/Test/badge.svg)](https://github.com/socketry/io-stream/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [project documentation](https://socketry.github.io/io-stream) for more details.
10
+
11
+ ## Contributing
12
+
13
+ We welcome contributions to this project.
14
+
15
+ 1. Fork it.
16
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
17
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
18
+ 4. Push to the branch (`git push origin my-new-feature`).
19
+ 5. Create new Pull Request.
20
+
21
+ ### Developer Certificate of Origin
22
+
23
+ This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
24
+
25
+ ### Contributor Covenant
26
+
27
+ This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
28
+
29
+ ## See Also
30
+
31
+ - [async-io](https://github.com/socketry/async-io) — Where this implementation originally came from.
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: io-stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
14
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
15
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
16
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
17
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
18
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
19
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
20
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
21
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
22
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
23
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
24
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
25
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
26
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
27
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
28
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
30
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
31
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
32
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
33
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
34
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
35
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
36
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
37
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
+ -----END CERTIFICATE-----
40
+ date: 2024-04-22 00:00:00.000000000 Z
41
+ dependencies: []
42
+ description:
43
+ email:
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/io/buffered.rb
49
+ - lib/io/readable.rb
50
+ - lib/io/stream.rb
51
+ - lib/io/stream/buffered_stream.rb
52
+ - lib/io/stream/string_buffer.rb
53
+ - lib/io/stream/version.rb
54
+ - license.md
55
+ - readme.md
56
+ homepage: https://github.com/socketry/io-stream
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ documentation_uri: https://socketry.github.io/io-stream
61
+ source_code_uri: https://github.com/socketry/io-stream.git
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.1'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.5.3
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Provides a generic stream wrapper for IO instances.
81
+ test_files: []
metadata.gz.sig ADDED
@@ -0,0 +1 @@
1
+ ��f֩�����P�E�-z˶�oQ%�/*$(��Id��߄3$J x�V�(;�$ewJ �x:��x�?︊w�����h����E���8�(��[�^}�u�ΙL�HZ1���!�g �t��z`�n��"�z�C��g��j=�4��L^/�NF�����pD�f�!���Y\�"٢q�s�ڀ4v[�|؂�~�m��[�0�+�`Y����هiV�V]ҽ�nT1�ۻ��e���ɓNX��C�P��&��nYJ�`q��b��� U��{6�ӳ'*=�}� =��5[��\�NGHΦ�f>���y2y�U�c���X�:��%2Zχ���e�݅���N������ĪΨ3�+��˥6R˽p�Oܸ,•�t����