fake_io 0.1.0
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/.document +3 -0
- data/.github/workflows/ruby.yml +28 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +8 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +20 -0
- data/README.md +93 -0
- data/Rakefile +13 -0
- data/fake_io.gemspec +61 -0
- data/gemspec.yml +20 -0
- data/lib/fake_io/version.rb +4 -0
- data/lib/fake_io.rb +1213 -0
- data/spec/classes/test_io.rb +36 -0
- data/spec/fake_io_spec.rb +1212 -0
- data/spec/spec_helper.rb +4 -0
- metadata +81 -0
data/lib/fake_io.rb
ADDED
@@ -0,0 +1,1213 @@
|
|
1
|
+
#
|
2
|
+
# The {FakeIO} module provides an API for communicating with controlled
|
3
|
+
# resources, that is still compatible with the standard
|
4
|
+
# [IO](http://rubydoc.info/stdlib/core/IO) class.
|
5
|
+
#
|
6
|
+
# To utilize the {FakeIO} module, simply include it into a class and define
|
7
|
+
# either {#io_read} and/or {#io_write}, to handle the reading and writing
|
8
|
+
# of data.
|
9
|
+
#
|
10
|
+
# The {#io_open} method handles optionally opening and assigning the
|
11
|
+
# file descriptor for the IO stream. The {#io_close} method handles
|
12
|
+
# optionally closing the IO stream.
|
13
|
+
#
|
14
|
+
module FakeIO
|
15
|
+
|
16
|
+
include Enumerable
|
17
|
+
include File::Constants
|
18
|
+
|
19
|
+
# The position within the IO stream
|
20
|
+
attr_reader :pos
|
21
|
+
|
22
|
+
alias tell pos
|
23
|
+
|
24
|
+
# The end-of-file indicator
|
25
|
+
attr_reader :eof
|
26
|
+
|
27
|
+
alias eof? eof
|
28
|
+
|
29
|
+
# The external encoding to convert all read data into.
|
30
|
+
#
|
31
|
+
# @return [Encoding]
|
32
|
+
attr_accessor :external_encoding
|
33
|
+
|
34
|
+
# The internal encoding to convert all internal data to.
|
35
|
+
#
|
36
|
+
# @return [Encoding, nil]
|
37
|
+
attr_accessor :internal_encoding
|
38
|
+
|
39
|
+
#
|
40
|
+
# Initializes the IO stream.
|
41
|
+
#
|
42
|
+
def initialize
|
43
|
+
@read = true
|
44
|
+
@write = true
|
45
|
+
|
46
|
+
@closed = true
|
47
|
+
@autoclose = true
|
48
|
+
@close_on_exec = true
|
49
|
+
|
50
|
+
@binmode = false
|
51
|
+
@tty = false
|
52
|
+
|
53
|
+
@external_encoding = Encoding.default_external
|
54
|
+
@internal_encoding = Encoding.default_internal
|
55
|
+
|
56
|
+
@sync = false
|
57
|
+
|
58
|
+
open
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Announce an intention to access data from the current file in a specific
|
63
|
+
# pattern.
|
64
|
+
#
|
65
|
+
# @param [:normal, :sequential, :random, :willneed, :dontneed, :noreuse] advice
|
66
|
+
# The advice mode.
|
67
|
+
#
|
68
|
+
# @param [Integer] offset
|
69
|
+
# The offset within the file.
|
70
|
+
#
|
71
|
+
# @param [Integer] len
|
72
|
+
# The length
|
73
|
+
#
|
74
|
+
# @return [nil]
|
75
|
+
#
|
76
|
+
# @note Not implemented by default.
|
77
|
+
#
|
78
|
+
# @see https://man7.org/linux/man-pages/man2/posix_fadvise.2.html
|
79
|
+
#
|
80
|
+
def advise(advice,offset=0,len=0)
|
81
|
+
# no-op
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Sets the encoding.
|
86
|
+
#
|
87
|
+
# @param [Array] arguments
|
88
|
+
#
|
89
|
+
# @overload set_encoding(encoding)
|
90
|
+
# @param [String] encoding
|
91
|
+
# The encoding string, which can be the encoding name or the external
|
92
|
+
# and internal encoding names separated by a `:` or `,` character.
|
93
|
+
#
|
94
|
+
# @overload set_encoding(ext_inc,int_enc)
|
95
|
+
# @param [Encoding] ext_inc
|
96
|
+
# The new external encoding.
|
97
|
+
#
|
98
|
+
# @param [Encoding] int_enc
|
99
|
+
# The new internal encoding.
|
100
|
+
#
|
101
|
+
# @raise [TypeError]
|
102
|
+
# The given argument was not a String or Encoding object.
|
103
|
+
#
|
104
|
+
# @raise [ArgumentError]
|
105
|
+
# Either no arguments were given or more than three arguments were given.
|
106
|
+
#
|
107
|
+
def set_encoding(*arguments)
|
108
|
+
if arguments.length == 1
|
109
|
+
case arguments[0]
|
110
|
+
when String
|
111
|
+
string = arguments[0]
|
112
|
+
|
113
|
+
if string.include?(':') || string.include?(',')
|
114
|
+
ext_enc, int_enc = string.split(/[:,]\s*/,2)
|
115
|
+
|
116
|
+
self.external_encoding = Encoding.find(ext_enc)
|
117
|
+
self.internal_encoding = Encoding.find(int_enc)
|
118
|
+
else
|
119
|
+
self.external_encoding = Encoding.find(string)
|
120
|
+
end
|
121
|
+
when Encoding
|
122
|
+
self.external_encoding = arguments[0]
|
123
|
+
else
|
124
|
+
raise(TypeError,"argument must be a String or Encoding object")
|
125
|
+
end
|
126
|
+
elsif arguments.length == 2 || arguments.length == 3
|
127
|
+
self.external_encoding = arguments[0]
|
128
|
+
self.internal_encoding = arguments[1]
|
129
|
+
else
|
130
|
+
raise(ArgumentError,"wrong number of arguments (given #{arguments.length}, expected 1..3)")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Sets the {#external_encoding} based on the Byte Order Mark (BOM) bytes at
|
136
|
+
# the beginning of the file.
|
137
|
+
#
|
138
|
+
def set_encoding_by_bom
|
139
|
+
case (b1 = getbyte)
|
140
|
+
when 0x00 # possible UTF-32 (BE)
|
141
|
+
b2 = getbyte
|
142
|
+
b3 = getbyte
|
143
|
+
b4 = getbyte
|
144
|
+
|
145
|
+
if (b2 == 0x00) && (b3 == 0xFE) && (b4 == 0xFF)
|
146
|
+
# UTF-32 (BE) BOM (0x00 0x00 0xFE 0xFF) detected
|
147
|
+
self.external_encoding = Encoding::UTF_32BE
|
148
|
+
else
|
149
|
+
ungetbyte(b4) if b4
|
150
|
+
ungetbyte(b3) if b3
|
151
|
+
ungetbyte(b2) if b2
|
152
|
+
ungetbyte(b1) if b1
|
153
|
+
end
|
154
|
+
when 0x28 # possible UTF-7
|
155
|
+
b2 = getbyte
|
156
|
+
b3 = getbyte
|
157
|
+
|
158
|
+
if (b2 == 0x2F) && (b3 == 0x76)
|
159
|
+
# UTF-7 BOM (0x28 0x2F 0x76) detected
|
160
|
+
self.external_encoding = Encoding::UTF_7
|
161
|
+
else
|
162
|
+
ungetbyte(b3) if b3
|
163
|
+
ungetbyte(b2) if b2
|
164
|
+
ungetbyte(b1) if b1
|
165
|
+
end
|
166
|
+
when 0xEF # possible UTF-8
|
167
|
+
b2 = getbyte
|
168
|
+
b3 = getbyte
|
169
|
+
|
170
|
+
if (b2 == 0xBB) && (b3 == 0xBF)
|
171
|
+
self.external_encoding = Encoding::UTF_8
|
172
|
+
else
|
173
|
+
ungetbyte(b3) if b3
|
174
|
+
ungetbyte(b2) if b2
|
175
|
+
ungetbyte(b1) if b1
|
176
|
+
end
|
177
|
+
when 0xFE # possible UTF-16 (BE)
|
178
|
+
b2 = getbyte
|
179
|
+
|
180
|
+
if (b2 == 0xFF)
|
181
|
+
self.external_encoding = Encoding::UTF_16BE
|
182
|
+
else
|
183
|
+
ungetbyte(b2) if b2
|
184
|
+
ungetbyte(b1) if b1
|
185
|
+
end
|
186
|
+
when 0xFF # possible UTF-16 (LE) or UTF-32 (LE)
|
187
|
+
b2 = getbyte
|
188
|
+
|
189
|
+
if (b2 == 0xFE)
|
190
|
+
self.external_encoding = Encoding::UTF_16LE
|
191
|
+
else
|
192
|
+
ungetbyte(b2) if b2
|
193
|
+
ungetbyte(b1) if b1
|
194
|
+
end
|
195
|
+
else
|
196
|
+
ungetbyte(b1) if b1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Iterates over each block within the IO stream.
|
202
|
+
#
|
203
|
+
# @yield [block]
|
204
|
+
# The given block will be passed each block of data from the IO
|
205
|
+
# stream.
|
206
|
+
#
|
207
|
+
# @yieldparam [String] block
|
208
|
+
# A block of data from the IO stream.
|
209
|
+
#
|
210
|
+
# @return [Enumerator]
|
211
|
+
# If no block is given, an enumerator object will be returned.
|
212
|
+
#
|
213
|
+
# @raise [IOError]
|
214
|
+
# The stream is closed for reading.
|
215
|
+
#
|
216
|
+
def each_chunk
|
217
|
+
return enum_for(__method__) unless block_given?
|
218
|
+
|
219
|
+
unless @read
|
220
|
+
raise(IOError,"closed for reading")
|
221
|
+
end
|
222
|
+
|
223
|
+
unless io_buffer_empty?
|
224
|
+
# read from the buffer first
|
225
|
+
chunk = io_buffer_read
|
226
|
+
chunk.force_encoding(external_encoding)
|
227
|
+
|
228
|
+
yield chunk
|
229
|
+
end
|
230
|
+
|
231
|
+
until @eof
|
232
|
+
begin
|
233
|
+
# no data currently available, sleep and retry
|
234
|
+
until (chunk = io_read)
|
235
|
+
sleep(1)
|
236
|
+
end
|
237
|
+
rescue EOFError
|
238
|
+
@eof = true
|
239
|
+
break
|
240
|
+
end
|
241
|
+
|
242
|
+
chunk.force_encoding(internal_encoding) if internal_encoding
|
243
|
+
chunk.force_encoding(external_encoding)
|
244
|
+
|
245
|
+
unless chunk.empty?
|
246
|
+
yield chunk
|
247
|
+
else
|
248
|
+
# short read
|
249
|
+
@eof = true
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
#
|
255
|
+
# Reads data from the IO stream.
|
256
|
+
#
|
257
|
+
# @param [Integer, nil] length
|
258
|
+
# The maximum amount of data to read. If `nil` is given, the entire
|
259
|
+
# IO stream will be read.
|
260
|
+
#
|
261
|
+
# @param [#<<] buffer
|
262
|
+
# The optional buffer to append the data to.
|
263
|
+
#
|
264
|
+
# @return [String]
|
265
|
+
# The data read from the IO stream.
|
266
|
+
#
|
267
|
+
def read(length=nil,buffer=nil)
|
268
|
+
bytes_remaining = (length || Float::INFINITY)
|
269
|
+
result = String.new(encoding: external_encoding)
|
270
|
+
|
271
|
+
each_chunk do |chunk|
|
272
|
+
if bytes_remaining < chunk.bytesize
|
273
|
+
fragment = chunk.byteslice(0,bytes_remaining)
|
274
|
+
|
275
|
+
remaining_length = chunk.bytesize - bytes_remaining
|
276
|
+
remaining_data = chunk.byteslice(bytes_remaining,remaining_length)
|
277
|
+
|
278
|
+
result << fragment
|
279
|
+
io_buffer_append(remaining_data)
|
280
|
+
@pos += bytes_remaining
|
281
|
+
break
|
282
|
+
else
|
283
|
+
result << chunk
|
284
|
+
bytes_read = chunk.bytesize
|
285
|
+
bytes_remaining -= bytes_read
|
286
|
+
@pos += bytes_read
|
287
|
+
end
|
288
|
+
|
289
|
+
# no more data to read
|
290
|
+
break if bytes_remaining == 0
|
291
|
+
end
|
292
|
+
|
293
|
+
unless result.empty?
|
294
|
+
buffer << result if buffer
|
295
|
+
return result
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
alias sysread read
|
300
|
+
alias read_nonblock read
|
301
|
+
|
302
|
+
#
|
303
|
+
# Reads partial data from the IO stream.
|
304
|
+
#
|
305
|
+
# @param [Integer] length
|
306
|
+
# The maximum amount of data to read.
|
307
|
+
#
|
308
|
+
# @param [#<<] buffer
|
309
|
+
# The optional buffer to append the data to.
|
310
|
+
#
|
311
|
+
# @return [String]
|
312
|
+
# The data read from the IO stream.
|
313
|
+
#
|
314
|
+
# @see #read
|
315
|
+
#
|
316
|
+
def readpartial(length,buffer=nil)
|
317
|
+
read(length,buffer)
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# Reads a byte from the IO stream.
|
322
|
+
#
|
323
|
+
# @return [Integer]
|
324
|
+
# A byte from the IO stream.
|
325
|
+
#
|
326
|
+
# @note
|
327
|
+
# Only available on Ruby > 1.9.
|
328
|
+
#
|
329
|
+
def getbyte
|
330
|
+
if (c = read(1))
|
331
|
+
c.bytes.first
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
#
|
336
|
+
# Reads a character from the IO stream.
|
337
|
+
#
|
338
|
+
# @return [String]
|
339
|
+
# A character from the IO stream.
|
340
|
+
#
|
341
|
+
def getc
|
342
|
+
read(1)
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# Un-reads a byte from the IO stream, append it to the read buffer.
|
347
|
+
#
|
348
|
+
# @param [Integer, String] byte
|
349
|
+
# The byte to un-read.
|
350
|
+
#
|
351
|
+
# @return [nil]
|
352
|
+
# The byte was appended to the read buffer.
|
353
|
+
#
|
354
|
+
# @note
|
355
|
+
# Only available on Ruby > 1.9.
|
356
|
+
#
|
357
|
+
def ungetbyte(byte)
|
358
|
+
char = case byte
|
359
|
+
when Integer then byte.chr
|
360
|
+
else byte.to_s
|
361
|
+
end
|
362
|
+
|
363
|
+
io_buffer_prepend(char)
|
364
|
+
@pos -= char.bytesize
|
365
|
+
return nil
|
366
|
+
end
|
367
|
+
|
368
|
+
#
|
369
|
+
# Un-reads a character from the IO stream, append it to the
|
370
|
+
# read buffer.
|
371
|
+
#
|
372
|
+
# @param [#to_s] char
|
373
|
+
# The character to un-read.
|
374
|
+
#
|
375
|
+
# @return [nil]
|
376
|
+
# The character was appended to the read buffer.
|
377
|
+
#
|
378
|
+
def ungetc(char)
|
379
|
+
char = char.to_s
|
380
|
+
|
381
|
+
io_buffer_prepend(char)
|
382
|
+
@pos -= char.bytesize
|
383
|
+
return nil
|
384
|
+
end
|
385
|
+
|
386
|
+
#
|
387
|
+
# Reads a string from the IO stream.
|
388
|
+
#
|
389
|
+
# @param [String] separator
|
390
|
+
# The separator character that designates the end of the string
|
391
|
+
# being read.
|
392
|
+
#
|
393
|
+
# @return [String]
|
394
|
+
# The string from the IO stream.
|
395
|
+
#
|
396
|
+
def gets(separator=$/)
|
397
|
+
# increment the line number
|
398
|
+
@lineno += 1
|
399
|
+
|
400
|
+
# if no separator is given, read everything
|
401
|
+
return read if separator.nil?
|
402
|
+
|
403
|
+
line = String.new(encoding: external_encoding)
|
404
|
+
|
405
|
+
while (c = read(1))
|
406
|
+
line << c
|
407
|
+
|
408
|
+
break if c == separator # separator reached
|
409
|
+
end
|
410
|
+
|
411
|
+
if line.empty?
|
412
|
+
# a line should atleast contain the separator
|
413
|
+
raise(EOFError,"end of file reached")
|
414
|
+
end
|
415
|
+
|
416
|
+
return line
|
417
|
+
end
|
418
|
+
|
419
|
+
#
|
420
|
+
# Reads a character from the IO stream.
|
421
|
+
#
|
422
|
+
# @return [Integer]
|
423
|
+
# The character from the IO stream.
|
424
|
+
#
|
425
|
+
# @raise [EOFError]
|
426
|
+
# The end-of-file has been reached.
|
427
|
+
#
|
428
|
+
# @see #getc
|
429
|
+
#
|
430
|
+
def readchar
|
431
|
+
unless (c = getc)
|
432
|
+
raise(EOFError,"end of file reached")
|
433
|
+
end
|
434
|
+
|
435
|
+
return c
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Reads a byte from the IO stream.
|
440
|
+
#
|
441
|
+
# @return [Integer]
|
442
|
+
# A byte from the IO stream.
|
443
|
+
#
|
444
|
+
# @raise [EOFError]
|
445
|
+
# The end-of-file has been reached.
|
446
|
+
#
|
447
|
+
def readbyte
|
448
|
+
unless (c = read(1))
|
449
|
+
raise(EOFError,"end of file reached")
|
450
|
+
end
|
451
|
+
|
452
|
+
return c.bytes.first
|
453
|
+
end
|
454
|
+
|
455
|
+
#
|
456
|
+
# Reads a line from the IO stream.
|
457
|
+
#
|
458
|
+
# @param [String] separator
|
459
|
+
# The separator character that designates the end of the string
|
460
|
+
# being read.
|
461
|
+
#
|
462
|
+
# @return [String]
|
463
|
+
# The string from the IO stream.
|
464
|
+
#
|
465
|
+
# @raise [EOFError]
|
466
|
+
# The end-of-file has been reached.
|
467
|
+
#
|
468
|
+
# @see #gets
|
469
|
+
#
|
470
|
+
def readline(separator=$/)
|
471
|
+
unless (line = gets(separator))
|
472
|
+
raise(EOFError,"end of file reached")
|
473
|
+
end
|
474
|
+
|
475
|
+
return line
|
476
|
+
end
|
477
|
+
|
478
|
+
#
|
479
|
+
# Iterates over each byte in the IO stream.
|
480
|
+
#
|
481
|
+
# @yield [byte]
|
482
|
+
# The given block will be passed each byte in the IO stream.
|
483
|
+
#
|
484
|
+
# @yieldparam [Integer] byte
|
485
|
+
# A byte from the IO stream.
|
486
|
+
#
|
487
|
+
# @return [Enumerator]
|
488
|
+
# If no block is given, an enumerator object will be returned.
|
489
|
+
#
|
490
|
+
def each_byte(&block)
|
491
|
+
return enum_for(__method__) unless block
|
492
|
+
|
493
|
+
each_chunk { |chunk| chunk.each_byte(&block) }
|
494
|
+
end
|
495
|
+
|
496
|
+
if RUBY_VERSION < '3.'
|
497
|
+
#
|
498
|
+
# Deprecated alias to {#each_bytes}.
|
499
|
+
#
|
500
|
+
# @deprecated Removed in Ruby 3.0.
|
501
|
+
#
|
502
|
+
def bytes
|
503
|
+
each_byte
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
#
|
508
|
+
# Iterates over each character in the IO stream.
|
509
|
+
#
|
510
|
+
# @yield [char]
|
511
|
+
# The given block will be passed each character in the IO stream.
|
512
|
+
#
|
513
|
+
# @yieldparam [String] char
|
514
|
+
# A character from the IO stream.
|
515
|
+
#
|
516
|
+
# @return [Enumerator]
|
517
|
+
# If no block is given, an enumerator object will be returned.
|
518
|
+
#
|
519
|
+
def each_char(&block)
|
520
|
+
return enum_for(__method__) unless block
|
521
|
+
|
522
|
+
each_chunk { |chunk| chunk.each_char(&block) }
|
523
|
+
end
|
524
|
+
|
525
|
+
if RUBY_VERSION < '3.'
|
526
|
+
#
|
527
|
+
# Deprecated alias to {#each_char}.
|
528
|
+
#
|
529
|
+
# @deprecated Removed in Ruby 3.0.
|
530
|
+
#
|
531
|
+
def chars
|
532
|
+
each_char
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
#
|
537
|
+
# Passes the Integer ordinal of each character in the stream.
|
538
|
+
#
|
539
|
+
# @yield [ord]
|
540
|
+
# The given block will be passed each codepoint.
|
541
|
+
#
|
542
|
+
# @yieldparam [String] ord
|
543
|
+
# The ordinal of a character from the stream.
|
544
|
+
#
|
545
|
+
# @return [Enumerator]
|
546
|
+
# If no block is given an Enumerator object will be returned.
|
547
|
+
#
|
548
|
+
# @note
|
549
|
+
# Only available on Ruby > 1.9.
|
550
|
+
#
|
551
|
+
def each_codepoint
|
552
|
+
return enum_for(__method__) unless block_given?
|
553
|
+
|
554
|
+
each_char { |c| yield c.ord }
|
555
|
+
end
|
556
|
+
|
557
|
+
if RUBY_VERSION < '3.'
|
558
|
+
#
|
559
|
+
# Deprecated alias to {#each_codepoint}.
|
560
|
+
#
|
561
|
+
# @deprecated Removed in Ruby 3.0
|
562
|
+
#
|
563
|
+
def codepoints
|
564
|
+
each_codepoint
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
#
|
569
|
+
# Iterates over each line in the IO stream.
|
570
|
+
#
|
571
|
+
# @yield [line]
|
572
|
+
# The given block will be passed each line in the IO stream.
|
573
|
+
#
|
574
|
+
# @yieldparam [String] line
|
575
|
+
# A line from the IO stream.
|
576
|
+
#
|
577
|
+
# @return [Enumerator]
|
578
|
+
# If no block is given, an enumerator object will be returned.
|
579
|
+
#
|
580
|
+
# @see #gets
|
581
|
+
#
|
582
|
+
def each_line(separator=$/)
|
583
|
+
return enum_for(__method__,separator) unless block_given?
|
584
|
+
|
585
|
+
loop do
|
586
|
+
begin
|
587
|
+
line = gets(separator)
|
588
|
+
rescue EOFError
|
589
|
+
break
|
590
|
+
end
|
591
|
+
|
592
|
+
yield line
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
alias each each_line
|
597
|
+
|
598
|
+
if RUBY_VERSION < '3.'
|
599
|
+
#
|
600
|
+
# Deprecated alias to {#each_line}.
|
601
|
+
#
|
602
|
+
# @deprecated Removed in Ruby 3.0.
|
603
|
+
#
|
604
|
+
def lines(*args)
|
605
|
+
each_line(*args)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
#
|
610
|
+
# Reads every line from the IO stream.
|
611
|
+
#
|
612
|
+
# @return [Array<String>]
|
613
|
+
# The lines in the IO stream.
|
614
|
+
#
|
615
|
+
# @see #gets
|
616
|
+
#
|
617
|
+
def readlines(separator=$/)
|
618
|
+
enum_for(:each_line,separator).to_a
|
619
|
+
end
|
620
|
+
|
621
|
+
#
|
622
|
+
# Writes data to the IO stream.
|
623
|
+
#
|
624
|
+
# @param [String] data
|
625
|
+
# The data to write.
|
626
|
+
#
|
627
|
+
# @return [Integer]
|
628
|
+
# The number of bytes written.
|
629
|
+
#
|
630
|
+
# @raise [IOError]
|
631
|
+
# The stream is closed for writing.
|
632
|
+
#
|
633
|
+
def write(data)
|
634
|
+
unless @write
|
635
|
+
raise(IOError,"closed for writing")
|
636
|
+
end
|
637
|
+
|
638
|
+
data = data.to_s
|
639
|
+
data.force_encoding(internal_encoding) if internal_encoding
|
640
|
+
|
641
|
+
io_write(data)
|
642
|
+
end
|
643
|
+
|
644
|
+
alias syswrite write
|
645
|
+
alias write_nonblock write
|
646
|
+
|
647
|
+
#
|
648
|
+
# Reads data at a given offset, without changing the current {#pos}.
|
649
|
+
#
|
650
|
+
# @param [Integer] maxlen
|
651
|
+
# The maximum amount of data to read. If `nil` is given, the entire
|
652
|
+
# IO stream will be read.
|
653
|
+
#
|
654
|
+
# @param [Integer] offset
|
655
|
+
# The offset to read the data at.
|
656
|
+
#
|
657
|
+
# @param [#<<, nil] outbuf
|
658
|
+
# The optional buffer to append the data to.
|
659
|
+
#
|
660
|
+
# @return [String]
|
661
|
+
# The data read from the IO stream.
|
662
|
+
#
|
663
|
+
# @see #read
|
664
|
+
#
|
665
|
+
def pread(maxlen,offset,outbuf)
|
666
|
+
old_pos = pos
|
667
|
+
seek(offset)
|
668
|
+
|
669
|
+
data = read(maxlen,outbuf)
|
670
|
+
seek(old_pos)
|
671
|
+
return data
|
672
|
+
end
|
673
|
+
|
674
|
+
#
|
675
|
+
# Writes data to the given offset, without changing the current {#pos}.
|
676
|
+
#
|
677
|
+
# @param [String] data
|
678
|
+
# The data to write.
|
679
|
+
#
|
680
|
+
# @param [Integer] offset
|
681
|
+
# The offset to write the data to.
|
682
|
+
#
|
683
|
+
# @return [Integer]
|
684
|
+
# The number of bytes written.
|
685
|
+
#
|
686
|
+
# @see #write
|
687
|
+
#
|
688
|
+
def pwrite(string,offset)
|
689
|
+
old_pos = pos
|
690
|
+
seek(offset)
|
691
|
+
|
692
|
+
bytes_written = write(string)
|
693
|
+
seek(old_pos)
|
694
|
+
return bytes_written
|
695
|
+
end
|
696
|
+
|
697
|
+
#
|
698
|
+
# Writes a byte or a character to the IO stream.
|
699
|
+
#
|
700
|
+
# @param [String, Integer] data
|
701
|
+
# The byte or character to write.
|
702
|
+
#
|
703
|
+
# @return [String, Integer]
|
704
|
+
# The byte or character that was written.
|
705
|
+
#
|
706
|
+
def putc(data)
|
707
|
+
char = case data
|
708
|
+
when String then data.chr
|
709
|
+
else data
|
710
|
+
end
|
711
|
+
|
712
|
+
write(char)
|
713
|
+
return data
|
714
|
+
end
|
715
|
+
|
716
|
+
#
|
717
|
+
# Prints data to the IO stream.
|
718
|
+
#
|
719
|
+
# @param [Array] arguments
|
720
|
+
# The data to print to the IO stream.
|
721
|
+
#
|
722
|
+
# @return [nil]
|
723
|
+
#
|
724
|
+
def print(*arguments)
|
725
|
+
arguments.each { |data| write(data) }
|
726
|
+
return nil
|
727
|
+
end
|
728
|
+
|
729
|
+
#
|
730
|
+
# Prints data with new-line characters to the IO stream.
|
731
|
+
#
|
732
|
+
# @param [Array] arguments
|
733
|
+
# The data to print to the IO stream.
|
734
|
+
#
|
735
|
+
# @return [nil]
|
736
|
+
#
|
737
|
+
def puts(*arguments)
|
738
|
+
arguments.each { |data| write("#{data}#{$/}") }
|
739
|
+
return nil
|
740
|
+
end
|
741
|
+
|
742
|
+
#
|
743
|
+
# Prints a formatted string to the IO stream.
|
744
|
+
#
|
745
|
+
# @param [String] format_string
|
746
|
+
# The format string to format the data.
|
747
|
+
#
|
748
|
+
# @param [Array] arguments
|
749
|
+
# The data to format.
|
750
|
+
#
|
751
|
+
# @return [nil]
|
752
|
+
#
|
753
|
+
def printf(format_string,*arguments)
|
754
|
+
write(format_string % arguments)
|
755
|
+
return nil
|
756
|
+
end
|
757
|
+
|
758
|
+
alias << write
|
759
|
+
|
760
|
+
# The PID associated with the IO stream.
|
761
|
+
#
|
762
|
+
# @return [Integer, nil]
|
763
|
+
# Returns the PID number or `nil`. Returns `nil` by default.
|
764
|
+
attr_reader :pid
|
765
|
+
|
766
|
+
#
|
767
|
+
# @raise [NotImplementedError]
|
768
|
+
# {#stat} is not implemented.
|
769
|
+
#
|
770
|
+
def stat
|
771
|
+
raise(NotImplementedError,"#{self.class}#stat is not implemented")
|
772
|
+
end
|
773
|
+
|
774
|
+
#
|
775
|
+
# Indicates whether the IO stream is associated with a terminal device.
|
776
|
+
#
|
777
|
+
# @return [Boolean]
|
778
|
+
#
|
779
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
780
|
+
#
|
781
|
+
def isatty
|
782
|
+
@tty
|
783
|
+
end
|
784
|
+
|
785
|
+
#
|
786
|
+
# @see #isatty
|
787
|
+
#
|
788
|
+
def tty?
|
789
|
+
@tty
|
790
|
+
end
|
791
|
+
|
792
|
+
#
|
793
|
+
# @param [Integer] new_pos
|
794
|
+
# The desired new position, relative to `whence`.
|
795
|
+
#
|
796
|
+
# @param [File::SEEK_CUR, File::SEEK_DATA, File::SEEK_END, File::SEEK_HOLE, File::SEEK_SET] whence
|
797
|
+
#
|
798
|
+
# @return [0]
|
799
|
+
#
|
800
|
+
def seek(new_pos,whence=SEEK_SET)
|
801
|
+
io_seek(new_pos,whence)
|
802
|
+
io_buffer_clear!
|
803
|
+
return 0
|
804
|
+
end
|
805
|
+
|
806
|
+
#
|
807
|
+
# @see #seek
|
808
|
+
#
|
809
|
+
def sysseek(offset,whence=SEEK_SET)
|
810
|
+
seek(new_pos,whence)
|
811
|
+
end
|
812
|
+
|
813
|
+
#
|
814
|
+
# @see #seek
|
815
|
+
#
|
816
|
+
def pos=(new_pos)
|
817
|
+
seek(new_pos,SEEK_SET)
|
818
|
+
@pos = new_pos
|
819
|
+
end
|
820
|
+
|
821
|
+
#
|
822
|
+
# The current line-number (how many times {#gets} has been called).
|
823
|
+
#
|
824
|
+
# @return [Integer]
|
825
|
+
# The current line-number.
|
826
|
+
#
|
827
|
+
# @raise [IOError]
|
828
|
+
# The stream was not opened for reading.
|
829
|
+
#
|
830
|
+
def lineno
|
831
|
+
unless @read
|
832
|
+
raise(IOError,"not opened for reading")
|
833
|
+
end
|
834
|
+
|
835
|
+
return @lineno
|
836
|
+
end
|
837
|
+
|
838
|
+
#
|
839
|
+
# Manually sets the current line-number.
|
840
|
+
#
|
841
|
+
# @param [Integer] number
|
842
|
+
# The new line-number.
|
843
|
+
#
|
844
|
+
# @return [Integer]
|
845
|
+
# The new line-number.
|
846
|
+
#
|
847
|
+
# @raise [IOError]
|
848
|
+
# The stream was not opened for reading.
|
849
|
+
#
|
850
|
+
def lineno=(number)
|
851
|
+
unless @read
|
852
|
+
raise(IOError,"not opened for reading")
|
853
|
+
end
|
854
|
+
|
855
|
+
return @lineno = number.to_i
|
856
|
+
end
|
857
|
+
|
858
|
+
#
|
859
|
+
# @return [IO]
|
860
|
+
#
|
861
|
+
# @see #seek
|
862
|
+
#
|
863
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
864
|
+
#
|
865
|
+
def rewind
|
866
|
+
seek(0,SEEK_SET)
|
867
|
+
|
868
|
+
@pos = 0
|
869
|
+
@lineno = 0
|
870
|
+
end
|
871
|
+
|
872
|
+
#
|
873
|
+
# @return [IO]
|
874
|
+
#
|
875
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
876
|
+
#
|
877
|
+
def binmode
|
878
|
+
@binmode = true
|
879
|
+
return self
|
880
|
+
end
|
881
|
+
|
882
|
+
#
|
883
|
+
# @return [Boolean]
|
884
|
+
#
|
885
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
886
|
+
#
|
887
|
+
def binmode?
|
888
|
+
@binmode == true
|
889
|
+
end
|
890
|
+
|
891
|
+
# Sets whether the IO stream will be auto-closed when finalized.
|
892
|
+
#
|
893
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
894
|
+
attr_writer :autoclose
|
895
|
+
|
896
|
+
#
|
897
|
+
# @return [true]
|
898
|
+
#
|
899
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
900
|
+
#
|
901
|
+
def autoclose?
|
902
|
+
@autoclose
|
903
|
+
end
|
904
|
+
|
905
|
+
# Sets the close-on-exec flag.
|
906
|
+
#
|
907
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
908
|
+
attr_writer :close_on_exec
|
909
|
+
|
910
|
+
#
|
911
|
+
# Indicates whether the close-on-exec flag is set.
|
912
|
+
#
|
913
|
+
# @return [Boolean]
|
914
|
+
#
|
915
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
916
|
+
#
|
917
|
+
def close_on_exec?
|
918
|
+
@close_on_exec
|
919
|
+
end
|
920
|
+
|
921
|
+
#
|
922
|
+
# @raise [NotImplementedError]
|
923
|
+
# {#ioctl} was not implemented in {FakeIO}.
|
924
|
+
#
|
925
|
+
def ioctl(command,argument)
|
926
|
+
raise(NotImplementedError,"#{self.class}#ioctl was not implemented")
|
927
|
+
end
|
928
|
+
|
929
|
+
#
|
930
|
+
# @raise [NotImplementedError]
|
931
|
+
# {#fcntl} was not implemented in {FakeIO}.
|
932
|
+
#
|
933
|
+
def fcntl(command,argument)
|
934
|
+
raise(NotImplementedError,"#{self.class}#fcntl was not implemented")
|
935
|
+
end
|
936
|
+
|
937
|
+
#
|
938
|
+
# Immediately writes all buffered data.
|
939
|
+
#
|
940
|
+
# @return [0]
|
941
|
+
#
|
942
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
943
|
+
#
|
944
|
+
def fsync
|
945
|
+
flush
|
946
|
+
return 0
|
947
|
+
end
|
948
|
+
|
949
|
+
alias fdatasync fsync
|
950
|
+
|
951
|
+
# The sync flag.
|
952
|
+
#
|
953
|
+
# @return [Boolean]
|
954
|
+
# Returns the sync mode, for compatibility with
|
955
|
+
# [IO](http://rubydoc.info/stdlib/core/IO).
|
956
|
+
attr_accessor :sync
|
957
|
+
|
958
|
+
#
|
959
|
+
# @return [IO]
|
960
|
+
#
|
961
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
962
|
+
#
|
963
|
+
def flush
|
964
|
+
self
|
965
|
+
end
|
966
|
+
|
967
|
+
#
|
968
|
+
# @raise [NotImplementedError]
|
969
|
+
# {#reopen} is not implemented.
|
970
|
+
#
|
971
|
+
def reopen(*arguments)
|
972
|
+
raise(NotImplementedError,"#{self.class}#reopen is not implemented")
|
973
|
+
end
|
974
|
+
|
975
|
+
#
|
976
|
+
# Closes the read end of a duplex IO stream.
|
977
|
+
#
|
978
|
+
def close_read
|
979
|
+
if @write then @read = false
|
980
|
+
else close
|
981
|
+
end
|
982
|
+
|
983
|
+
return nil
|
984
|
+
end
|
985
|
+
|
986
|
+
#
|
987
|
+
# Closes the write end of a duplex IO stream.
|
988
|
+
#
|
989
|
+
def close_write
|
990
|
+
if @read then @write = false
|
991
|
+
else close
|
992
|
+
end
|
993
|
+
|
994
|
+
return nil
|
995
|
+
end
|
996
|
+
|
997
|
+
#
|
998
|
+
# Determines whether the IO stream is closed.
|
999
|
+
#
|
1000
|
+
# @return [Boolean]
|
1001
|
+
# Specifies whether the IO stream has been closed.
|
1002
|
+
#
|
1003
|
+
def closed?
|
1004
|
+
@closed == true
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
#
|
1008
|
+
# Closes the IO stream.
|
1009
|
+
#
|
1010
|
+
def close
|
1011
|
+
io_close
|
1012
|
+
|
1013
|
+
@fd = nil
|
1014
|
+
|
1015
|
+
@read = false
|
1016
|
+
@write = false
|
1017
|
+
@closed = true
|
1018
|
+
return nil
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
#
|
1022
|
+
# The file descriptor
|
1023
|
+
#
|
1024
|
+
# @return [Integer]
|
1025
|
+
#
|
1026
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
1027
|
+
#
|
1028
|
+
def fileno
|
1029
|
+
@fd
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
#
|
1033
|
+
# The file descriptor
|
1034
|
+
#
|
1035
|
+
# @return [Integer]
|
1036
|
+
#
|
1037
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
1038
|
+
#
|
1039
|
+
def to_i
|
1040
|
+
@fd
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
#
|
1044
|
+
# @return [IO]
|
1045
|
+
#
|
1046
|
+
# @note For compatibility with [IO](http://rubydoc.info/stdlib/core/IO).
|
1047
|
+
#
|
1048
|
+
def to_io
|
1049
|
+
self
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
#
|
1053
|
+
# Inspects the IO stream.
|
1054
|
+
#
|
1055
|
+
# @return [String]
|
1056
|
+
# The inspected IO stream.
|
1057
|
+
#
|
1058
|
+
def inspect
|
1059
|
+
"#<#{self.class}: #{@fd.inspect if @fd}>"
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
protected
|
1063
|
+
|
1064
|
+
#
|
1065
|
+
# Opens the IO stream.
|
1066
|
+
#
|
1067
|
+
# @return [IO]
|
1068
|
+
# The opened IO stream.
|
1069
|
+
#
|
1070
|
+
def open
|
1071
|
+
@pos = 0
|
1072
|
+
@lineno = 0
|
1073
|
+
@eof = false
|
1074
|
+
|
1075
|
+
io_buffer_clear!
|
1076
|
+
|
1077
|
+
@fd = io_open
|
1078
|
+
@closed = false
|
1079
|
+
return self
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
#
|
1083
|
+
# @group Abstract Methods
|
1084
|
+
#
|
1085
|
+
|
1086
|
+
#
|
1087
|
+
# Place holder method used to open the IO stream.
|
1088
|
+
#
|
1089
|
+
# @return [fd]
|
1090
|
+
# The abstract file-descriptor that represents the stream.
|
1091
|
+
#
|
1092
|
+
# @abstract
|
1093
|
+
#
|
1094
|
+
def io_open
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
#
|
1098
|
+
# Place holder method used to seek to a position within the IO stream.
|
1099
|
+
#
|
1100
|
+
# @param [Integer] new_pos
|
1101
|
+
# The desired new position, relative to `whence`.
|
1102
|
+
#
|
1103
|
+
# @param [File::SEEK_CUR, File::SEEK_DATA, File::SEEK_END, File::SEEK_HOLE, File::SEEK_SET] whence
|
1104
|
+
#
|
1105
|
+
# @raise [NotImplementedError]
|
1106
|
+
# By default a `NotImplementedError` exception will be raised.
|
1107
|
+
#
|
1108
|
+
# @abstract
|
1109
|
+
#
|
1110
|
+
def io_seek(new_pos,whence)
|
1111
|
+
raise(NotImplementedError,"#{self.class}#io_seek is not implemented")
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
#
|
1115
|
+
# Place holder method used to read a block from the IO stream.
|
1116
|
+
#
|
1117
|
+
# @return [String]
|
1118
|
+
# Available data to be read.
|
1119
|
+
#
|
1120
|
+
# @raise [EOFError]
|
1121
|
+
# The end of the stream has been reached.
|
1122
|
+
#
|
1123
|
+
# @abstract
|
1124
|
+
#
|
1125
|
+
def io_read
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
#
|
1129
|
+
# Place holder method used to write data to the IO stream.
|
1130
|
+
#
|
1131
|
+
# @param [String] data
|
1132
|
+
# The data to write to the IO stream.
|
1133
|
+
#
|
1134
|
+
# @abstract
|
1135
|
+
#
|
1136
|
+
def io_write(data)
|
1137
|
+
0
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
#
|
1141
|
+
# Place holder method used to close the IO stream.
|
1142
|
+
#
|
1143
|
+
# @abstract
|
1144
|
+
#
|
1145
|
+
def io_close
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
private
|
1149
|
+
|
1150
|
+
#
|
1151
|
+
# @group Buffer Methods
|
1152
|
+
#
|
1153
|
+
|
1154
|
+
#
|
1155
|
+
# Clears the read buffer.
|
1156
|
+
#
|
1157
|
+
def io_buffer_clear!
|
1158
|
+
@io_buffer = nil
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
#
|
1162
|
+
# Determines if the read buffer is empty.
|
1163
|
+
#
|
1164
|
+
# @return [Boolean]
|
1165
|
+
# Specifies whether the read buffer is empty.
|
1166
|
+
#
|
1167
|
+
def io_buffer_empty?
|
1168
|
+
@io_buffer.nil?
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
#
|
1172
|
+
# Reads data from the read buffer.
|
1173
|
+
#
|
1174
|
+
# @return [String]
|
1175
|
+
# Data read from the buffer.
|
1176
|
+
#
|
1177
|
+
def io_buffer_read
|
1178
|
+
chunk = @io_buffer
|
1179
|
+
io_buffer_clear!
|
1180
|
+
return chunk
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
#
|
1184
|
+
# Prepends data to the front of the read buffer.
|
1185
|
+
#
|
1186
|
+
# @param [String] data
|
1187
|
+
# The data to prepend.
|
1188
|
+
#
|
1189
|
+
def io_buffer_prepend(data)
|
1190
|
+
@io_buffer ||= if internal_encoding
|
1191
|
+
String.new(encoding: internal_encoding)
|
1192
|
+
else
|
1193
|
+
String.new
|
1194
|
+
end
|
1195
|
+
@io_buffer.insert(0,data.force_encoding(@io_buffer.encoding))
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
#
|
1199
|
+
# Appends data to the read buffer.
|
1200
|
+
#
|
1201
|
+
# @param [String] data
|
1202
|
+
# The data to append.
|
1203
|
+
#
|
1204
|
+
def io_buffer_append(data)
|
1205
|
+
@io_buffer ||= if internal_encoding
|
1206
|
+
String.new(encoding: internal_encoding)
|
1207
|
+
else
|
1208
|
+
String.new
|
1209
|
+
end
|
1210
|
+
@io_buffer << data.force_encoding(@io_buffer.encoding)
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
end
|