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,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