io-stream 0.7.0 → 0.9.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/io/stream/buffered.rb +19 -16
- data/lib/io/stream/generic.rb +1 -1
- data/lib/io/stream/readable.rb +98 -23
- data/lib/io/stream/version.rb +1 -1
- data/readme.md +9 -9
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 108295b1d28ce05e7c9e9e3f7b3d26427f564a85527a9f23a24ad5b47a261dea
|
4
|
+
data.tar.gz: 2aa1c2f6d600d38667e5030122aecd1faa97f81be44695edf6776dda541d5a89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6c9fc15b84c0ef6d58e2400aeebaac0a36fd67f656dcf10e2f4a1dde2176434dc8f9464809f2b5c05af2563c8bcb1a74ef1e243b7263348d36d24f00df2af7c
|
7
|
+
data.tar.gz: 92c3a90776a6f6308ab7d0fa25d35b7df65021f70cb8aadf01e7363961c818392c2ce3fe861b20b38965acebcca851e7419708ad18b2536ae892496b9ea39ade
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/io/stream/buffered.rb
CHANGED
@@ -106,23 +106,26 @@ module IO::Stream
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
return
|
109
|
+
if RUBY_VERSION >= "3.3"
|
110
|
+
def syswrite(buffer)
|
111
|
+
return @io.write(buffer)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
def syswrite(buffer)
|
115
|
+
while true
|
116
|
+
result = @io.write_nonblock(buffer, exception: false)
|
117
|
+
|
118
|
+
case result
|
119
|
+
when :wait_readable
|
120
|
+
@io.wait_readable(@io.timeout) or raise ::IO::TimeoutError, "read timeout"
|
121
|
+
when :wait_writable
|
122
|
+
@io.wait_writable(@io.timeout) or raise ::IO::TimeoutError, "write timeout"
|
124
123
|
else
|
125
|
-
|
124
|
+
if result == buffer.bytesize
|
125
|
+
return
|
126
|
+
else
|
127
|
+
buffer = buffer.byteslice(result, buffer.bytesize)
|
128
|
+
end
|
126
129
|
end
|
127
130
|
end
|
128
131
|
end
|
data/lib/io/stream/generic.rb
CHANGED
data/lib/io/stream/readable.rb
CHANGED
@@ -58,9 +58,18 @@ module IO::Stream
|
|
58
58
|
|
59
59
|
# Read data from the stream.
|
60
60
|
# @parameter size [Integer | Nil] The number of bytes to read. If nil, read until end of stream.
|
61
|
-
# @
|
62
|
-
|
63
|
-
|
61
|
+
# @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
|
62
|
+
# @returns [String] The data read from the stream, or the provided buffer filled with data.
|
63
|
+
def read(size = nil, buffer = nil)
|
64
|
+
if size == 0
|
65
|
+
if buffer
|
66
|
+
buffer.clear
|
67
|
+
buffer.force_encoding(Encoding::BINARY)
|
68
|
+
return buffer
|
69
|
+
else
|
70
|
+
return String.new(encoding: Encoding::BINARY)
|
71
|
+
end
|
72
|
+
end
|
64
73
|
|
65
74
|
if size
|
66
75
|
until @done or @read_buffer.bytesize >= size
|
@@ -76,26 +85,37 @@ module IO::Stream
|
|
76
85
|
end
|
77
86
|
end
|
78
87
|
|
79
|
-
return consume_read_buffer(size)
|
88
|
+
return consume_read_buffer(size, buffer)
|
80
89
|
end
|
81
90
|
|
82
91
|
# Read at most `size` bytes from the stream. Will avoid reading from the underlying stream if possible.
|
83
|
-
|
84
|
-
|
92
|
+
# @parameter size [Integer | Nil] The number of bytes to read. If nil, read all available data.
|
93
|
+
# @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
|
94
|
+
# @returns [String] The data read from the stream, or the provided buffer filled with data.
|
95
|
+
def read_partial(size = nil, buffer = nil)
|
96
|
+
if size == 0
|
97
|
+
if buffer
|
98
|
+
buffer.clear
|
99
|
+
buffer.force_encoding(Encoding::BINARY)
|
100
|
+
return buffer
|
101
|
+
else
|
102
|
+
return String.new(encoding: Encoding::BINARY)
|
103
|
+
end
|
104
|
+
end
|
85
105
|
|
86
106
|
if !@done and @read_buffer.empty?
|
87
107
|
fill_read_buffer
|
88
108
|
end
|
89
109
|
|
90
|
-
return consume_read_buffer(size)
|
110
|
+
return consume_read_buffer(size, buffer)
|
91
111
|
end
|
92
112
|
|
93
113
|
# Read exactly the specified number of bytes.
|
94
114
|
# @parameter size [Integer] The number of bytes to read.
|
95
115
|
# @parameter exception [Class] The exception to raise if not enough data is available.
|
96
116
|
# @returns [String] The data read from the stream.
|
97
|
-
def read_exactly(size, exception: EOFError)
|
98
|
-
if buffer = read(size)
|
117
|
+
def read_exactly(size, buffer = nil, exception: EOFError)
|
118
|
+
if buffer = read(size, buffer)
|
99
119
|
if buffer.bytesize != size
|
100
120
|
raise exception, "Could not read enough data!"
|
101
121
|
end
|
@@ -107,8 +127,11 @@ module IO::Stream
|
|
107
127
|
end
|
108
128
|
|
109
129
|
# This is a compatibility shim for existing code that uses `readpartial`.
|
110
|
-
|
111
|
-
|
130
|
+
# @parameter size [Integer | Nil] The number of bytes to read.
|
131
|
+
# @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
|
132
|
+
# @returns [String] The data read from the stream.
|
133
|
+
def readpartial(size = nil, buffer = nil)
|
134
|
+
read_partial(size, buffer) or raise EOFError, "Encountered done while reading data!"
|
112
135
|
end
|
113
136
|
|
114
137
|
# Find the index of a pattern in the read buffer, reading more data if needed.
|
@@ -116,17 +139,28 @@ module IO::Stream
|
|
116
139
|
# @parameter offset [Integer] The offset to start searching from.
|
117
140
|
# @parameter limit [Integer | Nil] The maximum number of bytes to read while searching.
|
118
141
|
# @returns [Integer | Nil] The index of the pattern, or nil if not found.
|
119
|
-
private def index_of(pattern, offset, limit)
|
142
|
+
private def index_of(pattern, offset, limit, discard = false)
|
120
143
|
# We don't want to split on the pattern, so we subtract the size of the pattern.
|
121
144
|
split_offset = pattern.bytesize - 1
|
122
|
-
|
145
|
+
|
123
146
|
until index = @read_buffer.index(pattern, offset)
|
124
147
|
offset = @read_buffer.bytesize - split_offset
|
125
148
|
|
126
149
|
offset = 0 if offset < 0
|
127
150
|
|
128
|
-
|
129
|
-
|
151
|
+
if limit and offset >= limit
|
152
|
+
return nil
|
153
|
+
end
|
154
|
+
|
155
|
+
unless fill_read_buffer
|
156
|
+
return nil
|
157
|
+
end
|
158
|
+
|
159
|
+
if discard
|
160
|
+
# If we are discarding, we should consume the read buffer up to the offset:
|
161
|
+
consume_read_buffer(offset)
|
162
|
+
offset = 0
|
163
|
+
end
|
130
164
|
end
|
131
165
|
|
132
166
|
return index
|
@@ -136,7 +170,8 @@ module IO::Stream
|
|
136
170
|
# @parameter pattern [String] The pattern to match.
|
137
171
|
# @parameter offset [Integer] The offset to start searching from.
|
138
172
|
# @parameter limit [Integer] The maximum number of bytes to read, including the pattern (even if chomped).
|
139
|
-
# @
|
173
|
+
# @parameter chomp [Boolean] Whether to remove the pattern from the returned data.
|
174
|
+
# @returns [String | Nil] The contents of the stream up until the pattern, or nil if the pattern was not found.
|
140
175
|
def read_until(pattern, offset = 0, limit: nil, chomp: true)
|
141
176
|
if index = index_of(pattern, offset, limit)
|
142
177
|
return nil if limit and index >= limit
|
@@ -149,6 +184,28 @@ module IO::Stream
|
|
149
184
|
end
|
150
185
|
end
|
151
186
|
|
187
|
+
# Efficiently discard data from the stream until encountering pattern.
|
188
|
+
# @parameter pattern [String] The pattern to match.
|
189
|
+
# @parameter offset [Integer] The offset to start searching from.
|
190
|
+
# @parameter limit [Integer] The maximum number of bytes to read, including the pattern.
|
191
|
+
# @returns [String | Nil] The contents of the stream up until the pattern, or nil if the pattern was not found.
|
192
|
+
def discard_until(pattern, offset = 0, limit: nil)
|
193
|
+
if index = index_of(pattern, offset, limit, true)
|
194
|
+
@read_buffer.freeze
|
195
|
+
|
196
|
+
if limit and index >= limit
|
197
|
+
@read_buffer = @read_buffer.byteslice(limit, @read_buffer.bytesize)
|
198
|
+
|
199
|
+
return nil
|
200
|
+
end
|
201
|
+
|
202
|
+
matched = @read_buffer.byteslice(0, index+pattern.bytesize)
|
203
|
+
@read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
|
204
|
+
|
205
|
+
return matched
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
152
209
|
# Peek at data in the buffer without consuming it.
|
153
210
|
# @parameter size [Integer | Nil] The number of bytes to peek at. If nil, peek at all available data.
|
154
211
|
# @returns [String] The data in the buffer without consuming it.
|
@@ -296,25 +353,43 @@ module IO::Stream
|
|
296
353
|
|
297
354
|
# Consumes at most `size` bytes from the buffer.
|
298
355
|
# @parameter size [Integer | Nil] The amount of data to consume. If nil, consume entire buffer.
|
299
|
-
|
356
|
+
# @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
|
357
|
+
# @returns [String | Nil] The consumed data, or nil if no data available.
|
358
|
+
def consume_read_buffer(size = nil, buffer = nil)
|
300
359
|
# If we are at done, and the read buffer is empty, we can't consume anything.
|
301
|
-
|
360
|
+
if @done && @read_buffer.empty?
|
361
|
+
# Clear the buffer even when returning nil
|
362
|
+
if buffer
|
363
|
+
buffer.clear
|
364
|
+
buffer.force_encoding(Encoding::BINARY)
|
365
|
+
end
|
366
|
+
return nil
|
367
|
+
end
|
302
368
|
|
303
369
|
result = nil
|
304
370
|
|
305
371
|
if size.nil? or size >= @read_buffer.bytesize
|
306
372
|
# Consume the entire read buffer:
|
307
|
-
|
373
|
+
if buffer
|
374
|
+
buffer.clear
|
375
|
+
buffer << @read_buffer
|
376
|
+
result = buffer
|
377
|
+
else
|
378
|
+
result = @read_buffer
|
379
|
+
end
|
308
380
|
@read_buffer = StringBuffer.new
|
309
381
|
else
|
310
|
-
# This approach uses more memory.
|
311
|
-
# result = @read_buffer.slice!(0, size)
|
312
|
-
|
313
382
|
# We know that we are not going to reuse the original buffer.
|
314
383
|
# But byteslice will generate a hidden copy. So let's freeze it first:
|
315
384
|
@read_buffer.freeze
|
316
385
|
|
317
|
-
|
386
|
+
if buffer
|
387
|
+
# Use replace instead of clear + << for better performance
|
388
|
+
buffer.replace(@read_buffer.byteslice(0, size))
|
389
|
+
result = buffer
|
390
|
+
else
|
391
|
+
result = @read_buffer.byteslice(0, size)
|
392
|
+
end
|
318
393
|
@read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize)
|
319
394
|
end
|
320
395
|
|
data/lib/io/stream/version.rb
CHANGED
data/readme.md
CHANGED
@@ -12,6 +12,15 @@ Please see the [project documentation](https://socketry.github.io/io-stream) for
|
|
12
12
|
|
13
13
|
Please see the [project releases](https://socketry.github.io/io-streamreleases/index) for all releases.
|
14
14
|
|
15
|
+
### v0.9.0
|
16
|
+
|
17
|
+
- Add support for `buffer` parameter in `read`, `read_exactly`, and `read_partial` methods to allow reading into a provided buffer.
|
18
|
+
|
19
|
+
### v0.8.0
|
20
|
+
|
21
|
+
- On Ruby v3.3+, use `IO#write` directly instead of `IO#write_nonblock`, for better performance.
|
22
|
+
- Introduce support for `Readable#discard_until` method to discard data until a specific pattern is found.
|
23
|
+
|
15
24
|
### v0.7.0
|
16
25
|
|
17
26
|
- Split stream functionality into separate `Readable` and `Writable` modules for better modularity and composition.
|
@@ -53,15 +62,6 @@ Please see the [project releases](https://socketry.github.io/io-streamreleases/i
|
|
53
62
|
|
54
63
|
- Add convenient `IO.Stream()` constructor method for creating buffered streams.
|
55
64
|
|
56
|
-
### v0.3.0
|
57
|
-
|
58
|
-
- Add support for timeouts with compatibility shims for various IO types.
|
59
|
-
|
60
|
-
### v0.2.0
|
61
|
-
|
62
|
-
- Prefer `write_nonblock` in `syswrite` implementation for better non-blocking behavior.
|
63
|
-
- Add test cases for crash scenarios.
|
64
|
-
|
65
65
|
## See Also
|
66
66
|
|
67
67
|
- [async-io](https://github.com/socketry/async-io) — Where this implementation originally came from.
|
data/releases.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v0.9.0
|
4
|
+
|
5
|
+
- Add support for `buffer` parameter in `read`, `read_exactly`, and `read_partial` methods to allow reading into a provided buffer.
|
6
|
+
|
7
|
+
## v0.8.0
|
8
|
+
|
9
|
+
- On Ruby v3.3+, use `IO#write` directly instead of `IO#write_nonblock`, for better performance.
|
10
|
+
- Introduce support for `Readable#discard_until` method to discard data until a specific pattern is found.
|
11
|
+
|
3
12
|
## v0.7.0
|
4
13
|
|
5
14
|
- Split stream functionality into separate `Readable` and `Writable` modules for better modularity and composition.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
Binary file
|