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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/NEWS.md +14 -1
  4. data/README.md +75 -94
  5. data/lib/io/like.rb +1916 -1314
  6. data/lib/io/like_helpers/abstract_io.rb +512 -0
  7. data/lib/io/like_helpers/blocking_io.rb +86 -0
  8. data/lib/io/like_helpers/buffered_io.rb +555 -0
  9. data/lib/io/like_helpers/character_io/basic_reader.rb +122 -0
  10. data/lib/io/like_helpers/character_io/converter_reader.rb +252 -0
  11. data/lib/io/like_helpers/character_io.rb +529 -0
  12. data/lib/io/like_helpers/delegated_io.rb +250 -0
  13. data/lib/io/like_helpers/duplexed_io.rb +259 -0
  14. data/lib/io/like_helpers/io.rb +21 -0
  15. data/lib/io/like_helpers/io_wrapper.rb +290 -0
  16. data/lib/io/like_helpers/pipeline.rb +77 -0
  17. data/lib/io/like_helpers/ruby_facts.rb +33 -0
  18. data/lib/io/like_helpers.rb +14 -0
  19. metadata +107 -224
  20. data/.yardopts +0 -1
  21. data/Rakefile +0 -228
  22. data/ruby.1.8.mspec +0 -7
  23. data/spec/binmode_spec.rb +0 -29
  24. data/spec/close_read_spec.rb +0 -64
  25. data/spec/close_spec.rb +0 -36
  26. data/spec/close_write_spec.rb +0 -61
  27. data/spec/closed_spec.rb +0 -16
  28. data/spec/each_byte_spec.rb +0 -38
  29. data/spec/each_line_spec.rb +0 -11
  30. data/spec/each_spec.rb +0 -11
  31. data/spec/eof_spec.rb +0 -11
  32. data/spec/fixtures/classes.rb +0 -96
  33. data/spec/fixtures/gets.txt +0 -9
  34. data/spec/fixtures/numbered_lines.txt +0 -5
  35. data/spec/fixtures/one_byte.txt +0 -1
  36. data/spec/fixtures/paragraphs.txt +0 -7
  37. data/spec/fixtures/readlines.txt +0 -6
  38. data/spec/flush_spec.rb +0 -8
  39. data/spec/getc_spec.rb +0 -44
  40. data/spec/gets_spec.rb +0 -212
  41. data/spec/isatty_spec.rb +0 -6
  42. data/spec/lineno_spec.rb +0 -84
  43. data/spec/output_spec.rb +0 -47
  44. data/spec/pos_spec.rb +0 -53
  45. data/spec/print_spec.rb +0 -97
  46. data/spec/printf_spec.rb +0 -24
  47. data/spec/putc_spec.rb +0 -57
  48. data/spec/puts_spec.rb +0 -99
  49. data/spec/read_spec.rb +0 -162
  50. data/spec/readchar_spec.rb +0 -49
  51. data/spec/readline_spec.rb +0 -60
  52. data/spec/readlines_spec.rb +0 -140
  53. data/spec/readpartial_spec.rb +0 -92
  54. data/spec/rewind_spec.rb +0 -56
  55. data/spec/seek_spec.rb +0 -72
  56. data/spec/shared/each.rb +0 -204
  57. data/spec/shared/eof.rb +0 -116
  58. data/spec/shared/pos.rb +0 -39
  59. data/spec/shared/tty.rb +0 -12
  60. data/spec/shared/write.rb +0 -53
  61. data/spec/sync_spec.rb +0 -56
  62. data/spec/sysread_spec.rb +0 -87
  63. data/spec/sysseek_spec.rb +0 -68
  64. data/spec/syswrite_spec.rb +0 -60
  65. data/spec/tell_spec.rb +0 -7
  66. data/spec/to_io_spec.rb +0 -19
  67. data/spec/tty_spec.rb +0 -6
  68. data/spec/ungetc_spec.rb +0 -118
  69. data/spec/write_spec.rb +0 -61
  70. data/spec_helper.rb +0 -49
  71. /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'