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