1 # Methods common to IO::Readable, IO::Writable, and IO::Seekable.
4 # Calls both +close_read+ and +close_write+, and raises an exception unless
5 # at least one succeeds. It may be desirable to reimplement this.
7 cr = respond_to?(:close_read)
8 cw = respond_to?(:close_write)
27 raise NotImplementedError, "close_read and close_write unimplemented", caller
31 # Checks both +closed_read+? and +closed_write+?, raising an exception if
32 # neither method exists. This method must be redefined for IO::Readable
33 # and IO::Writable classes unless one of the previously mentioned methods
36 cr = respond_to?(:closed_read?)
37 cw = respond_to?(:closed_write?)
39 return closed_read? && closed_write?
45 raise NotImplementedError, "closed_read? and closed_write? unimplemented", caller
49 # Do not override this method. Instead, use +isatty+.
54 # Always +false+. May be redefined.
59 # Always 0. May be redefined.
64 # Always +true+. May be redefined.
69 # Raises a +NotImplementedError+. May be redefined.
71 raise NotImplementedError, "fcntl not implemented", caller
74 # Always returns the object. May be redefined.
79 [ :fileno, :pid, :sync=, :path ].each do |method|
80 define_method(method) {nil}
85 # The IO::Readable mixin provides classes with several methods for reading
86 # a data stream, similar to those found in the IO class. The +sysread+ and
87 # +close_read+ methods must be defined. Additionally, <tt>closed_read?</tt>,
88 # <tt>closed?</tt>, and <tt>close</tt> are recommended.
90 # IO::Readable provides buffering, but only the absolute minimum needed to
91 # provide a complete implementation. For example, #eof checks for the end of
92 # file by reading and buffering a single byte.
94 # The example below illustrates the use of IO::Readable in a class that
95 # concatenates multiple streams. Note that seeking is not supported but one
96 # can start over with #rewind.
98 # :include:../../test/read_joiner.rb
101 REQUIRED_METHODS = [ :sysread, :close_read ]
106 # This adds three identically named class methods that function like
107 # IO::read, IO::readlines, and IO::foreach. These may be factored into
108 # IO::Openable or removed entirely in a future release.
109 def self.included(base) #:nodoc:
110 unless base.respond_to?(:read)
111 def base.read(name,length=nil,offset=nil)
112 name = [name] unless name.respond_to?(:to_ary)
114 object.seek(offset) if offset
117 object.close_read if object.respond_to?(:close_read)
120 unless base.respond_to?(:readlines)
121 def base.readlines(name,sep_string=$/)
122 name = [name] unless name.respond_to?(:to_ary)
124 object.readlines(sep_string)
126 object.close_read if object.respond_to?(:close_read)
129 unless base.respond_to?(:foreach)
130 def base.foreach(*args,&block)
131 readlines(*args).each(&block)
137 # As with with IO#gets, read and return a line, with lines separated by
138 # +sep_string+. This method may be overridden, although this is not
139 # recommended unless one is intimately familiar with the required behaviors
141 def gets(sep_string=$/)
142 return read(nil) unless sep_string
152 if paragraph && line.empty?
173 if position && @_iom_pos == position
174 raise IOError, "loop encountered", caller
177 line = nil if line == ""
178 if line && respond_to?(:lineno=) && respond_to?(:lineno)
185 # This method returns the number of times IO::Readable#gets was called.
186 # The count is reset on calls to IO::Seekable#rewind.
191 # This method and +lineno+ must be redefined together, or not at all.
196 # Like IO#read, read up to +length+ bytes and return them, optionally
197 # assigning them to +buffer+ as well.
198 def read(length = nil,buffer = "")
199 raise ArgumentError, "negative length #{length} given", caller if (length||0) < 0
200 return "" if length == 0 && (@_iom_buffer.nil? || @_iom_buffer.length > 0)
201 return (length ? nil : "") if eof
202 return "" if length == 0
206 @_iom_buffer << sysread(length-@_iom_buffer.length) if @_iom_buffer.length<length
211 while str = sysread(1024)
218 buffer[0..-1] = @_iom_buffer.slice!(0..(length || 0)-1)
220 @_iom_pos += buffer.length
224 # See IO#getc. Implemented via #read.
231 raise IOError, "unread stream", caller unless @_iom_buffer
232 @_iom_buffer[0,0] = char.chr
239 # tell if respond_to?(:tell)
241 return false unless @_iom_buffer.empty?
253 # Reads a character as with IO#getc, but raises an +EOFError+ on end of
254 # file. Implemented via #getc.
256 getc or raise EOFError, "end of file reached", caller
259 # Reads a character as with #gets, but raises an +EOFError+ on
260 # end of file. Implemented via #gets.
261 def readline(sep_string = $/)
262 gets(sep_string) or raise EOFError, "end of file reached", caller
265 # See IO#readlines. Implemented via #gets.
266 def readlines(sep_string = $/)
269 array << line while line = gets(sep_string)
273 # See IO#each_byte. Implemented via #getc.
281 # See IO#each_line. Implemented via #gets.
282 def each_line(sep_string = $/)
283 while line = gets(sep_string)
291 # See IO#eof?. Implemented via #eof.
298 # The IO::Writable mixin provides classes with several methods for writing
299 # a data stream, similar to those found in the IO class. The methods
300 # +syswrite+ and +close_write+ must be provided. Additionally,
301 # <tt>closed_write?</tt>, <tt>closed?</tt>, and +close+ are recommended.
303 # The following example illustrates the use of IO::Readable, IO::Writable, and
304 # IO::Seekable. It wraps around another stream and applies rot13 to the
307 # :include:../../test/rot13_filter.rb
310 REQUIRED_METHODS = [ :syswrite, :close_write ]
316 @_iom_pos += string.to_s.length
317 @_iom_buffer.slice!(0,string.to_s.length) if @_iom_buffer
318 syswrite(string.to_s)
321 # See IO#<<. Implemented via #write.
323 # write(string.respond_to?(:to_int) ? string.to_int.chr : string)
328 # See IO#print. Implemented via #write.
329 def print(obj = $_,*args)
330 ([obj]+args).each do |line|
331 write(line + ($\||''))
336 # See IO#puts. Implemented via #write.
337 def puts(obj = "\n",*args)
338 [obj,args].flatten.each do |line|
339 line = "nil" if line.nil?
340 write(line.to_s + (line.to_s[-1] == ?\n ? "" : "\n"))
345 # See IO#printf. Implemented via #write.
346 def printf(first="",*rest)
347 write(sprintf(first,*rest))
351 # See IO#putc. Implemented via #write
353 if char.respond_to?(:to_int)
354 write((char.to_int & 0xFF).chr)
355 elsif char.respond_to?(:to_str) && char.to_str.length > 0
356 write(char.to_str[0,1])
358 raise TypeError, "can't convert #{char.class} to Integer", caller
363 # Returns +self+. May be redefined.
370 # The IO::Seekable mixin provides classes with several methods for seeking
371 # within a data stream, similar to those found in the IO class. The method
372 # +sysseek+ must be provided. This method should operate identically to
373 # IO#sysseek, seeking to the desired location and returning the final
374 # absolute offset. One should also consider optimizing for
375 # <tt>sysseek(0,IO::SEEK_CUR)</tt>. This particular call should simply return
376 # the position in the stream.
378 # For an example implementation, see IO::Writable.
381 REQUIRED_METHODS = [ :sysseek ]
384 # See IO#pos. Implemented via #tell.
389 # See IO#pos=. Implemented via #seek.
395 # See IO#rewind. Calls both #seek and IO::Readable#lineno=, if included.
398 self.lineno = 0 if respond_to?(:lineno=)
402 # See IO#seek. This method calls #sysseek and only should be overridden in a
403 # class that is taking responsibility for tracking its own position. If this
404 # is the case, the class should also override #seek, as well as
405 # IO::Readable#read, IO::Readable#ungetc, IO::Readable#getc,
406 # IO::Readable#eof, IO::Writable#write, and IO::Writable#putc, if the
407 # respective modules are included.
408 def seek(amount, whence = IO::SEEK_SET)
409 if whence == IO::SEEK_CUR && @_iom_buffer
410 amount -= @_iom_buffer.length
413 @_iom_pos += @_iom_buffer.length if @_iom_buffer
414 @_iom_buffer = nil if @_iom_buffer
415 @_iom_pos = sysseek(amount, whence)
419 # See IO#tell. See #seek for information on overriding this method.
426 # The IO::Closable mixin works with IO::Readable, IO::Writable, and
427 # IO::Seekable. It provides #closed_read?, #closed_write?, #close_read, and
428 # #close_write methods, and wraps the required methods for the aforementioned
429 # modules such that they raise an exception if applied to a closed object.
430 # You must include _at_ _least_ one of IO::Readable or IO::Writable for this
431 # mixin to be useful.
433 # This mixin is highly experimental. It currently works by trapping
434 # method_added and wrapping the newly defined method.
436 WRAPPED_READ_METHODS = [ :sysread, :getc, :ungetc, :eof, :read, :gets ]
437 WRAPPED_WRITE_METHODS = [ :syswrite, :putc, :write ]
438 WRAPPED_BOTH_METHODS = [ :sysseek, :tell, :seek, :reopen, :pos, :pos= ]
439 WRAPPED_METHODS = WRAPPED_READ_METHODS + WRAPPED_WRITE_METHODS + WRAPPED_BOTH_METHODS
441 # Either #close or #close_read was called.
443 !respond_to?(:sysread) || !!@_closed_read
446 # Either #close or #close_write was called
448 !respond_to?(:syswrite) || !!@_closed_write
451 # Object is closed for both reading and writing
453 closed_read? && closed_write?
456 # Wrap an existing method in with a prerequisite that the file must not be
459 # wrap_method_for_close(:printf, :closed_write?)
460 def self.wrap_method_for_close(method,condition = :closed?) #:nodoc:
461 class_eval(<<-EOF,__FILE__,__LINE__)
462 def with_io_sneakiness_do_#{method}(*args,&block)
463 raise IOError, "stream closed", caller if #{condition}
464 send(:without_io_sneakiness_do_#{method},*args,&block)
467 private "with_io_sneakiness_do_#{method}"
470 WRAPPED_READ_METHODS.each do |method|
471 wrap_method_for_close(method,:closed_read?)
474 WRAPPED_WRITE_METHODS.each do |method|
475 wrap_method_for_close(method,:closed_write?)
478 WRAPPED_BOTH_METHODS.each do |method|
479 wrap_method_for_close(method,:closed?)
482 def with_io_sneakiness_do_reopen(*args, &block) #:nodoc:
485 without_io_sneakiness_do_reopen(*args,&block)
488 # After this method is called, future read operations will fail, and
489 # #closed_read? will read return true. If this method is redefined, it
490 # will be transparently wrapped to preserve this behavior.
491 def close_read(*args,&block)
492 closed_io_error if closed_read?
494 without_io_sneakiness_do_close_read(*args,&block) if respond_to?(:without_io_sneakiness_do_close_read)
495 without_io_sneakiness_do_close(*args,&block) if closed_write? && respond_to?(:without_io_sneakiness_do_close)
498 # After this method is called, future write operations will fail, and
499 # #closed_write? will write return true. If this method is redefined, it
500 # will be transparently wrapped to preserve this behavior.
501 def close_write(*args,&block)
502 closed_io_error if closed_write?
503 @_closed_write = true
504 without_io_sneakiness_do_close_write(*args,&block) if respond_to?(:without_io_sneakiness_do_close_write)
505 without_io_sneakiness_do_close(*args,&block) if closed_read? && respond_to?(:without_io_sneakiness_do_close)
508 # Calls both close_read and close_write.
509 def close(*args,&block)
510 closed_io_error if closed?
511 close_read(*args,&block) unless closed_read?
512 close_write(*args,&block) unless closed_write?
515 [:close_read, :close_write, :close].each do |method|
516 alias_method("with_io_sneakiness_do_#{method}", method)
520 def self.included(base) #:nodoc:
521 # p base.instance_methods.sort
522 WRAPPED_METHODS.each do |method|
523 if method_defined?(method) && !private_method_defined?(without) #&& private_method_defined?(with)
524 base.send(:alias_method, without, method)
525 base.send(:alias_method, method, with)
526 base.send(:private, with, without)
527 base.send(:public, method)
530 ::IO::Sneakiness.extend_into(base)
535 raise IOError, "stream closed", caller[1..-1]
540 # The IO::Openable mixin is a set of _class_ methods concerning opening a
541 # stream. As such, it should be +extend+ed, not +include+d into a class.
544 # include IO::Readable
545 # include IO::Writable
546 # include IO::Seekable
547 # extend IO::Openable
551 # Currently the only method, this method creates a new object, and yields
552 # it if a block is given, in a manner similar to IO::open.
567 # The IO::Editable mixin allows for full emulation of the IO class by
568 # including IO::Readable, IO::Writable, IO::Seekable, and IO::Closable. The
569 # following is a partial implementation of a looped IO class which writes to
572 # :include:../../test/io_loop.rb
574 REQUIRED_METHODS = IO::Readable::REQUIRED_METHODS + IO::Writable::REQUIRED_METHODS + IO::Seekable::REQUIRED_METHODS
582 # Adds an +open+ class method which acts like IO::open.
583 def self.included(base)
584 unless base.respond_to?(:open)
585 base.extend(IO::Openable)
586 [ IO::Readable, IO::Writable, IO::Seekable, IO::Closable ].each do |mod|
587 base.send(:include,mod)
588 # mod.send(:included,base)
595 # We nest these in IO because otherwise, :nodoc: is ignored.
598 MIXINS_VERSION = '0.3'
599 module Sneakiness #:nodoc:
602 with = "with_io_sneakiness_do_#{method}"
603 without = "without_io_sneakiness_do_#{method}"
604 if !@io_sneakiness_disabled && (method_defined?(with) || private_method_defined?(with))
605 alias_method without, method
606 disable_sneakiness do
607 alias_method method, with
608 private with, without
612 method_added_without_io_sneakiness(method)
615 def self.extend_into(base)
616 unless base.respond_to?(:method_added_without_io_sneakiness)
618 alias_method :method_added_without_io_sneakiness, :method_added
625 def disable_sneakiness
626 old_crit = Thread.critical
627 Thread.critical = true
628 @io_sneakiness_disabled = true
631 @io_sneakiness_disabled = false
632 Thread.critical = old_crit if Thread.critical != old_crit
638 module Simple #:nodoc:
642 return "" if length == 0
643 @_simple_buffer ||= ""
644 while @_simple_buffer.length < length && chunk = (readchunk rescue nil)
646 @_simple_buffer << chunk
648 out = @_simple_buffer.slice!(0,length)
650 raise EOFError, "end of file reached", caller