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,290 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/nonblock'
|
4
|
+
require 'io/wait'
|
5
|
+
|
6
|
+
require 'io/like_helpers/delegated_io'
|
7
|
+
require 'io/like_helpers/io'
|
8
|
+
require 'io/like_helpers/ruby_facts'
|
9
|
+
|
10
|
+
class IO; module LikeHelpers
|
11
|
+
|
12
|
+
##
|
13
|
+
# This class adapts Ruby's IO implementation to the more primitive interface and
|
14
|
+
# behaviors expected by this library.
|
15
|
+
class IOWrapper < DelegatedIO
|
16
|
+
include RubyFacts
|
17
|
+
|
18
|
+
##
|
19
|
+
# Reads at most `length` bytes from the stream starting at `offset` without
|
20
|
+
# modifying the read position in the stream.
|
21
|
+
#
|
22
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
23
|
+
# and reading more bytes would block.
|
24
|
+
#
|
25
|
+
# @param length [Integer] the maximum number of bytes to read
|
26
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
27
|
+
# to begin reading
|
28
|
+
# @param buffer [String] if provided, a buffer into which the bytes should be
|
29
|
+
# placed
|
30
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
31
|
+
# `buffer`
|
32
|
+
#
|
33
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
34
|
+
# @return [String] a new String containing the bytes read if `buffer` is `nil`
|
35
|
+
# or `buffer` if provided
|
36
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
37
|
+
# the operation would block
|
38
|
+
#
|
39
|
+
# @raise [EOFError] when reading at the end of the stream
|
40
|
+
# @raise [IOError] if the stream is not readable
|
41
|
+
def pread(length, offset, buffer: nil, buffer_offset: 0)
|
42
|
+
if ! buffer.nil?
|
43
|
+
if buffer_offset < 0 || buffer_offset >= buffer.bytesize
|
44
|
+
raise ArgumentError, 'buffer_offset is not a valid buffer index'
|
45
|
+
end
|
46
|
+
if buffer.bytesize - buffer_offset < length
|
47
|
+
raise ArgumentError, 'length is greater than available buffer space'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_readable
|
52
|
+
|
53
|
+
content = delegate.pread(length, offset)
|
54
|
+
return content if Symbol === content || buffer.nil?
|
55
|
+
|
56
|
+
buffer[buffer_offset, content.bytesize] = content
|
57
|
+
return content.bytesize
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Writes at most `length` bytes to the stream starting at `offset` without
|
62
|
+
# modifying the write position in the stream.
|
63
|
+
#
|
64
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
65
|
+
# and writing more bytes would block.
|
66
|
+
#
|
67
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
68
|
+
# @param offset [Integer] the offset from the beginning of the stream at which
|
69
|
+
# to begin writing
|
70
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
71
|
+
#
|
72
|
+
# @return [Integer] the number of bytes written
|
73
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
74
|
+
# the operation would block
|
75
|
+
#
|
76
|
+
# @raise [IOError] if the stream is not writable
|
77
|
+
def pwrite(buffer, offset, length: buffer.bytesize)
|
78
|
+
assert_writable
|
79
|
+
|
80
|
+
delegate.pwrite(buffer[0, length], offset)
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Reads bytes from the stream.
|
85
|
+
#
|
86
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
87
|
+
# and reading more bytes would block.
|
88
|
+
#
|
89
|
+
# @param length [Integer] the number of bytes to read
|
90
|
+
# @param buffer [String] the buffer into which bytes will be read (encoding
|
91
|
+
# assumed to be binary)
|
92
|
+
# @param buffer_offset [Integer] the index at which to insert bytes into
|
93
|
+
# `buffer`
|
94
|
+
#
|
95
|
+
# @return [Integer] the number of bytes read if `buffer` is not `nil`
|
96
|
+
# @return [String] a buffer containing the bytes read if `buffer` is `nil`
|
97
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
98
|
+
# the operation would block
|
99
|
+
#
|
100
|
+
# @raise [EOFError] when reading at the end of the stream
|
101
|
+
# @raise [IOError] if the stream is not readable
|
102
|
+
def read(length, buffer: nil, buffer_offset: 0)
|
103
|
+
if ! buffer.nil?
|
104
|
+
if buffer_offset < 0 || buffer_offset >= buffer.bytesize
|
105
|
+
raise ArgumentError, 'buffer_offset is not a valid buffer index'
|
106
|
+
end
|
107
|
+
if buffer.bytesize - buffer_offset < length
|
108
|
+
raise ArgumentError, 'length is greater than available buffer space'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
assert_readable
|
113
|
+
content = nonblock? ?
|
114
|
+
read_nonblock(length) :
|
115
|
+
delegate.sysread(length)
|
116
|
+
|
117
|
+
return content if Symbol === content || buffer.nil?
|
118
|
+
|
119
|
+
buffer[buffer_offset, content.bytesize] = content
|
120
|
+
return content.bytesize
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Returns `true` if the stream is readable and `false` otherwise.
|
125
|
+
#
|
126
|
+
# @return [Boolean]
|
127
|
+
def readable?
|
128
|
+
return false if closed?
|
129
|
+
return @readable if defined?(@readable) && ! @readable.nil?
|
130
|
+
|
131
|
+
@readable =
|
132
|
+
begin
|
133
|
+
delegate.read(0)
|
134
|
+
true
|
135
|
+
rescue IOError
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Returns whether or not the stream has input available.
|
142
|
+
#
|
143
|
+
# @return [true] if input is available
|
144
|
+
# @return [false] if input is not available
|
145
|
+
def ready?
|
146
|
+
# This is a hack to work around the fact that IO#ready? returns an object
|
147
|
+
# instance instead of true, contrary to documentation.
|
148
|
+
!!super
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Sets the current, unbuffered stream position to `amount` based on the
|
153
|
+
# setting of `whence`.
|
154
|
+
#
|
155
|
+
# | `whence` | `amount` Interpretation |
|
156
|
+
# | -------- | ----------------------- |
|
157
|
+
# | `:CUR` or `IO::SEEK_CUR` | `amount` added to current stream position |
|
158
|
+
# | `:END` or `IO::SEEK_END` | `amount` added to end of stream position (`amount` will usually be negative here) |
|
159
|
+
# | `:SET` or `IO::SEEK_SET` | `amount` used as absolute position |
|
160
|
+
#
|
161
|
+
# @param amount [Integer] the amount to move the position in bytes
|
162
|
+
# @param whence [Integer, Symbol] the position alias from which to consider
|
163
|
+
# `amount`
|
164
|
+
#
|
165
|
+
# @return [Integer] the new stream position
|
166
|
+
#
|
167
|
+
# @raise [IOError] if the stream is closed
|
168
|
+
# @raise [Errno::ESPIPE] if the stream is not seekable
|
169
|
+
def seek(amount, whence = IO::SEEK_SET)
|
170
|
+
assert_open
|
171
|
+
delegate.sysseek(amount, whence)
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Waits until the stream becomes ready for at least 1 of the specified events.
|
176
|
+
#
|
177
|
+
# @param events [Integer] a bit mask of `IO::READABLE`, `IO::WRITABLE`, or
|
178
|
+
# `IO::PRIORITY`
|
179
|
+
# @param timeout [Numeric, nil] the timeout in seconds or no timeout if `nil`
|
180
|
+
#
|
181
|
+
# @return [true] if the stream becomes ready for at least one of the given
|
182
|
+
# events
|
183
|
+
# @return [false] if the IO does not become ready before the timeout
|
184
|
+
def wait(events, timeout = nil)
|
185
|
+
# The !! is a hack to work around the fact that IO#wait returns an object
|
186
|
+
# instance instead of true, contrary to documentation.
|
187
|
+
return !!super unless RBVER_LT_3_0
|
188
|
+
|
189
|
+
# The rest of this implementation is for backward compatibility with the
|
190
|
+
# IO#wait implamentation on Ruby versions prior to 3.0.
|
191
|
+
#
|
192
|
+
# TODO:
|
193
|
+
# Remove this when Ruby 2.7 and below are no longer supported by this
|
194
|
+
# library.
|
195
|
+
assert_open
|
196
|
+
mode =
|
197
|
+
case events & (IO::READABLE | IO::WRITABLE)
|
198
|
+
when IO::READABLE | IO::WRITABLE
|
199
|
+
:read_write
|
200
|
+
when IO::READABLE
|
201
|
+
:read
|
202
|
+
when IO::WRITABLE
|
203
|
+
:write
|
204
|
+
else
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
# The !! is a hack to work around the fact that IO#wait returns an object
|
208
|
+
# instance instead of true, contrary to documentation.
|
209
|
+
!!delegate.wait(timeout, mode)
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Writes bytes to the stream.
|
214
|
+
#
|
215
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
216
|
+
# and writing more bytes would block.
|
217
|
+
#
|
218
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
219
|
+
# @param length [Integer] the number of bytes to write from `buffer`
|
220
|
+
#
|
221
|
+
# @return [Integer] the number of bytes written
|
222
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
223
|
+
# the operation would block
|
224
|
+
#
|
225
|
+
# @raise [IOError] if the stream is not writable
|
226
|
+
def write(buffer, length: buffer.bytesize)
|
227
|
+
assert_writable
|
228
|
+
return delegate.syswrite(buffer[0, length]) unless nonblock?
|
229
|
+
write_nonblock(buffer[0, length])
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Returns `true` if the stream is writable and `false` otherwise.
|
234
|
+
#
|
235
|
+
# @return [Boolean]
|
236
|
+
def writable?
|
237
|
+
return false if closed?
|
238
|
+
return @writable if defined?(@writable) && ! @writable.nil?
|
239
|
+
|
240
|
+
@writable =
|
241
|
+
begin
|
242
|
+
delegate.write
|
243
|
+
true
|
244
|
+
rescue IOError
|
245
|
+
false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
##
|
252
|
+
# Reads bytes from the stream without blocking.
|
253
|
+
#
|
254
|
+
# Note that a partial read will occur if the stream is in non-blocking mode
|
255
|
+
# and reading more bytes would block.
|
256
|
+
#
|
257
|
+
# @param length [Integer] the number of bytes to read
|
258
|
+
#
|
259
|
+
# @return [String] a buffer containing the bytes read
|
260
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
261
|
+
# the operation would block
|
262
|
+
#
|
263
|
+
# @raise [EOFError] when reading at the end of the stream
|
264
|
+
# @raise [IOError] if the stream is not readable
|
265
|
+
def read_nonblock(length)
|
266
|
+
result = delegate.read_nonblock(length, exception: false)
|
267
|
+
raise EOFError if result.nil?
|
268
|
+
result
|
269
|
+
end
|
270
|
+
|
271
|
+
##
|
272
|
+
# Writes bytes to the stream without blocking.
|
273
|
+
#
|
274
|
+
# Note that a partial write will occur if the stream is in non-blocking mode
|
275
|
+
# and writing more bytes would block.
|
276
|
+
#
|
277
|
+
# @param buffer [String] the bytes to write (encoding assumed to be binary)
|
278
|
+
#
|
279
|
+
# @return [Integer] the number of bytes written
|
280
|
+
# @return [:wait_readable, :wait_writable] if the stream is non-blocking and
|
281
|
+
# the operation would block
|
282
|
+
#
|
283
|
+
# @raise [IOError] if the stream is not writable
|
284
|
+
def write_nonblock(buffer)
|
285
|
+
delegate.write_nonblock(buffer, exception: false)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end; end
|
289
|
+
|
290
|
+
# vim: ts=2 sw=2 et
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/like_helpers/blocking_io'
|
4
|
+
require 'io/like_helpers/buffered_io'
|
5
|
+
require 'io/like_helpers/character_io'
|
6
|
+
require 'io/like_helpers/delegated_io'
|
7
|
+
|
8
|
+
class IO; module LikeHelpers
|
9
|
+
|
10
|
+
##
|
11
|
+
# This class creates a pipeline of streams necessary to satisfy the internal
|
12
|
+
# needs of IO::Like:
|
13
|
+
# CharacterIO >> BufferedIO >> BlockingIO >> delegate
|
14
|
+
#
|
15
|
+
# Each segment of the pipeline is directly accessible so that the methods of
|
16
|
+
# IO::Like may use them as necessary.
|
17
|
+
class Pipeline < DelegatedIO
|
18
|
+
##
|
19
|
+
# Creates a new intance of this class.
|
20
|
+
#
|
21
|
+
# @param delegate [LikeHelpers::AbstractIO] a readable and/or writable stream
|
22
|
+
# @param autoclose [Boolean] when `true` close the delegate when this stream
|
23
|
+
# is closed
|
24
|
+
def initialize(
|
25
|
+
delegate,
|
26
|
+
autoclose: true
|
27
|
+
)
|
28
|
+
raise ArgumentError, 'delegate cannot be nil' if delegate.nil?
|
29
|
+
|
30
|
+
super(BufferedIO.new(BlockingIO.new(delegate, autoclose: autoclose)))
|
31
|
+
|
32
|
+
@character_io = CharacterIO.new(buffered_io, blocking_io)
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# A better name for the delegate of this stream that correctly identifies it
|
37
|
+
# as a BufferedIO instance.
|
38
|
+
alias_method :buffered_io, :delegate
|
39
|
+
public :buffered_io
|
40
|
+
|
41
|
+
##
|
42
|
+
# @return a reference to the BlockingIO delegate of the BufferedIO
|
43
|
+
def blocking_io
|
44
|
+
buffered_io.delegate
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# A reference to the CharacterIO instance
|
49
|
+
attr_reader :character_io
|
50
|
+
|
51
|
+
##
|
52
|
+
# @return a reference to the original delegate given to the initializer of
|
53
|
+
# this stream
|
54
|
+
def concrete_io
|
55
|
+
blocking_io.delegate
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
##
|
61
|
+
# Creates an instance of this class that copies state from `other`.
|
62
|
+
#
|
63
|
+
# @param other [Pipeline] the instance to copy
|
64
|
+
#
|
65
|
+
# @return [nil]
|
66
|
+
#
|
67
|
+
# @raise [IOError] if `other` is closed
|
68
|
+
def initialize_copy(other)
|
69
|
+
super
|
70
|
+
|
71
|
+
@character_io = @character_io.dup
|
72
|
+
@character_io.buffered_io = buffered_io
|
73
|
+
@character_io.blocking_io = blocking_io
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end; end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IO; module LikeHelpers
|
4
|
+
|
5
|
+
##
|
6
|
+
# This module provides constants that represent true/false facts about the Ruby
|
7
|
+
# runtime.
|
8
|
+
module RubyFacts
|
9
|
+
##
|
10
|
+
# Set to `true` if the runtime Ruby version is less than 3.4.
|
11
|
+
RBVER_LT_3_4 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4')
|
12
|
+
|
13
|
+
##
|
14
|
+
# Set to `true` if the runtime Ruby version is less than 3.3.
|
15
|
+
RBVER_LT_3_3 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3')
|
16
|
+
|
17
|
+
##
|
18
|
+
# Set to `true` if the runtime Ruby version is less than 3.2.
|
19
|
+
RBVER_LT_3_2 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2')
|
20
|
+
|
21
|
+
##
|
22
|
+
# Set to `true` if the runtime Ruby version is less than 3.1.
|
23
|
+
RBVER_LT_3_1 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1')
|
24
|
+
|
25
|
+
##
|
26
|
+
# Set to `true` if the runtime Ruby version is less than 3.0.4.
|
27
|
+
RBVER_LT_3_0_4 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0.4')
|
28
|
+
|
29
|
+
##
|
30
|
+
# Set to `true` if the runtime Ruby version is less than 3.0.
|
31
|
+
RBVER_LT_3_0 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
|
32
|
+
end
|
33
|
+
end; end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class IO
|
2
|
+
|
3
|
+
##
|
4
|
+
# This module is a namespace for classes and modules used to implement IO::Like.
|
5
|
+
module LikeHelpers
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'io/like_helpers/abstract_io'
|
10
|
+
require 'io/like_helpers/buffered_io'
|
11
|
+
require 'io/like_helpers/delegated_io'
|
12
|
+
require 'io/like_helpers/duplexed_io'
|
13
|
+
require 'io/like_helpers/io'
|
14
|
+
require 'io/like_helpers/ruby_facts'
|