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,529 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'io/like_helpers/character_io/basic_reader'
|
3
|
+
require 'io/like_helpers/character_io/converter_reader'
|
4
|
+
require 'io/like_helpers/ruby_facts'
|
5
|
+
|
6
|
+
class IO; module LikeHelpers
|
7
|
+
|
8
|
+
##
|
9
|
+
# This class implements a stream that reads or writes characters to or from a
|
10
|
+
# byte oriented stream.
|
11
|
+
class CharacterIO
|
12
|
+
include RubyFacts
|
13
|
+
|
14
|
+
##
|
15
|
+
# Creates a new intance of this class.
|
16
|
+
#
|
17
|
+
# @param buffered_io [LikeHelpers::BufferedIO] a readable and/or writable
|
18
|
+
# stream that always blocks
|
19
|
+
# @param blocking_io [LikeHelpers::BlockingIO] a readable and/or writable
|
20
|
+
# stream that always blocks
|
21
|
+
# @param internal_encoding [Encoding, String] the internal encoding
|
22
|
+
# @param external_encoding [Encoding, String] the external encoding
|
23
|
+
# @param encoding_opts [Hash] options to be passed to String#encode
|
24
|
+
# @param sync [Boolean] when `true` causes write operations to bypass internal
|
25
|
+
# buffering
|
26
|
+
def initialize(
|
27
|
+
buffered_io,
|
28
|
+
blocking_io = buffered_io,
|
29
|
+
encoding_opts: {},
|
30
|
+
external_encoding: nil,
|
31
|
+
internal_encoding: nil,
|
32
|
+
sync: false
|
33
|
+
)
|
34
|
+
raise ArgumentError, 'buffered_io cannot be nil' if buffered_io.nil?
|
35
|
+
raise ArgumentError, 'blocking_io cannot be nil' if blocking_io.nil?
|
36
|
+
|
37
|
+
@buffered_io = buffered_io
|
38
|
+
@blocking_io = blocking_io
|
39
|
+
self.sync = sync
|
40
|
+
|
41
|
+
set_encoding(external_encoding, internal_encoding, **encoding_opts)
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :buffered_io
|
45
|
+
attr_accessor :blocking_io
|
46
|
+
|
47
|
+
##
|
48
|
+
# Returns `true` if the read buffer is empty and `false` otherwise.
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
def buffer_empty?
|
52
|
+
return true unless readable?
|
53
|
+
character_reader.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# The external encoding of this stream.
|
58
|
+
attr_reader :external_encoding
|
59
|
+
|
60
|
+
##
|
61
|
+
# The internal encoding of this stream. This is only used for read
|
62
|
+
# operations.
|
63
|
+
attr_reader :internal_encoding
|
64
|
+
|
65
|
+
##
|
66
|
+
# Reads all remaining characters from the stream.
|
67
|
+
#
|
68
|
+
# @return [String] a buffer containing the characters that were read
|
69
|
+
#
|
70
|
+
# @raise [Encoding::InvalidByteSequenceError] if character conversion is being
|
71
|
+
# performed and the next sequence of bytes are invalid in the external
|
72
|
+
# encoding
|
73
|
+
# @raise [EOFError] when reading at the end of the stream
|
74
|
+
# @raise [IOError] if the stream is not readable
|
75
|
+
def read_all
|
76
|
+
read_all_internal
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Returns the next character from the stream.
|
81
|
+
#
|
82
|
+
# @return [String] a buffer containing the character that was read
|
83
|
+
#
|
84
|
+
# @raise [EOFError] when reading at the end of the stream
|
85
|
+
# @raise [IOError] if the stream is not readable
|
86
|
+
def read_char
|
87
|
+
char = nil
|
88
|
+
|
89
|
+
begin
|
90
|
+
# The delegate's read buffer will have at least 1 byte in it at this
|
91
|
+
# point.
|
92
|
+
loop do
|
93
|
+
buffer = character_reader.content
|
94
|
+
char = buffer.force_encoding(character_reader.encoding)[0]
|
95
|
+
# Return the next character if it is valid for the encoding.
|
96
|
+
break if ! char.nil? && char.valid_encoding?
|
97
|
+
# Or if the buffer has more than 16 bytes in it, valid or not.
|
98
|
+
break if buffer.bytesize >= 16
|
99
|
+
|
100
|
+
character_reader.refill(false)
|
101
|
+
# At least 1 byte was added to the buffer, so try again.
|
102
|
+
end
|
103
|
+
rescue EOFError, IOError
|
104
|
+
# Reraise when no bytes were available.
|
105
|
+
raise if char.nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
character_reader.consume(char.bytesize)
|
109
|
+
char
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Returns the next line from the stream.
|
114
|
+
#
|
115
|
+
# @param separator [String, nil] a non-empty String that separates each
|
116
|
+
# line, an empty String that equates to 2 or more successive newlines as
|
117
|
+
# the separator, or `nil` to indicate reading all remaining data
|
118
|
+
# @param limit [Integer, nil] an Integer limiting the number of bytes
|
119
|
+
# returned in each line or `nil` to indicate no limit
|
120
|
+
# @param chomp [Boolean] when `true` trailing newlines and carriage returns
|
121
|
+
# will be removed from each line; ignored when `separator` is `nil`
|
122
|
+
#
|
123
|
+
# @return [String] a buffer containing the characters that were read
|
124
|
+
#
|
125
|
+
# @raise [EOFError] when reading at the end of the stream
|
126
|
+
# @raise [IOError] if the stream is not readable
|
127
|
+
def read_line(separator: $/, limit: nil, chomp: false, discard_newlines: false)
|
128
|
+
return read_all_internal(chomp: chomp) if ! (separator || limit)
|
129
|
+
|
130
|
+
if String === separator && separator.encoding != Encoding::BINARY
|
131
|
+
separator = separator.encode(character_reader.encoding).b
|
132
|
+
end
|
133
|
+
content = ''.b
|
134
|
+
|
135
|
+
return content.force_encoding(character_reader.encoding) if limit == 0
|
136
|
+
|
137
|
+
begin
|
138
|
+
self.discard_newlines if discard_newlines
|
139
|
+
|
140
|
+
index = nil
|
141
|
+
extra = 0
|
142
|
+
need_more = false
|
143
|
+
offset = 0
|
144
|
+
loop do
|
145
|
+
already_consumed = content.bytesize
|
146
|
+
content << character_reader.content
|
147
|
+
|
148
|
+
if separator && ! index
|
149
|
+
if Regexp === separator
|
150
|
+
match = content.match(separator, offset)
|
151
|
+
if match
|
152
|
+
index = match.end(0)
|
153
|
+
# Truncate the content to the end of the separator.
|
154
|
+
content.slice!(index..-1)
|
155
|
+
end
|
156
|
+
else
|
157
|
+
index = content.index(separator, offset)
|
158
|
+
if index
|
159
|
+
index += separator.bytesize
|
160
|
+
# Truncate the content to the end of the separator.
|
161
|
+
content.slice!(index..-1)
|
162
|
+
else
|
163
|
+
# Optimize the search that happens in the next loop iteration by
|
164
|
+
# excluding the range of bytes already searched.
|
165
|
+
offset = [0, content.bytesize - separator.bytesize + 1].max
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if limit && content.bytesize >= limit
|
171
|
+
# Truncate the content to no more than limit + 16 bytes in order to
|
172
|
+
# ensure that the last character is not truncated at the limit
|
173
|
+
# boundary.
|
174
|
+
need_more =
|
175
|
+
loop do
|
176
|
+
last_character =
|
177
|
+
content[0, limit + extra]
|
178
|
+
.force_encoding(character_reader.encoding)[-1]
|
179
|
+
# No more bytes are needed because the last character is whole and
|
180
|
+
# valid or we hit the limit + 16 bytes hard limit.
|
181
|
+
break false if last_character.valid_encoding?
|
182
|
+
break false if extra >= 16
|
183
|
+
extra += 1
|
184
|
+
# More bytes are needed, but the end of the character buffer has
|
185
|
+
# been reached.
|
186
|
+
break true if limit + extra > content.bytesize
|
187
|
+
end
|
188
|
+
|
189
|
+
content.slice!((limit + extra)..-1) unless need_more
|
190
|
+
end
|
191
|
+
|
192
|
+
character_reader.consume(content.bytesize - already_consumed)
|
193
|
+
|
194
|
+
# The separator string was found.
|
195
|
+
break if index
|
196
|
+
# The limit was reached.
|
197
|
+
break if limit && content.bytesize >= limit && ! need_more
|
198
|
+
|
199
|
+
character_reader.refill(false)
|
200
|
+
end
|
201
|
+
|
202
|
+
self.discard_newlines if discard_newlines
|
203
|
+
rescue EOFError
|
204
|
+
raise if content.empty?
|
205
|
+
end
|
206
|
+
|
207
|
+
# Remove the separator when requested.
|
208
|
+
content.slice!(separator) if chomp && separator
|
209
|
+
|
210
|
+
content.force_encoding(character_reader.encoding)
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Returns `true` if the stream is readable and `false` otherwise.
|
215
|
+
#
|
216
|
+
# @return [Boolean]
|
217
|
+
def readable?
|
218
|
+
return @readable if defined?(@readable) && ! @readable.nil?
|
219
|
+
@readable = buffered_io.readable?
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Clears the state of this stream.
|
224
|
+
#
|
225
|
+
# @return [nil]
|
226
|
+
def clear
|
227
|
+
return unless @character_reader
|
228
|
+
@character_reader.clear
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Sets the external and internal encodings of the stream.
|
234
|
+
#
|
235
|
+
# @param external [Encoding, nil] the external encoding
|
236
|
+
# @param internal [Encoding, nil] the internal encoding
|
237
|
+
# @param opts [Hash] encoding conversion options used when character or
|
238
|
+
# newline conversion is performed
|
239
|
+
#
|
240
|
+
# @return [nil]
|
241
|
+
def set_encoding(external, internal, **opts)
|
242
|
+
if external.nil? && ! internal.nil?
|
243
|
+
raise ArgumentError,
|
244
|
+
'external encoding cannot be nil when internal encoding is not nil'
|
245
|
+
end
|
246
|
+
|
247
|
+
internal = nil if internal == external
|
248
|
+
|
249
|
+
self.encoding_opts = opts
|
250
|
+
@internal_encoding = internal
|
251
|
+
@external_encoding = external
|
252
|
+
@character_reader = nil
|
253
|
+
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
|
257
|
+
##
|
258
|
+
# When set to `true` the internal write buffer will be bypassed. Any data
|
259
|
+
# currently in the buffer will be flushed prior to the next output operation.
|
260
|
+
# When set to `false`, the internal write buffer will be enabled.
|
261
|
+
#
|
262
|
+
# @param sync [Boolean] the sync mode
|
263
|
+
#
|
264
|
+
# @return [Boolean] the given value for `sync`
|
265
|
+
def sync=(sync)
|
266
|
+
@sync = sync ? true : false
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# @return [Boolean] `true` if the internal write buffer is being bypassed and
|
271
|
+
# `false` otherwise
|
272
|
+
def sync?
|
273
|
+
@sync ||= false
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Places bytes at the beginning of the read buffer.
|
278
|
+
#
|
279
|
+
# @param buffer [String] the bytes to insert into the read buffer
|
280
|
+
# @param length [Integer] the number of bytes from the beginning of `buffer`
|
281
|
+
# to insert into the read buffer
|
282
|
+
#
|
283
|
+
# @return [nil]
|
284
|
+
#
|
285
|
+
# @raise [IOError] if the remaining space in the internal buffer is
|
286
|
+
# insufficient to contain the given data
|
287
|
+
# @raise [IOError] if the stream is not readable
|
288
|
+
def unread(buffer, length: buffer.bytesize)
|
289
|
+
length = Integer(length)
|
290
|
+
raise ArgumentError, 'length must be at least 0' if length < 0
|
291
|
+
|
292
|
+
assert_readable
|
293
|
+
|
294
|
+
character_reader(length).unread(buffer.b, length: length)
|
295
|
+
end
|
296
|
+
|
297
|
+
##
|
298
|
+
# Returns `true` if the stream is writable and `false` otherwise.
|
299
|
+
#
|
300
|
+
# @return [Boolean]
|
301
|
+
def writable?
|
302
|
+
return @writable if defined?(@writable) && ! @writable.nil?
|
303
|
+
@writable = buffered_io.writable?
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Writes characters to the stream, performing character and newline conversion
|
308
|
+
# first if necessary.
|
309
|
+
#
|
310
|
+
# This method always blocks until all data is written.
|
311
|
+
#
|
312
|
+
# @param buffer [String] the characters to write
|
313
|
+
#
|
314
|
+
# @return [Integer] the number of bytes written, after conversion
|
315
|
+
#
|
316
|
+
# @raise [IOError] if the stream is not writable
|
317
|
+
def write(buffer)
|
318
|
+
assert_writable
|
319
|
+
|
320
|
+
target_encoding = external_encoding
|
321
|
+
if target_encoding.nil? || target_encoding == Encoding::BINARY
|
322
|
+
target_encoding = buffer.encoding
|
323
|
+
end
|
324
|
+
if target_encoding != buffer.encoding || ! encoding_opts_w.empty?
|
325
|
+
buffer = buffer.encode(target_encoding, **encoding_opts_w)
|
326
|
+
end
|
327
|
+
|
328
|
+
writer = sync? ? blocking_io : buffered_io
|
329
|
+
buffer = buffer.b
|
330
|
+
bytes_written = 0
|
331
|
+
while bytes_written < buffer.bytesize do
|
332
|
+
bytes_written += writer.write(buffer[bytes_written..-1])
|
333
|
+
end
|
334
|
+
bytes_written
|
335
|
+
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
##
|
340
|
+
# Raises an exception if the stream is not open for reading.
|
341
|
+
#
|
342
|
+
# @return [nil]
|
343
|
+
#
|
344
|
+
# @raise IOError if the stream is not open for reading
|
345
|
+
def assert_readable
|
346
|
+
raise IOError, 'not opened for reading' unless readable?
|
347
|
+
end
|
348
|
+
|
349
|
+
##
|
350
|
+
# Raises an exception if the stream is not open for writing.
|
351
|
+
#
|
352
|
+
# @return [nil]
|
353
|
+
#
|
354
|
+
# @raise IOError if the stream is not open for writing
|
355
|
+
def assert_writable
|
356
|
+
raise IOError, 'not opened for writing' unless writable?
|
357
|
+
end
|
358
|
+
|
359
|
+
##
|
360
|
+
# @param buffer_size [Integer, nil] the size of the internal character buffer;
|
361
|
+
# ignored unless character or newline conversion will be performed
|
362
|
+
#
|
363
|
+
# @return [BasicReader, ConverterReader] a character reader based on the
|
364
|
+
# external encoding, internal encoding, and universal newline settings of
|
365
|
+
# this stream
|
366
|
+
def character_reader(buffer_size = nil)
|
367
|
+
return @character_reader if @character_reader
|
368
|
+
|
369
|
+
# Hack the internal encoding to be the default internal encoding when:
|
370
|
+
# 1. Ruby is less than version 3.3 (for compatibility)
|
371
|
+
# 2. The internal encoding is not set explicitly
|
372
|
+
# 3. Character conversion would be necessary with it set
|
373
|
+
internal_encoding = self.internal_encoding
|
374
|
+
if RBVER_LT_3_3 &&
|
375
|
+
! internal_encoding &&
|
376
|
+
external_encoding != Encoding::BINARY &&
|
377
|
+
external_encoding != Encoding.default_internal
|
378
|
+
internal_encoding = Encoding.default_internal
|
379
|
+
end
|
380
|
+
|
381
|
+
@character_reader = if external_encoding &&
|
382
|
+
(internal_encoding || universal_newline?)
|
383
|
+
ConverterReader.new(
|
384
|
+
buffered_io,
|
385
|
+
buffer_size: buffer_size,
|
386
|
+
encoding_opts: encoding_opts_r,
|
387
|
+
external_encoding: external_encoding,
|
388
|
+
internal_encoding: internal_encoding
|
389
|
+
)
|
390
|
+
else
|
391
|
+
BasicReader.new(
|
392
|
+
buffered_io,
|
393
|
+
encoding: external_encoding
|
394
|
+
)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# Consumes 1 or more consecutive newline characters from the beginning of the
|
400
|
+
# stream.
|
401
|
+
#
|
402
|
+
# @return [nil]
|
403
|
+
def discard_newlines
|
404
|
+
newline = "\n".dup
|
405
|
+
if RBVER_LT_3_4
|
406
|
+
newline.encode!(internal_encoding) if internal_encoding
|
407
|
+
else
|
408
|
+
newline.encode!(character_reader.encoding)
|
409
|
+
end
|
410
|
+
newline.force_encoding(Encoding::BINARY)
|
411
|
+
begin
|
412
|
+
loop do
|
413
|
+
# Consume bytes matching the newline character from the beginning of the
|
414
|
+
# buffer.
|
415
|
+
while character_reader.content.start_with?(newline) do
|
416
|
+
character_reader.consume(newline.bytesize)
|
417
|
+
end
|
418
|
+
|
419
|
+
# Stop when adding more bytes to the buffer could not possibly complete
|
420
|
+
# the newline character.
|
421
|
+
break unless newline.start_with?(character_reader.content)
|
422
|
+
|
423
|
+
# This will stop the loop by raising EOFError if there are no more
|
424
|
+
# bytes.
|
425
|
+
character_reader.refill
|
426
|
+
end
|
427
|
+
rescue EOFError
|
428
|
+
# Stop when there are no more bytes to read from the stream.
|
429
|
+
end
|
430
|
+
|
431
|
+
nil
|
432
|
+
end
|
433
|
+
|
434
|
+
##
|
435
|
+
# Creates an instance of this class that copies state from `other`.
|
436
|
+
#
|
437
|
+
# @param other [CharacterIO] the instance to copy
|
438
|
+
#
|
439
|
+
# @return [nil]
|
440
|
+
#
|
441
|
+
# @raise [IOError] if `other` is closed
|
442
|
+
def initialize_copy(other)
|
443
|
+
super
|
444
|
+
|
445
|
+
@character_reader = nil
|
446
|
+
|
447
|
+
nil
|
448
|
+
end
|
449
|
+
|
450
|
+
##
|
451
|
+
# Sets the encoding options.
|
452
|
+
#
|
453
|
+
# @return _opts_
|
454
|
+
def encoding_opts=(opts)
|
455
|
+
if opts.key?(:newline) &&
|
456
|
+
! %i{universal crlf cr lf}.include?(opts[:newline])
|
457
|
+
raise ArgumentError, "unexpected value for newline option: #{opts[:newline]}"
|
458
|
+
end
|
459
|
+
|
460
|
+
# Ruby ignores xml conversion as well as newline decorators other than
|
461
|
+
# universal for reading.
|
462
|
+
@encoding_opts_r = opts.reject do |k, v|
|
463
|
+
k == :xml ||
|
464
|
+
k == :crlf_newline || k == :cr_newline || k == :lf_newline ||
|
465
|
+
(k == :newline && (v == :crlf || v == :cr || v == :lf))
|
466
|
+
end
|
467
|
+
|
468
|
+
# Ruby ignores the universal newline decorator for writing.
|
469
|
+
@encoding_opts_w = opts.reject do |k, v|
|
470
|
+
k == :universal_newline || (k == :newline && v == :universal)
|
471
|
+
end
|
472
|
+
|
473
|
+
opts
|
474
|
+
end
|
475
|
+
|
476
|
+
def universal_newline?
|
477
|
+
encoding_opts_r[:newline] ?
|
478
|
+
encoding_opts_r[:newline] == :universal :
|
479
|
+
!!encoding_opts_r.fetch(:universal_newline, false)
|
480
|
+
end
|
481
|
+
|
482
|
+
##
|
483
|
+
# The encoding options for reading.
|
484
|
+
attr_reader :encoding_opts_r
|
485
|
+
|
486
|
+
##
|
487
|
+
# The encoding options for writing.
|
488
|
+
attr_reader :encoding_opts_w
|
489
|
+
|
490
|
+
##
|
491
|
+
# Reads all remaining characters from the stream. This exists only to handle
|
492
|
+
# chomp behavior on Ruby < 3.2 without exposing that interface publicly.
|
493
|
+
#
|
494
|
+
# @todo Move this method implementation to #read_all when Ruby < 3.2 support
|
495
|
+
# is dropped.
|
496
|
+
#
|
497
|
+
# @param chomp [Boolean] performs a chomp on the content when `true` on Ruby <
|
498
|
+
# 3.2; otherwise, ignored
|
499
|
+
#
|
500
|
+
# @return [String] a buffer containing the characters that were read
|
501
|
+
#
|
502
|
+
# @raise [Encoding::InvalidByteSequenceError] if character conversion is being
|
503
|
+
# performed and the next sequence of bytes are invalid in the external
|
504
|
+
# encoding
|
505
|
+
# @raise [EOFError] when reading at the end of the stream
|
506
|
+
# @raise [IOError] if the stream is not readable
|
507
|
+
def read_all_internal(chomp: false)
|
508
|
+
content = ''.b
|
509
|
+
begin
|
510
|
+
loop do
|
511
|
+
already_consumed = content.bytesize
|
512
|
+
content << character_reader.content
|
513
|
+
character_reader.consume(content.bytesize - already_consumed)
|
514
|
+
character_reader.refill
|
515
|
+
end
|
516
|
+
rescue EOFError
|
517
|
+
raise if content.empty?
|
518
|
+
end
|
519
|
+
|
520
|
+
# HACK:
|
521
|
+
# A default chomp is performed on Ruby <3.2 when chomp is requested.
|
522
|
+
content.chomp! if RBVER_LT_3_2 && chomp
|
523
|
+
|
524
|
+
content.force_encoding(character_reader.encoding)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end; end
|
528
|
+
|
529
|
+
# vim: ts=2 sw=2 et
|