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,512 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IO; module LikeHelpers
|
4
|
+
|
5
|
+
##
|
6
|
+
# @abstract It defines the structure expected of low level streams and largely
|
7
|
+
# reflects the structure of the IO class, but most methods raise
|
8
|
+
# NotImplementedError. Only the most basic semantics for stream opening and
|
9
|
+
# closing are provided.
|
10
|
+
class AbstractIO
|
11
|
+
##
|
12
|
+
# @overload open(*args, **kwargs)
|
13
|
+
# Equivalent to {#initialize}.
|
14
|
+
#
|
15
|
+
# @return a new instances of this class
|
16
|
+
#
|
17
|
+
# @overload open(*args, **kwargs)
|
18
|
+
# Yields the new instance of this class to the block, ensures the instance
|
19
|
+
# is closed once the block completes, and returns the result of the block.
|
20
|
+
#
|
21
|
+
# @yieldparam stream an instance of this class
|
22
|
+
#
|
23
|
+
# @return [block result]
|
24
|
+
#
|
25
|
+
# @param args a list of arguments passed to the initializer of this class
|
26
|
+
# @param kwargs a list of keyword arguments passed to the initializer of this
|
27
|
+
# class
|
28
|
+
def self.open(*args, **kwargs)
|
29
|
+
io = new(*args, **kwargs)
|
30
|
+
return io unless block_given?
|
31
|
+
|
32
|
+
begin
|
33
|
+
yield(io)
|
34
|
+
ensure
|
35
|
+
while Symbol === io.close do
|
36
|
+
warn 'warning: waiting for nonblocking close to complete at the end of the open method'
|
37
|
+
# A wait timeout is used in order to allow a retry in case the stream
|
38
|
+
# was closed in another thread while waiting.
|
39
|
+
io.wait(IO::READABLE | IO::WRITABLE, 1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Creates a new instance of this class.
|
46
|
+
#
|
47
|
+
# @param kwargs [Hash] only provided for compatibility with .open on Ruby 2.6
|
48
|
+
#
|
49
|
+
# @todo Remove explicit `kwargs` parameter when Ruby 2.6 support is dropped.
|
50
|
+
def initialize(**kwargs)
|
51
|
+
@closed = false
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Announces an intention to access data from the stream in a specific pattern.
|
56
|
+
#
|
57
|
+
# This method is a no-op if not implemented. If `offset` and `len` are both
|
58
|
+
# `0`, then the entire stream is affected.
|
59
|
+
#
|
60
|
+
# | `advice` | Meaning |
|
61
|
+
# | -------- | ------- |
|
62
|
+
# | `:normal` | No advice given; default assumption for the stream. |
|
63
|
+
# | `:sequential` | The data will be read sequentially from lower offsets to higher ones. |
|
64
|
+
# | `:random` | The data will be accessed in random order. |
|
65
|
+
# | `:willneed` | The data will be accessed in the near future. |
|
66
|
+
# | `:dontneed` | The data will not be accessed in the near future. |
|
67
|
+
# | `:noreuse` | The data will only be accessed once. |
|
68
|
+
#
|
69
|
+
# See `posix_fadvise(2)` for more details.
|
70
|
+
#
|
71
|
+
# @param advice [Symbol] the access pattern
|
72
|
+
# @param offset [Integer] the starting location of the data that will be
|
73
|
+
# accessed
|
74
|
+
# @param len [Integer] the length of the data that will be accessed
|
75
|
+
#
|
76
|
+
# @return [nil]
|
77
|
+
#
|
78
|
+
# @raise [IOError] if the stream is closed
|
79
|
+
def advise(advice, offset = 0, len = 0)
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Closes the stream.
|
85
|
+
#
|
86
|
+
# Most operations on the stream after this method is called will result in
|
87
|
+
# IOErrors being raised.
|
88
|
+
#
|
89
|
+
# @return [nil] on success
|
90
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
91
|
+
# the operation would block
|
92
|
+
def close
|
93
|
+
@closed = true
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Returns `true` if this stream is closed and `false` otherwise.
|
99
|
+
#
|
100
|
+
# @return [Boolean]
|
101
|
+
def closed?
|
102
|
+
@closed
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Sets the close-on-exec flag for the underlying file descriptor.
|
107
|
+
#
|
108
|
+
# Note that setting this to `false` can lead to file descriptor leaks in
|
109
|
+
# multithreaded applications that fork and exec or use the `system` method.
|
110
|
+
#
|
111
|
+
# @return [Boolean]
|
112
|
+
def close_on_exec=(close_on_exec)
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Returns `true` if the close-on-exec flag is set for this stream and `false`
|
118
|
+
# otherwise.
|
119
|
+
#
|
120
|
+
# @return [Boolean]
|
121
|
+
def close_on_exec?
|
122
|
+
raise NotImplementedError
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Issues low level commands to control or query the file-oriented stream upon
|
127
|
+
# which this stream is based.
|
128
|
+
#
|
129
|
+
# @param integer_cmd [Integer] passed directly to `fcntl(2)` as an operation
|
130
|
+
# identifier
|
131
|
+
# @param arg [Integer, String] passed directly to `fcntl(2)` if an Integer and
|
132
|
+
# as a binary sequence of bytes otherwise
|
133
|
+
#
|
134
|
+
# @return [Integer] the return value of `fcntl(2)` when not an error
|
135
|
+
#
|
136
|
+
# @raise [IOError] if the stream is closed
|
137
|
+
# @raise [NotImplementedError] on platforms without the `fcntl(2)` function
|
138
|
+
# @raise [SystemCallError] on system level errors
|
139
|
+
def fcntl(integer_cmd, arg)
|
140
|
+
raise NotImplementedError
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Triggers the operating system to write any buffered metadata to disk
|
145
|
+
# immediately.
|
146
|
+
#
|
147
|
+
# The default implementation calls {#fsync}, but override this if the stream
|
148
|
+
# natively supports the equivalent of `fdatasync(2)` to offer better
|
149
|
+
# performance when only metadata needs to be flushed to disk.
|
150
|
+
#
|
151
|
+
# @return [0, nil]
|
152
|
+
def fdatasync
|
153
|
+
fsync
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# @return [Integer] the numeric file descriptor for the stream
|
158
|
+
def fileno
|
159
|
+
raise NotImplementedError
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Triggers the operating system to write any buffered data to disk
|
164
|
+
# immediately.
|
165
|
+
#
|
166
|
+
# @return [0, nil]
|
167
|
+
def fsync
|
168
|
+
raise NotImplementedError
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Issues low level commands to control or query the device upon which this
|
173
|
+
# stream is based.
|
174
|
+
#
|
175
|
+
# @param integer_cmd [Integer] passed directly to `ioctl(2)` as a device
|
176
|
+
# dependent request code
|
177
|
+
# @param arg [Integer, String] passed directly to `ioctl(2)` if an Integer and
|
178
|
+
# as a binary sequence of bytes otherwise
|
179
|
+
#
|
180
|
+
# @return [Integer] the return value of `ioctl(2)` when not an error
|
181
|
+
#
|
182
|
+
# @raise [IOError] if the stream is closed
|
183
|
+
# @raise [NotImplementedError] on platforms without the `ioctl(2)` function
|
184
|
+
# @raise [SystemCallError] on system level errors
|
185
|
+
def ioctl(integer_cmd, arg)
|
186
|
+
raise NotImplementedError
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Yields `self` to the given block after setting the blocking mode as dictated
|
191
|
+
# by `nonblock`.
|
192
|
+
#
|
193
|
+
# Ensures that the original blocking mode is reinstated after yielding.
|
194
|
+
#
|
195
|
+
# @param nonblock [Boolean] sets the stream to non-blocking mode if `true` and
|
196
|
+
# blocking mode otherwise
|
197
|
+
#
|
198
|
+
# @yieldparam self [Like] this stream
|
199
|
+
#
|
200
|
+
# @return [self]
|
201
|
+
#
|
202
|
+
# @raise [IOError] if the stream is closed
|
203
|
+
def nonblock(nonblock = true)
|
204
|
+
assert_open
|
205
|
+
begin
|
206
|
+
orig_nonblock = nonblock?
|
207
|
+
self.nonblock = nonblock
|
208
|
+
yield(self)
|
209
|
+
ensure
|
210
|
+
self.nonblock = orig_nonblock
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Sets the stream into either blocking or non-blocking mode.
|
216
|
+
#
|
217
|
+
# @param nonblock [Boolean] `true` for non-blocking mode, `false` otherwise
|
218
|
+
#
|
219
|
+
# @raise [IOError] if the stream is closed
|
220
|
+
def nonblock=(nonblock)
|
221
|
+
raise NotImplementedError
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Returns whether or not the stream is in non-blocking mode.
|
226
|
+
#
|
227
|
+
# @return [true] if the stream is in non-blocking mode
|
228
|
+
# @return [false] if the stream is in blocking mode
|
229
|
+
#
|
230
|
+
# @raise [IOError] if the stream is closed
|
231
|
+
def nonblock?
|
232
|
+
raise NotImplementedError
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# @return [Integer] the number of bytes that can be read without blocking or
|
237
|
+
# `0` if unknown
|
238
|
+
#
|
239
|
+
# @raise [IOError] if the stream is not open for reading
|
240
|
+
def nread
|
241
|
+
assert_readable
|
242
|
+
0
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Returns the path of the file associated with this stream.
|
247
|
+
#
|
248
|
+
# @return [String]
|
249
|
+
def path
|
250
|
+
raise NotImplementedError
|
251
|
+
end
|
252
|
+
|
253
|
+
##
|
254
|
+
# Returns the pid of the process associated with this stream.
|
255
|
+
#
|
256
|
+
# @return [Integer] if the stream is associated with a process
|
257
|
+
# @return [nil] if the stream is not associated with a process
|
258
|
+
#
|
259
|
+
# @raise [IOError] if the stream is closed
|
260
|
+
def pid
|
261
|
+
assert_open
|
262
|
+
nil
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Reads at most `length` bytes from the stream starting at `offset` without
|
267
|
+
# modifying the read position in the stream.
|
268
|
+
#
|
269
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
270
|
+
# and reading more bytes would block.
|
271
|
+
#
|
272
|
+
# @param length [Integer] the maximum number of bytes to read
|
273
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
274
|
+
# to begin reading
|
275
|
+
# @param buffer [String] if provided, a buffer into which the bytes should be
|
276
|
+
# placed
|
277
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
278
|
+
# `buffer`
|
279
|
+
#
|
280
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
281
|
+
# @return [String] a new String containing the bytes read if `buffer` is `nil`
|
282
|
+
# or `buffer` if provided
|
283
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
284
|
+
# the operation would block
|
285
|
+
#
|
286
|
+
# @raise [EOFError] when reading at the end of the stream
|
287
|
+
# @raise [IOError] if the stream is not readable
|
288
|
+
def pread(length, offset, buffer: nil, buffer_offset: 0)
|
289
|
+
position = seek(0, IO::SEEK_CUR)
|
290
|
+
|
291
|
+
seek(offset, IO::SEEK_SET)
|
292
|
+
begin
|
293
|
+
read(length, buffer: buffer, buffer_offset: buffer_offset)
|
294
|
+
ensure
|
295
|
+
seek(position, IO::SEEK_SET)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Writes at most `length` bytes to the stream starting at `offset` without
|
301
|
+
# modifying the write position in the stream.
|
302
|
+
#
|
303
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
304
|
+
# and writing more bytes would block.
|
305
|
+
#
|
306
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
307
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
308
|
+
# to begin writing
|
309
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
310
|
+
#
|
311
|
+
# @return [Integer] the number of bytes written
|
312
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
313
|
+
# the operation would block
|
314
|
+
#
|
315
|
+
# @raise [IOError] if the stream is not writable
|
316
|
+
def pwrite(buffer, offset, length: buffer.bytesize)
|
317
|
+
position = seek(0, IO::SEEK_CUR)
|
318
|
+
|
319
|
+
seek(offset, IO::SEEK_SET)
|
320
|
+
begin
|
321
|
+
write(buffer, length: length)
|
322
|
+
ensure
|
323
|
+
seek(position, IO::SEEK_SET)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
##
|
328
|
+
# Reads bytes from the stream.
|
329
|
+
#
|
330
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
331
|
+
# and reading more bytes would block.
|
332
|
+
#
|
333
|
+
# @param length [Integer] the number of bytes to read
|
334
|
+
# @param buffer [String] the buffer into which bytes will be read (encoding
|
335
|
+
# assumed to be binary)
|
336
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
337
|
+
# `buffer`
|
338
|
+
#
|
339
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
340
|
+
# @return [String] a buffer containing the bytes read if `buffer` is `nil`
|
341
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
342
|
+
# the operation would block
|
343
|
+
#
|
344
|
+
# @raise [EOFError] when reading at the end of the stream
|
345
|
+
# @raise [IOError] if the stream is not readable
|
346
|
+
def read(length, buffer: nil, buffer_offset: 0)
|
347
|
+
assert_readable
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Returns `true` if the stream is readable and `false` otherwise.
|
352
|
+
#
|
353
|
+
# @return [Boolean]
|
354
|
+
def readable?
|
355
|
+
false
|
356
|
+
end
|
357
|
+
|
358
|
+
##
|
359
|
+
# Returns whether or not the stream has input available.
|
360
|
+
#
|
361
|
+
# @return [true] if input is available
|
362
|
+
# @return [false] if input is not available
|
363
|
+
def ready?
|
364
|
+
assert_open
|
365
|
+
false
|
366
|
+
end
|
367
|
+
|
368
|
+
##
|
369
|
+
# Sets the current stream position to `amount` based on the setting of
|
370
|
+
# `whence`.
|
371
|
+
#
|
372
|
+
# | `whence` | `amount` Interpretation |
|
373
|
+
# | -------- | ----------------------- |
|
374
|
+
# | `:CUR` or `IO::SEEK_CUR` | `amount` added to current stream position |
|
375
|
+
# | `:END` or `IO::SEEK_END` | `amount` added to end of stream position (`amount` will usually be negative here) |
|
376
|
+
# | `:SET` or `IO::SEEK_SET` | `amount` used as absolute position |
|
377
|
+
#
|
378
|
+
# @param amount [Integer] the amount to move the position in bytes
|
379
|
+
# @param whence [Integer, Symbol] the position alias from which to consider
|
380
|
+
# `amount`
|
381
|
+
#
|
382
|
+
# @return [Integer] the new stream position
|
383
|
+
#
|
384
|
+
# @raise [IOError] if the stream is closed
|
385
|
+
# @raise [Errno::ESPIPE] if the stream is not seekable
|
386
|
+
def seek(amount, whence)
|
387
|
+
assert_open
|
388
|
+
raise Errno::ESPIPE
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# Returns status information for the stream.
|
393
|
+
#
|
394
|
+
# @return [File::Stat]
|
395
|
+
def stat
|
396
|
+
raise NotImplementedError
|
397
|
+
end
|
398
|
+
|
399
|
+
##
|
400
|
+
# Returns the native IO object upon which this stream is based.
|
401
|
+
#
|
402
|
+
# @return [IO]
|
403
|
+
def to_io
|
404
|
+
raise NotImplementedError
|
405
|
+
end
|
406
|
+
|
407
|
+
##
|
408
|
+
# Returns whether or not the stream is a tty.
|
409
|
+
#
|
410
|
+
# @return [true] if the stream is a tty
|
411
|
+
# @return [false] if the stream is not a tty
|
412
|
+
#
|
413
|
+
# @raise [IOError] if the stream is closed
|
414
|
+
def tty?
|
415
|
+
assert_open
|
416
|
+
false
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
# Waits until the stream becomes ready for at least 1 of the specified events.
|
421
|
+
#
|
422
|
+
# @param events [Integer] a bit mask of `IO::READABLE`, `IO::WRITABLE`, or
|
423
|
+
# `IO::PRIORITY`
|
424
|
+
# @param timeout [Numeric, nil] the timeout in seconds or no timeout if `nil`
|
425
|
+
#
|
426
|
+
# @return [true] if the stream becomes ready for at least one of the given
|
427
|
+
# events
|
428
|
+
# @return [false] if the IO does not become ready before the timeout
|
429
|
+
def wait(events, timeout = nil)
|
430
|
+
raise NotImplementedError
|
431
|
+
end
|
432
|
+
|
433
|
+
##
|
434
|
+
# Writes bytes to the stream.
|
435
|
+
#
|
436
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
437
|
+
# and writing more bytes would block.
|
438
|
+
#
|
439
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
440
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
441
|
+
#
|
442
|
+
# @return [Integer] the number of bytes written
|
443
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
444
|
+
# the operation would block
|
445
|
+
#
|
446
|
+
# @raise [IOError] if the stream is not writable
|
447
|
+
def write(buffer, length: buffer.bytesize)
|
448
|
+
assert_writable
|
449
|
+
end
|
450
|
+
|
451
|
+
##
|
452
|
+
# Returns `true` if the stream is writable and `false` otherwise.
|
453
|
+
#
|
454
|
+
# @return [Boolean]
|
455
|
+
def writable?
|
456
|
+
false
|
457
|
+
end
|
458
|
+
|
459
|
+
private
|
460
|
+
|
461
|
+
##
|
462
|
+
# Raises an exception if the stream is closed.
|
463
|
+
#
|
464
|
+
# @return [nil]
|
465
|
+
#
|
466
|
+
# @raise IOError if the stream is closed
|
467
|
+
def assert_open
|
468
|
+
raise IOError, 'uninitialized stream' unless defined? @closed
|
469
|
+
raise IOError, 'closed stream' if closed?
|
470
|
+
end
|
471
|
+
|
472
|
+
##
|
473
|
+
# Raises an exception if the stream is closed or not open for reading.
|
474
|
+
#
|
475
|
+
# @return [nil]
|
476
|
+
#
|
477
|
+
# @raise IOError if the stream is closed or not open for reading
|
478
|
+
def assert_readable
|
479
|
+
assert_open
|
480
|
+
raise IOError, 'not opened for reading' unless readable?
|
481
|
+
end
|
482
|
+
|
483
|
+
##
|
484
|
+
# Raises an exception if the stream is closed or not open for writing.
|
485
|
+
#
|
486
|
+
# @return [nil]
|
487
|
+
#
|
488
|
+
# @raise IOError if the stream is closed or not open for writing
|
489
|
+
def assert_writable
|
490
|
+
assert_open
|
491
|
+
raise IOError, 'not opened for writing' unless writable?
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# Creates an instance of this class that copies state from `other`.
|
496
|
+
#
|
497
|
+
# @param other [AbstractIO] the instance to copy
|
498
|
+
#
|
499
|
+
# @return [nil]
|
500
|
+
#
|
501
|
+
# @raise [IOError] if `other` is closed
|
502
|
+
def initialize_copy(other)
|
503
|
+
assert_open
|
504
|
+
|
505
|
+
super
|
506
|
+
|
507
|
+
nil
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end; end
|
511
|
+
|
512
|
+
# vim: ts=2 sw=2 et
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/like_helpers/delegated_io'
|
4
|
+
|
5
|
+
class IO; module LikeHelpers
|
6
|
+
|
7
|
+
##
|
8
|
+
# This class implements a stream that always blocks regardless of the blocking
|
9
|
+
# state of the delegate.
|
10
|
+
class BlockingIO < DelegatedIO
|
11
|
+
##
|
12
|
+
# Reads bytes from the stream.
|
13
|
+
#
|
14
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
15
|
+
# and reading more bytes would block. If no bytes can be read, however, the
|
16
|
+
# read will block until at least 1 byte can be read.
|
17
|
+
#
|
18
|
+
# @param length [Integer] the number of bytes to read
|
19
|
+
# @param buffer [String] the buffer into which bytes will be read (encoding
|
20
|
+
# assumed to be binary)
|
21
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
22
|
+
# `buffer`
|
23
|
+
#
|
24
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
25
|
+
# @return [String] a buffer containing the bytes read if `buffer` is `nil`
|
26
|
+
#
|
27
|
+
# @raise [EOFError] when reading at the end of the stream
|
28
|
+
# @raise [IOError] if the stream is not readable
|
29
|
+
def read(length, buffer: nil, buffer_offset: 0)
|
30
|
+
ensure_blocking { super }
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Writes bytes to the stream.
|
35
|
+
#
|
36
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
37
|
+
# and writing more bytes would block. If no bytes can be written, however,
|
38
|
+
# the write will block until at least 1 byte can be written.
|
39
|
+
#
|
40
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
41
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
42
|
+
#
|
43
|
+
# @return [Integer] the number of bytes written
|
44
|
+
#
|
45
|
+
# @raise [IOError] if the stream is not writable
|
46
|
+
def write(buffer, length: buffer.bytesize)
|
47
|
+
ensure_blocking { super }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
##
|
53
|
+
# Runs the given block in a wait loop that exits only when the block returns a
|
54
|
+
# non-Symbol value.
|
55
|
+
#
|
56
|
+
# This method is intended to wrap an IO operation that may be non-blocking and
|
57
|
+
# effectively turn it into a blocking operation without changing underlying
|
58
|
+
# settings of the stream itself.
|
59
|
+
#
|
60
|
+
# @return the return value of the block if not a Symbol
|
61
|
+
#
|
62
|
+
# @raise [RuntimeError] if the Symbol returned by the block is neither
|
63
|
+
# `:wait_readable` nor `:wait_writable`
|
64
|
+
def ensure_blocking
|
65
|
+
begin
|
66
|
+
while Symbol === (result = yield) do
|
67
|
+
# A wait timeout is used in order to allow a retry in case the stream was
|
68
|
+
# closed in another thread while waiting.
|
69
|
+
case result
|
70
|
+
when :wait_readable
|
71
|
+
wait(IO::READABLE, 1)
|
72
|
+
when :wait_writable
|
73
|
+
wait(IO::WRITABLE, 1)
|
74
|
+
else
|
75
|
+
raise "Unexpected result: #{result}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
rescue Errno::EINTR
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end; end
|