io-like 0.3.1 → 0.4.0.pre1
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 +7 -0
- data/LICENSE +1 -1
- data/NEWS.md +14 -1
- data/README.md +75 -94
- data/lib/io/like.rb +1916 -1314
- data/lib/io/like_helpers/abstract_io.rb +512 -0
- data/lib/io/like_helpers/blocking_io.rb +86 -0
- data/lib/io/like_helpers/buffered_io.rb +555 -0
- data/lib/io/like_helpers/character_io/basic_reader.rb +122 -0
- data/lib/io/like_helpers/character_io/converter_reader.rb +252 -0
- data/lib/io/like_helpers/character_io.rb +529 -0
- data/lib/io/like_helpers/delegated_io.rb +250 -0
- data/lib/io/like_helpers/duplexed_io.rb +259 -0
- data/lib/io/like_helpers/io.rb +21 -0
- data/lib/io/like_helpers/io_wrapper.rb +290 -0
- data/lib/io/like_helpers/pipeline.rb +77 -0
- data/lib/io/like_helpers/ruby_facts.rb +33 -0
- data/lib/io/like_helpers.rb +14 -0
- metadata +107 -224
- data/.yardopts +0 -1
- data/Rakefile +0 -228
- data/ruby.1.8.mspec +0 -7
- data/spec/binmode_spec.rb +0 -29
- data/spec/close_read_spec.rb +0 -64
- data/spec/close_spec.rb +0 -36
- data/spec/close_write_spec.rb +0 -61
- data/spec/closed_spec.rb +0 -16
- data/spec/each_byte_spec.rb +0 -38
- data/spec/each_line_spec.rb +0 -11
- data/spec/each_spec.rb +0 -11
- data/spec/eof_spec.rb +0 -11
- data/spec/fixtures/classes.rb +0 -96
- data/spec/fixtures/gets.txt +0 -9
- data/spec/fixtures/numbered_lines.txt +0 -5
- data/spec/fixtures/one_byte.txt +0 -1
- data/spec/fixtures/paragraphs.txt +0 -7
- data/spec/fixtures/readlines.txt +0 -6
- data/spec/flush_spec.rb +0 -8
- data/spec/getc_spec.rb +0 -44
- data/spec/gets_spec.rb +0 -212
- data/spec/isatty_spec.rb +0 -6
- data/spec/lineno_spec.rb +0 -84
- data/spec/output_spec.rb +0 -47
- data/spec/pos_spec.rb +0 -53
- data/spec/print_spec.rb +0 -97
- data/spec/printf_spec.rb +0 -24
- data/spec/putc_spec.rb +0 -57
- data/spec/puts_spec.rb +0 -99
- data/spec/read_spec.rb +0 -162
- data/spec/readchar_spec.rb +0 -49
- data/spec/readline_spec.rb +0 -60
- data/spec/readlines_spec.rb +0 -140
- data/spec/readpartial_spec.rb +0 -92
- data/spec/rewind_spec.rb +0 -56
- data/spec/seek_spec.rb +0 -72
- data/spec/shared/each.rb +0 -204
- data/spec/shared/eof.rb +0 -116
- data/spec/shared/pos.rb +0 -39
- data/spec/shared/tty.rb +0 -12
- data/spec/shared/write.rb +0 -53
- data/spec/sync_spec.rb +0 -56
- data/spec/sysread_spec.rb +0 -87
- data/spec/sysseek_spec.rb +0 -68
- data/spec/syswrite_spec.rb +0 -60
- data/spec/tell_spec.rb +0 -7
- data/spec/to_io_spec.rb +0 -19
- data/spec/tty_spec.rb +0 -6
- data/spec/ungetc_spec.rb +0 -118
- data/spec/write_spec.rb +0 -61
- data/spec_helper.rb +0 -49
- /data/{LICENSE-rubyspec → rubyspec/LICENSE} +0 -0
@@ -0,0 +1,555 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/like_helpers/delegated_io'
|
4
|
+
require 'io/like_helpers/io'
|
5
|
+
|
6
|
+
class IO; module LikeHelpers
|
7
|
+
|
8
|
+
##
|
9
|
+
# This class implements a stream that buffers data read from or written to a
|
10
|
+
# delegate.
|
11
|
+
class BufferedIO < DelegatedIO
|
12
|
+
##
|
13
|
+
# The default size of the internal buffer.
|
14
|
+
DEFAULT_BUFFER_SIZE = 8192
|
15
|
+
|
16
|
+
##
|
17
|
+
# Creates a new intance of this class.
|
18
|
+
#
|
19
|
+
# @param delegate [LikeHelpers::AbstractIO] a readable and/or writable stream
|
20
|
+
# @param autoclose [Boolean] when `true` close the delegate when this stream
|
21
|
+
# is closed
|
22
|
+
# @param buffer_size [Integer] the size of the internal buffer in bytes
|
23
|
+
def initialize(delegate, autoclose: true, buffer_size: DEFAULT_BUFFER_SIZE)
|
24
|
+
buffer_size = Integer(buffer_size)
|
25
|
+
if buffer_size <= 0
|
26
|
+
raise ArgumentError, 'buffer_size must be greater than 0'
|
27
|
+
end
|
28
|
+
|
29
|
+
super(delegate, autoclose: autoclose)
|
30
|
+
|
31
|
+
@buffer_size = buffer_size
|
32
|
+
@buffer = "\0".b * @buffer_size
|
33
|
+
@start_idx = @end_idx = @unread_offset = 0
|
34
|
+
@mode = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# The size of the internal buffer in bytes.
|
39
|
+
attr_reader :buffer_size
|
40
|
+
|
41
|
+
##
|
42
|
+
# Closes this stream, flushing data from the write buffer first if necessary.
|
43
|
+
#
|
44
|
+
# The delegate is closed if autoclose is enabled for the stream.
|
45
|
+
#
|
46
|
+
# @return [nil] on success
|
47
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
48
|
+
# the operation would block
|
49
|
+
def close
|
50
|
+
return nil if closed?
|
51
|
+
|
52
|
+
begin
|
53
|
+
result = flush if @mode == :write
|
54
|
+
ensure
|
55
|
+
# Complete the closing process if #flush completed normally or an
|
56
|
+
# exception was raised.
|
57
|
+
result = super unless Symbol === result
|
58
|
+
end
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Flushes any data in the write buffer and then forwards the call to the
|
64
|
+
# delegate.
|
65
|
+
#
|
66
|
+
# @return [0, nil] on success
|
67
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
68
|
+
# the operation would block
|
69
|
+
def fdatasync
|
70
|
+
assert_open
|
71
|
+
|
72
|
+
result = flush
|
73
|
+
return result if Symbol === result
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Flushes any data in the write buffer to the delegate.
|
79
|
+
#
|
80
|
+
# @return [nil] on success
|
81
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
82
|
+
# the operation would block
|
83
|
+
#
|
84
|
+
# @raise [IOError] if the stream is closed
|
85
|
+
def flush
|
86
|
+
assert_open
|
87
|
+
|
88
|
+
set_write_mode
|
89
|
+
|
90
|
+
while @start_idx < @end_idx do
|
91
|
+
remaining = @end_idx - @start_idx
|
92
|
+
result = delegate.write(@buffer[@start_idx, remaining])
|
93
|
+
return result if Symbol === result
|
94
|
+
@start_idx += result
|
95
|
+
end
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Flushes any data in the write buffer and then forwards the call to the
|
101
|
+
# delegate.
|
102
|
+
#
|
103
|
+
# @return [0, nil] on success
|
104
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
105
|
+
# the operation would block
|
106
|
+
#
|
107
|
+
# @raise [IOError] if the stream is closed
|
108
|
+
def fsync
|
109
|
+
result = flush
|
110
|
+
return result if Symbol === result
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# @return [Integer] the number of bytes available to read from the internal
|
116
|
+
# buffer
|
117
|
+
#
|
118
|
+
# @raise [IOError] if the stream is not readable
|
119
|
+
def nread
|
120
|
+
assert_readable
|
121
|
+
|
122
|
+
return 0 if read_buffer_empty?
|
123
|
+
return @end_idx - @start_idx
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Reads up to `length` bytes from the read buffer but does not advance the
|
128
|
+
# stream position.
|
129
|
+
#
|
130
|
+
# @param length [Integer, nil] the number of bytes to read or `nil` for all
|
131
|
+
# bytes
|
132
|
+
#
|
133
|
+
# @return [String] a buffer containing the bytes read
|
134
|
+
#
|
135
|
+
# @raise [IOError] if the stream is not readable
|
136
|
+
def peek(length = nil)
|
137
|
+
if length.nil?
|
138
|
+
length = nread
|
139
|
+
else
|
140
|
+
length = Integer(length)
|
141
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
142
|
+
end
|
143
|
+
|
144
|
+
assert_readable
|
145
|
+
|
146
|
+
return ''.b unless @mode == :read
|
147
|
+
|
148
|
+
available = @end_idx - @start_idx
|
149
|
+
length = available if available < length
|
150
|
+
return @buffer[@start_idx, length]
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Reads at most `length` bytes from the stream starting at `offset` without
|
155
|
+
# modifying the read position in the stream.
|
156
|
+
#
|
157
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
158
|
+
# and reading more bytes would block.
|
159
|
+
#
|
160
|
+
# @note This method is not thread safe. Override it and add a mutex if thread
|
161
|
+
# safety is desired.
|
162
|
+
#
|
163
|
+
# @param length [Integer] the maximum number of bytes to read
|
164
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
165
|
+
# to begin reading
|
166
|
+
# @param buffer [String] if provided, a buffer into which the bytes should be
|
167
|
+
# placed
|
168
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
169
|
+
# `buffer`
|
170
|
+
#
|
171
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
172
|
+
# @return [String] a new String containing the bytes read if `buffer` is `nil`
|
173
|
+
# or `buffer` if provided
|
174
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
175
|
+
# the operation would block
|
176
|
+
#
|
177
|
+
# @raise [EOFError] when reading at the end of the stream
|
178
|
+
# @raise [IOError] if the stream is not readable
|
179
|
+
def pread(length, offset, buffer: nil, buffer_offset: 0)
|
180
|
+
offset = Integer(offset)
|
181
|
+
raise ArgumentError, 'offset must be at least 0' if offset < 0
|
182
|
+
length = Integer(length)
|
183
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
184
|
+
if ! buffer.nil?
|
185
|
+
if buffer_offset < 0 || buffer_offset >= buffer.bytesize
|
186
|
+
raise ArgumentError, 'buffer_offset is not a valid buffer index'
|
187
|
+
end
|
188
|
+
if buffer.bytesize - buffer_offset < length
|
189
|
+
raise ArgumentError, 'length is greater than available buffer space'
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
assert_readable
|
194
|
+
|
195
|
+
result = set_read_mode
|
196
|
+
return result if Symbol === result
|
197
|
+
|
198
|
+
super
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Writes at most `length` bytes to the stream starting at `offset` without
|
203
|
+
# modifying the write position in the stream.
|
204
|
+
#
|
205
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
206
|
+
# and writing more bytes would block.
|
207
|
+
#
|
208
|
+
# @note This method is not thread safe. Override it and add a mutex if thread
|
209
|
+
# safety is desired.
|
210
|
+
#
|
211
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
212
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
213
|
+
# to begin writing
|
214
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
215
|
+
#
|
216
|
+
# @return [Integer] the number of bytes written
|
217
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
218
|
+
# the operation would block
|
219
|
+
#
|
220
|
+
# @raise [IOError] if the stream is not writable
|
221
|
+
def pwrite(buffer, offset, length: buffer.bytesize)
|
222
|
+
offset = Integer(offset)
|
223
|
+
raise ArgumentError, 'offset must be at least 0' if offset < 0
|
224
|
+
length = Integer(length)
|
225
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
226
|
+
|
227
|
+
assert_writable
|
228
|
+
|
229
|
+
set_write_mode
|
230
|
+
|
231
|
+
super
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Reads bytes from the stream.
|
236
|
+
#
|
237
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
238
|
+
# and reading more bytes would block.
|
239
|
+
#
|
240
|
+
# @param length [Integer] the number of bytes to read
|
241
|
+
# @param buffer [String] the buffer into which bytes will be read (encoding
|
242
|
+
# assumed to be binary)
|
243
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
244
|
+
# `buffer`
|
245
|
+
#
|
246
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
247
|
+
# @return [String] a buffer containing the bytes read if `buffer` is `nil`
|
248
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
249
|
+
# the operation would block
|
250
|
+
#
|
251
|
+
# @raise [EOFError] when reading at the end of the stream
|
252
|
+
# @raise [IOError] if the stream is not readable
|
253
|
+
def read(length, buffer: nil, buffer_offset: 0)
|
254
|
+
length = Integer(length)
|
255
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
256
|
+
if ! buffer.nil?
|
257
|
+
if buffer_offset < 0 || buffer_offset >= buffer.bytesize
|
258
|
+
raise ArgumentError, 'buffer_offset is not a valid buffer index'
|
259
|
+
end
|
260
|
+
if buffer.bytesize - buffer_offset < length
|
261
|
+
raise ArgumentError, 'length is greater than available buffer space'
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Reload the internal buffer when empty.
|
266
|
+
if read_buffer_empty?
|
267
|
+
result = refill
|
268
|
+
return result if Symbol === result
|
269
|
+
end
|
270
|
+
|
271
|
+
available = @end_idx - @start_idx
|
272
|
+
length = available if available < length
|
273
|
+
content = @buffer[@start_idx, length]
|
274
|
+
@start_idx += length
|
275
|
+
@unread_offset += [@unread_offset, length].min
|
276
|
+
return content if buffer.nil?
|
277
|
+
|
278
|
+
buffer[buffer_offset, length] = content
|
279
|
+
return length
|
280
|
+
end
|
281
|
+
|
282
|
+
##
|
283
|
+
# Returns `true` if the read buffer is empty and `false` otherwise.
|
284
|
+
#
|
285
|
+
# @return [Boolean]
|
286
|
+
def read_buffer_empty?
|
287
|
+
@mode != :read || @start_idx >= @end_idx
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Refills the read buffer.
|
292
|
+
#
|
293
|
+
# @return [Integer] the number of bytes added to the read buffer
|
294
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
295
|
+
# the operation would block
|
296
|
+
#
|
297
|
+
# @raise [EOFError] when reading at the end of the stream
|
298
|
+
# @raise [IOError] if the stream is not readable
|
299
|
+
def refill
|
300
|
+
assert_readable
|
301
|
+
|
302
|
+
result = set_read_mode
|
303
|
+
return result if Symbol === result
|
304
|
+
|
305
|
+
remaining = @end_idx - @start_idx
|
306
|
+
available = buffer_size - remaining
|
307
|
+
if available == 0
|
308
|
+
# The read buffer is already full.
|
309
|
+
return 0
|
310
|
+
elsif available >= buffer_size
|
311
|
+
# The read buffer is empty, so prepare to fill it at the beginning.
|
312
|
+
@start_idx = @end_idx = @unread_offset = 0
|
313
|
+
elsif @start_idx > 0
|
314
|
+
# Shift the remaining buffer content to the beginning of the buffer.
|
315
|
+
@buffer[0, remaining] = @buffer[@start_idx, remaining]
|
316
|
+
@start_idx = 0
|
317
|
+
@end_idx = remaining
|
318
|
+
end
|
319
|
+
|
320
|
+
result =
|
321
|
+
delegate.read(available, buffer: @buffer, buffer_offset: @end_idx)
|
322
|
+
|
323
|
+
# Return non-integer results from the delegate.
|
324
|
+
return result if Symbol === result
|
325
|
+
|
326
|
+
@end_idx += result
|
327
|
+
|
328
|
+
result
|
329
|
+
end
|
330
|
+
|
331
|
+
##
|
332
|
+
# Sets the current stream position to `amount` based on the setting of
|
333
|
+
# `whence`.
|
334
|
+
#
|
335
|
+
# | `whence` | `amount` Interpretation |
|
336
|
+
# | -------- | ----------------------- |
|
337
|
+
# | `:CUR` or `IO::SEEK_CUR` | `amount` added to current stream position |
|
338
|
+
# | `:END` or `IO::SEEK_END` | `amount` added to end of stream position (`amount` will usually be negative here) |
|
339
|
+
# | `:SET` or `IO::SEEK_SET` | `amount` used as absolute position |
|
340
|
+
#
|
341
|
+
# @param amount [Integer] the amount to move the position in bytes
|
342
|
+
# @param whence [Integer, Symbol] the position alias from which to consider
|
343
|
+
# `amount`
|
344
|
+
#
|
345
|
+
# @return [Integer] the new stream position
|
346
|
+
#
|
347
|
+
# @raise [IOError] if the stream is closed
|
348
|
+
# @raise [Errno::ESPIPE] if the stream is not seekable
|
349
|
+
def seek(amount, whence = IO::SEEK_SET)
|
350
|
+
case @mode
|
351
|
+
when :write
|
352
|
+
result = flush
|
353
|
+
return result if Symbol === result
|
354
|
+
when :read
|
355
|
+
case whence
|
356
|
+
when IO::SEEK_CUR, :CUR
|
357
|
+
amount -= @end_idx - @start_idx - @unread_offset
|
358
|
+
end
|
359
|
+
end
|
360
|
+
@mode = nil
|
361
|
+
|
362
|
+
result = super(amount, whence)
|
363
|
+
# Clear the buffer only if the seek was successful.
|
364
|
+
@start_idx = @end_idx = @unread_offset = 0
|
365
|
+
result
|
366
|
+
end
|
367
|
+
|
368
|
+
##
|
369
|
+
# Advances forward in the read buffer up to `length` bytes.
|
370
|
+
#
|
371
|
+
# @param length [Integer, nil] the number of bytes to skip or `nil` for all
|
372
|
+
# bytes
|
373
|
+
#
|
374
|
+
# @return [Integer] the number of bytes actually skipped
|
375
|
+
#
|
376
|
+
# @raise [IOError] if the stream is not readable
|
377
|
+
def skip(length = nil)
|
378
|
+
if length.nil?
|
379
|
+
length = nread
|
380
|
+
else
|
381
|
+
length = Integer(length)
|
382
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
383
|
+
end
|
384
|
+
|
385
|
+
assert_readable
|
386
|
+
|
387
|
+
return 0 unless @mode == :read
|
388
|
+
|
389
|
+
remaining = @end_idx - @start_idx
|
390
|
+
length = remaining if length > remaining
|
391
|
+
@start_idx += length
|
392
|
+
@unread_offset += [@unread_offset, length].min
|
393
|
+
|
394
|
+
length
|
395
|
+
end
|
396
|
+
|
397
|
+
##
|
398
|
+
# Places bytes at the beginning of the read buffer.
|
399
|
+
#
|
400
|
+
# @param buffer [String] the bytes to insert into the read buffer
|
401
|
+
# @param length [Integer] the number of bytes from the beginning of `buffer`
|
402
|
+
# to insert into the read buffer
|
403
|
+
#
|
404
|
+
# @return [nil]
|
405
|
+
#
|
406
|
+
# @raise [IOError] if the remaining space in the internal buffer is
|
407
|
+
# insufficient to contain the given data
|
408
|
+
# @raise [IOError] if the stream is not readable
|
409
|
+
def unread(buffer, length: buffer.bytesize)
|
410
|
+
length = Integer(length)
|
411
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
412
|
+
|
413
|
+
assert_readable
|
414
|
+
|
415
|
+
result = set_read_mode
|
416
|
+
return result if Symbol === result
|
417
|
+
|
418
|
+
used = @end_idx - @start_idx
|
419
|
+
if length > @buffer_size - used
|
420
|
+
raise IOError, 'insufficient buffer space for unread'
|
421
|
+
end
|
422
|
+
|
423
|
+
if length > @start_idx
|
424
|
+
# Shift the available buffer content to the end of the buffer
|
425
|
+
new_start_idx = @buffer_size - used
|
426
|
+
@buffer[new_start_idx, used] = @buffer[@start_idx, used]
|
427
|
+
@start_idx = new_start_idx
|
428
|
+
@end_idx = @buffer_size
|
429
|
+
end
|
430
|
+
|
431
|
+
@start_idx -= length
|
432
|
+
@unread_offset += length
|
433
|
+
@buffer[@start_idx, length] = buffer[0, length]
|
434
|
+
|
435
|
+
nil
|
436
|
+
end
|
437
|
+
|
438
|
+
##
|
439
|
+
# Waits until the stream becomes ready for at least 1 of the specified events.
|
440
|
+
#
|
441
|
+
# @param events [Integer] a bit mask of `IO::READABLE`, `IO::WRITABLE`, or
|
442
|
+
# `IO::PRIORITY`
|
443
|
+
# @param timeout [Numeric, nil] the timeout in seconds or no timeout if `nil`
|
444
|
+
#
|
445
|
+
# @return [true] if the stream becomes ready for at least one of the given
|
446
|
+
# events
|
447
|
+
# @return [false] if the IO does not become ready before the timeout
|
448
|
+
def wait(events, timeout = nil)
|
449
|
+
assert_open
|
450
|
+
|
451
|
+
if events & (IO::READABLE | IO::PRIORITY) > 0 && ! read_buffer_empty?
|
452
|
+
return true
|
453
|
+
end
|
454
|
+
|
455
|
+
super
|
456
|
+
end
|
457
|
+
|
458
|
+
##
|
459
|
+
# Writes bytes to the stream.
|
460
|
+
#
|
461
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
462
|
+
# and writing more bytes would block.
|
463
|
+
#
|
464
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
465
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
466
|
+
#
|
467
|
+
# @return [Integer] the number of bytes written
|
468
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
469
|
+
# the operation would block
|
470
|
+
#
|
471
|
+
# @raise [IOError] if the stream is not writable
|
472
|
+
def write(buffer, length: buffer.bytesize)
|
473
|
+
length = Integer(length)
|
474
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
475
|
+
|
476
|
+
assert_writable
|
477
|
+
|
478
|
+
set_write_mode
|
479
|
+
|
480
|
+
available = @buffer_size - @end_idx
|
481
|
+
if available <= 0
|
482
|
+
result = flush
|
483
|
+
return result if Symbol === result
|
484
|
+
|
485
|
+
@start_idx = @end_idx = @unread_offset = 0
|
486
|
+
available = @buffer_size
|
487
|
+
end
|
488
|
+
|
489
|
+
length = available if available < length
|
490
|
+
@buffer[@end_idx, length] = buffer.b[0, length]
|
491
|
+
@end_idx += length
|
492
|
+
length
|
493
|
+
end
|
494
|
+
|
495
|
+
##
|
496
|
+
# Returns `true` if the write buffer it empty and `false` otherwise.
|
497
|
+
#
|
498
|
+
# @return [Boolean]
|
499
|
+
def write_buffer_empty?
|
500
|
+
@mode != :write || @start_idx >= @end_idx
|
501
|
+
end
|
502
|
+
|
503
|
+
private
|
504
|
+
|
505
|
+
##
|
506
|
+
# Creates an instance of this class that copies state from `other`.
|
507
|
+
#
|
508
|
+
# @param other [BufferedIO] the instance to copy
|
509
|
+
#
|
510
|
+
# @return [nil]
|
511
|
+
#
|
512
|
+
# @raise [IOError] if `other` is closed
|
513
|
+
def initialize_copy(other)
|
514
|
+
super
|
515
|
+
|
516
|
+
@buffer = @buffer.dup
|
517
|
+
|
518
|
+
nil
|
519
|
+
end
|
520
|
+
|
521
|
+
##
|
522
|
+
# Switches the stream into read mode if it was previously in write mode.
|
523
|
+
#
|
524
|
+
# This triggers a flush operation if needed.
|
525
|
+
#
|
526
|
+
# @return [nil] if read mode is enabled
|
527
|
+
# @return [:wait_readable, :wait_writable] if a flush is needed and would
|
528
|
+
# block
|
529
|
+
def set_read_mode
|
530
|
+
if @mode == :write
|
531
|
+
result = flush
|
532
|
+
return result if Symbol === result
|
533
|
+
end
|
534
|
+
@mode = :read
|
535
|
+
nil
|
536
|
+
end
|
537
|
+
|
538
|
+
##
|
539
|
+
# Switches the stream into write mode if it was previously in read mode.
|
540
|
+
#
|
541
|
+
# @return [nil]
|
542
|
+
def set_write_mode
|
543
|
+
if @mode == :read
|
544
|
+
# Rewind delegate to buffered read position if possible.
|
545
|
+
seek(0, IO::SEEK_CUR) rescue nil
|
546
|
+
# Ensure the read buffer is cleared even if the stream is not seekable.
|
547
|
+
@start_idx = @end_idx = @unread_offset = 0
|
548
|
+
end
|
549
|
+
@mode = :write
|
550
|
+
nil
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end; end
|
554
|
+
|
555
|
+
# vim: ts=2 sw=2 et
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IO; module LikeHelpers; class CharacterIO
|
4
|
+
|
5
|
+
##
|
6
|
+
# This class exists mostly to provide an interface that is compatible with that
|
7
|
+
# of ConverterReader. It is otherwise a thin wrapper around the BufferedIO
|
8
|
+
# instance provided to it as a data source.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class BasicReader
|
12
|
+
##
|
13
|
+
# Creates a new intance of this class.
|
14
|
+
#
|
15
|
+
# @param buffered_io [LikeHelpers::BufferedIO] a readable stream that always
|
16
|
+
# blocks
|
17
|
+
# @param encoding [Encoding, nil] the encoding to apply to the content provided by
|
18
|
+
# this stream
|
19
|
+
#
|
20
|
+
# When `encoding` is `nil`, #encoding will return the current value of
|
21
|
+
# Encoding.default_external when called.
|
22
|
+
def initialize(
|
23
|
+
buffered_io,
|
24
|
+
encoding: nil
|
25
|
+
)
|
26
|
+
@buffered_io = buffered_io
|
27
|
+
@encoding = encoding ? Encoding.find(encoding) : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Clears the state of this reader.
|
32
|
+
#
|
33
|
+
# @return [nil]
|
34
|
+
def clear
|
35
|
+
buffered_io.flush
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Returns the bytes of the buffer as a binary encoded String.
|
41
|
+
#
|
42
|
+
# The returned bytes should be encoded using the value of {#encoding} and
|
43
|
+
# `String#force_encoding`. Bytes are returned rather than characters because
|
44
|
+
# CharacterIO#read_line works on bytes for compatibility with the MRI
|
45
|
+
# implementation and working with characters would be inefficient in that
|
46
|
+
# case.
|
47
|
+
#
|
48
|
+
# @return [String] the bytes of the buffer
|
49
|
+
#
|
50
|
+
# @raise [IOError] if the stream is not readable
|
51
|
+
def content
|
52
|
+
buffered_io.peek
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Consumes bytes from the front of the buffer.
|
57
|
+
#
|
58
|
+
# @param length [Integer, nil] the number of bytes to consume
|
59
|
+
#
|
60
|
+
# @return [nil]
|
61
|
+
#
|
62
|
+
# @raise [IOError] if the stream is not readable
|
63
|
+
def consume(length)
|
64
|
+
buffered_io.skip(length)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Returns `true` if the read buffer is empty and `false` otherwise.
|
70
|
+
#
|
71
|
+
# This implementation does not have its own a buffer, so this method always
|
72
|
+
# returns `true`.
|
73
|
+
#
|
74
|
+
# @return [Boolean]
|
75
|
+
def empty?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @return [Encoding] the encoding to apply on byte strings from #content
|
81
|
+
def encoding
|
82
|
+
@encoding || Encoding.default_external
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Refills the buffer from the stream.
|
87
|
+
#
|
88
|
+
# @param many [Boolean] ignored in this implementation; see
|
89
|
+
# ConverterReader#refill
|
90
|
+
#
|
91
|
+
# @return [nil]
|
92
|
+
#
|
93
|
+
# @raise [EOFError] when reading at the end of the stream
|
94
|
+
# @raise [IOError] if the stream is not readable
|
95
|
+
# @raise [IOError] if the buffer is already full
|
96
|
+
def refill(many = true)
|
97
|
+
bytes_added = buffered_io.refill
|
98
|
+
raise IOError, 'no bytes read' if bytes_added < 1
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Places bytes at the beginning of the read buffer.
|
104
|
+
#
|
105
|
+
# @param buffer [String] the bytes to insert into the read buffer
|
106
|
+
# @param length [Integer] the number of bytes from the beginning of `buffer`
|
107
|
+
# to insert into the read buffer
|
108
|
+
#
|
109
|
+
# @return [nil]
|
110
|
+
#
|
111
|
+
# @raise [IOError] if the remaining space in the internal buffer is
|
112
|
+
# insufficient to contain the given data
|
113
|
+
# @raise [IOError] if the stream is not readable
|
114
|
+
def unread(buffer, length: buffer.bytesize)
|
115
|
+
return buffered_io.unread(buffer, length: length)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
attr_reader :buffered_io
|
121
|
+
end
|
122
|
+
end; end; end
|