class GxG::IO::IO
local input / output objects: Stream, Pipe, File, Device replacement File/Pipe/Socket IO object and methods built upon EM/EM-Synchrony (uses std. IO obj. for low-level work but attempts actual non-blocking) built-in 'non-blocking' does not appear to actually work (looking at you matz) .fd: numeric file descriptor or IO object mode: file mode. a string or an integer opt: hash for specifying mode by name. :mode
Same as mode parameter
:external_encoding
External encoding for the IO. “-” is a synonym for the default external encoding.
:internal_encoding
Internal encoding for the IO. “-” is a synonym for the default internal encoding.
If the value is nil no conversion occurs.
:encoding
Specifies external and internal encodings as “extern:intern”.
:textmode
If the value is truth value, same as “t” in argument mode.
:binmode
If the value is truth value, same as “b” in argument mode.
:autoclose
If the value is false, the fd will be kept open after this IO instance gets finalized.
Public Class Methods
new(*args)
click to toggle source
Calls superclass method
# File lib/gxg/gxg_io.rb, line 1396 def initialize(*args) # Sockets : http://www.tutorialspoint.com/ruby/ruby_socket_programming.htm # io.c : http://rxr.whitequark.org/mri/source/io.c?v=1.9.3#8028 # File Permission Modes : http://www.tutorialspoint.com/ruby/ruby_input_output.htm params = self.process_parameters(*args) mode_string = "" mode_numeric = 0 if params[:file_descriptor] mode_flags = {:binary => ::File::BINARY, :text => ::File::TEXT, :read => ::File::RDONLY, :write => ::File::WRONLY, :readwrite => ::File::RDWR, :create => ::File::CREAT, :overwrite => ::File::TRUNC, :append => ::File::APPEND} modes = GxG::IO::IO::valid_mode_set({:type => :file, :read => true, :write => true, :text => true, :binary => true}) else mode_flags = {:binary => ::IO::BINARY, :text => ::IO::TEXT, :read => ::IO::RDONLY, :write => ::IO::WRONLY, :readwrite => ::IO::RDWR, :create => ::IO::CREAT, :overwrite => ::IO::TRUNC, :append => ::IO::APPEND} modes = GxG::IO::IO::valid_mode_set({:type => :io, :read => true, :write => true, :text => true, :binary => true}) end the_mode_used = nil options = {} # :mode #Same as mode parameter if params[:mode] if params[:mode].index(:binary) if params[:mode].index(:read) if params[:mode].index(:write) mode_string = "binary_readwrite" mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite]) else mode_string = "binary_read" mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read]) end else if params[:mode].index(:write) mode_string = "binary_write" mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write]) end end else if params[:mode].index(:read) if params[:mode].index(:write) mode_string = "text_readwrite" mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite]) else mode_string = "text_read" mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read]) end else if params[:mode].index(:write) mode_string = "text_write" mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write]) end end end if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) ) mode_string << "_overwrite" mode_numeric = (mode_numeric | mode_flags[:overwrite]) else if params[:mode].index(:append) mode_string << "_append" mode_numeric = (mode_numeric | mode_flags[:append]) else if params[:mode].index(:create) mode_string << "_create" mode_numeric = (mode_numeric | mode_flags[:create]) end end end # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary] modes.to_enum.each do |mode_entry| mode_entry[:synonyms].to_enum.each do |the_synonym| if the_synonym.is_a?(Symbol) if the_synonym == mode_string.to_sym the_mode_used = mode_entry break end else if the_synonym.is_a?(Numeric) if (the_synonym & mode_numeric == mode_numeric) the_mode_used = mode_entry break end end end end if the_mode_used break end # end if (the_mode_used) if params[:file_descriptor] # At this point, only fs objects that already exist will be referenced here. if (mode_numeric & mode_flags[:create]) == mode_flags[:create] if the_mode_used[:if_create] options[:mode] = the_mode_used[:if_create].to_i else raise ArgumentError, "you cannot open read-only on a non-existent file system object" end else options[:mode] = the_mode_used[:if_exist].to_i end else # IO-ish object. # When the mode of original IO is read only, the mode cannot be changed to be writable. Similarly, # the mode cannot be changed from write only to readable. When such a change is attempted # the error is raised in different locations according to the platform. if params[:object].is_any?(::IO, GxG::IO::IO) if (params[:object].stat.readable? && (params[:mode].include?(:write) || params[:mode].include?(:readwrite)) && (params[:object].stat.writable? == false)) raise ArgumentError, "you cannot make a read-only IO writable" end if (params[:object].stat.writable? && (params[:mode].include?(:read) || params[:mode].include?(:readwrite)) && (params[:object].stat.readable? == false)) raise ArgumentError, "you cannot make a write-only IO readable" end if (params[:object].stat.binmode? && params[:mode].include?(:text)) raise ArgumentError, "you cannot make a binary IO text-only" end else unless params[:object].is_any?( ::StringIO, GxG::IO::StringIO) raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum" end end # if params[:mode].include?(:binary) # unless params[:object].binmode? # params[:object].binmode # end # end if (mode_numeric & mode_flags[:create]) == mode_flags[:create] if the_mode_used[:if_create] options[:mode] = the_mode_used[:if_create].to_i else raise ArgumentError, "you cannot open read-only on a non-existent file system object" end else options[:mode] = the_mode_used[:if_exist].to_i end end # else modes_list = [] modes.to_enum.each do |item| modes_list << item[:synonyms] end raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}" end end # @conversion_options = {:external => {}, :internal => {}} # if params[:mode].include?(:binary) options[:external_encoding] = ::Encoding::ASCII_8BIT else if params[:external_encoding].is_a?(::Encoding) options[:external_encoding] = params.delete(:external_encoding) #:2nd internal_encoding parameter and 3rd parameter optional hash ignored # if you want the supplied string's encoding - best to simply pass it on the .new method call params. else options[:external_encoding] = ::Encoding.default_external end end # # if external_encoding is BINARY/ASCII_8BIT - just nullify internal_encoding, and skip external/internal conversion option mapping. if params[:mode].include?(:binary) @internal_encoding = nil else if (params[:internal_encoding].is_a?(::Encoding) && params[:internal_encoding] != options[:external_encoding]) @internal_encoding = params.delete(:internal_encoding) else @internal_encoding = ::Encoding.default_internal end # if @internal_encoding if @internal_encoding != options[:external_encoding] if params[:external_conversion] # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding,params[:external_conversion]) else @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding) end # if @internal_encoding == ::Encoding::ASCII_8BIT # if params[:internal_conversion] nl_op = self.newline_option_used(params[:internal_conversion]) conv_options = {} if nl_op conv_options[(nl_op)] = true end @conversion_options[:internal] = conv_options end else if params[:internal_conversion] @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding],params[:internal_conversion]) else @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding]) end end # end end # end # @external_field_separator = nil if params[:external_field_separator] @external_field_separator = params[:external_field_separator] end @external_record_separator = nil if params[:external_record_separator] @external_record_separator = params[:external_record_separator] end @internal_field_separator = nil if params[:internal_field_separator] @internal_field_separator = params[:internal_field_separator] end @internal_record_separator = nil if params[:internal_record_separator] @internal_record_separator = params[:internal_record_separator] end #:autoclose # If the value is false, the fd will be kept open after this IO instance gets finalized. if params[:autoclose] options[:autoclose] = params.delete(:autoclose) end # mode_numeric = options.delete(:mode) if params[:file_descriptor] super(params[:file_descriptor],mode_numeric,options) else super(params[:object],mode_numeric,options) end # unless params[:object].is_any?(::StringIO, GxG::IO::StringIO) self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK) end # self end
pipe(*args)
click to toggle source
Calls superclass method
# File lib/gxg/gxg_io.rb, line 1335 def self.pipe(*args) # Goal: make IO.pipe available on ALL platforms a la false-pipe for code portability (via VFS??). Duplex contains an @input and an @output IO channel # pipes are not 'select' able on Winderz, so a faux-pipe must be shimmed in. if [:kazoo_os].include?(GxG::SYSTEM.platform[:platform]) # TODO: GxG::IO::IO::pipe : provide a ring buffer and return read and write endpoint ios for it where the platform does not support it # See: https://en.wikipedia.org/wiki/Circular_buffer # See also: http://comments.gmane.org/gmane.comp.lang.ruby.io-splice.general/11 # else super(*args) end end
Protected Class Methods
interpret_mode(params={})
click to toggle source
# File lib/gxg/gxg_io.rb, line 1271 def self.interpret_mode(params={}) # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom] # end
valid_mode_set(params={})
click to toggle source
# File lib/gxg/gxg_io.rb, line 1275 def self.valid_mode_set(params={}) # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom] valid_modes = [] valid_flags = [] if params[:binary] if params[:read] if params[:write] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_readwrite] else valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read] end else if params[:write] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_write] end end else if params[:read] if params[:write] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_readwrite] else valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read] end else if params[:write] valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_write] end end end @@valid_modes[(params[:type] || :io)].keys.to_enum.each do |mode_key| @@valid_modes[(params[:type] || :io)][(mode_key)][:synonyms].to_enum.each do |synonym| if synonym.is_a?(Numeric) valid_flags.to_enum.each do |flag| if synonym & flag == flag unless valid_modes.include?(@@valid_modes[(params[:type] || :io)][(mode_key)]) valid_modes << @@valid_modes[(params[:type] || :io)][(mode_key)] end end end end end end # valid_modes end
valid_modes()
click to toggle source
# File lib/gxg/gxg_io.rb, line 1268 def self.valid_modes() @@valid_modes end
Public Instance Methods
<<(*args)
click to toggle source
# File lib/gxg/gxg_io.rb, line 1940 def <<(*args) self.write(*args) self end
external_encoding=(*args)
click to toggle source
Public Instance Methods:
# File lib/gxg/gxg_io.rb, line 1348 def external_encoding=(*args) # unless self.binmode?() if args[0].is_any?(::Encoding, ::NilClass) # if args[0] == ::Encoding::ASCII_8BIT self.binmode() else # old_data = self.string().dup # if args[0].is_a?(::Encoding) self.set_encoding(args[0]) if @internal_encoding if @internal_encoding != self.external_encoding # preserve newline settings nl_op = self.newline_option_used(:external) options = {} if nl_op options[(nl_op)] = true end @conversion_options[:external] = ::String::transcoding_options(self.external_encoding,@internal_encoding,options) end end else self.set_encoding(::Encoding.default_external()) end # convert existing data??? # if self.external_encoding() != old_data.encoding() # # preserve newline settings # nl_op = self.newline_option_used(:external) # options = {} # if nl_op # options[(nl_op)] = true # end # old_data.transcode!(self.external_encoding(),options) # # # self.string.replace(old_data) # end # end # else raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}" end end # end
fcntl(*args)
click to toggle source
portable, cross-platform facade for Fcntl (even under Winderz)
Calls superclass method
# File lib/gxg/gxg_io.rb, line 1631 def fcntl(*args) # Thanks to Jon Cooper for the find at http://www.mail-archive.com/beanstalk-talk@googlegroups.com/msg01092.html #The FD_CLOEXEC flag is part of the POSIX API, which Windows doesn't support, #but it does provide analogous functionality. # #See http://www.perlmonks.org/index.pl?node_id=574349 # #You can implement a patch with https://github.com/jarib/childprocess/ which #appears to provide a consistent facade. # #See #http://rubydoc.info/github/jarib/childprocess/master/ChildProcess#close_on_exec-class_method # #Cheers, #Jon # # Attribution: https://github.com/jarib for Childprocess work. # # For Win32 See: http://rxr.whitequark.org/mri/source/win32/win32.c?v=1.9.3#fcntl # ... and older stuff at : http://rxr.whitequark.org/mri/source/wince/fcntl.h?v=1.8.7 # Ruby General Fcntl Stuff: http://ruby-doc.org/stdlib-1.9.3/libdoc/fcntl/rdoc/Fcntl.html # # ### fcntl commands: # F_DUPFD Integer Positive Integer; -1 on error Find the lowest numbered available file descriptor greater than or equal to arg and make it a copy of the receiver’s file descriptor. If arg is omitted, it is assumed to be equal to the receiver’s file descriptor. # F_GETFD N/A File descriptor flags Retrieves the associated file descriptor flags. Currently, these are either 0 or FD_CLOEXEC. These flags may be set with F_SETFD. # F_GETFL N/A Integer Returns the file status flags, i.e. a bitwise OR of O_APPEND, O_ASYNC, O_DIRECT, etc. O_ACCMODE is a bitmask for extracting the access mode from these flags. # F_GETLK struct flock * N/A The argument describes a lock the caller wishes to place on the file. If this is possible, the l_type field of the struct is set to Fcntl::F_UNLCK; otherwise the struct is updated with details of the current lock holder. # F_SETFD FD_CLOEXEC or 0 0; -1 on error Sets the file descriptor flags to arg.When arg is FD_CLOEXEC, this is equivalent to #close_on_exec=true. # F_SETFL Integer 0; -1 on error Set the file status flags to arg # F_SETLK struct flock * 0; -1 on error. When the struct’s l_type field has the value F_RDLCK or F_WRLCK, acquires the lock; when it has the value F_UNLCK, releases the lock. # F_SETLKW struct flock * 0; -1 on error. Behaves like F_SETLK, except when a conflicting lock is held this call blocks until the lock is released or a signal is caught. # fcntl_modes = {} case GxG::SYSTEM.platform()[:platform] when :windows # unless nil # fcntl_modes[:io] = {} # end # I *think* as of 1.9.2+ winderz now has fcntl support in ruby code, see: win32.c # TODO: test fcntl under Winderz super(*args) else super(*args) end end
read(length=0,outbuffer="")
click to toggle source
Calls superclass method
# File lib/gxg/gxg_io.rb, line 1677 def read(length=0,outbuffer="") if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK if outbuffer.is_a?(::String) outbuffer = self.transcode_to_internal(outbuffer) if self.internal_encoding if outbuffer.encoding != self.internal_encoding() outbuffer.force_encoding(self.internal_encoding()) end end end # # Attribution: MRI 1.9.3 docs. # TODO: research *proper* non-blocking reads and writes #read_nonblock just calls the read(2) system call. It causes all errors the #read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The caller #should care such errors. # #If the exception is Errno::EWOULDBLOCK or Errno::AGAIN, it is extended by #IO::WaitReadable. So IO::WaitReadable can be used to rescue the exceptions for #retrying read_nonblock. # #read_nonblock causes EOFError on EOF. # #If the read byte buffer is not empty, read_nonblock reads from the buffer like #readpartial. In this case, the read(2) system call is not called. # #When read_nonblock raises an exception kind of IO::WaitReadable, read_nonblock #should not be called until io is readable for avoiding busy loop. This can be #done as follows. # # # emulates blocking read (readpartial). # begin # result = io.read_nonblock(maxlen) # rescue IO::WaitReadable # IO.select([io]) # retry # end # #Although IO#read_nonblock doesn't raise IO::WaitWritable. #OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. If IO and SSL #should be used polymorphically, IO::WaitWritable should be rescued too. See #the document of OpenSSL::Buffering#read_nonblock for sample code. # #Note that this method is identical to readpartial except the non-blocking flag #is set. # millisecond: 1/1000th of a second (reminder) read_bytes = 0 # unless IO is some other type, use default buffer size if (self.tty? || self.stat.pipe?) buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i else if self.stat.socket? buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:read][:initial].to_i else # assume file buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i end end # until (read_bytes == length) begin if ((length - read_bytes) < buffer_size) outbuffer << self.transcode_to_internal(read_nonblock((length - read_bytes))) read_bytes += (length - read_bytes) else outbuffer << self.transcode_to_internal(read_nonblock(buffer_size)) read_bytes += buffer_size end # rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable pause begin # about 10 ms selected = GxG::IO::IO::select([self],nil,nil,0.010) until selected pause selected = GxG::IO::IO::select([self],nil,nil,0.010) end rescue Exception pause retry end retry end # pause end # outbuffer else result = self.transcode_to_internal(super(length)) end result end
readpartial(*args)
click to toggle source
# File lib/gxg/gxg_io.rb, line 1772 def readpartial(*args) # args[0] maxlen (Fixnum) # args[1] (optional) buffer_object # Note: use the same approach to pause/retry as IO.read_nonblock. # # Reads at most maxlen bytes from the I/O stream. It blocks only if ios has no data immediately available. It doesn’t block if some data available. # If the optional outbuf argument is present, it must reference a String, which will receive the data. It raises EOFError on end of file. # readpartial is designed for streams such as pipe, socket, tty, etc. It blocks only when no data immediately available. # This means that it blocks only when following all conditions hold: # the byte buffer in the IO object is empty. # the content of the stream is empty. # the stream is not reached to EOF. # When readpartial blocks, it waits data or EOF on the stream. If some data is reached, readpartial returns with the data. If EOF is reached, # readpartial raises EOFError. # When readpartial doesn’t blocks, it returns or raises immediately. If the byte buffer is not empty, it returns the data in the buffer. # Otherwise if the stream has some content, it returns the data in the stream. Otherwise if the stream is reached to EOF, it raises EOFError. # if self.closed_read? raise IOError, "not open for reading" else if self.eof? raise EOFError, "end of stream reached" else result = "" result.force_encoding(self.internal_encoding) data = self.read(*args) if data # non-nil if data.is_a?(::String) # 'blocking' or not? if (self.eof?() || (data && data.to_s.size > 0)) # no pseudo-blocking result << data data = nil else # pseudo-blocking until (self.eof?() || (data && data.to_s.size > 0)) data = self.read(*args) # string, buffer, or nil if data # if data.is_a?(::String) if (data && data.to_s.size > 0) result << data data = nil break else if self.eof?() raise EOFError, "end of stream reached" end end else # buffer result = data data = nil break end else if self.eof?() raise EOFError, "end of stream reached" end end # pause # end end else # buffer result = data end else if self.eof?() raise EOFError, "end of stream reached" end end # if result if result.is_a?(::String) if result.bytesize > 0 result else nil end else # buffer result end else nil end # end end end
write(data="")
click to toggle source
Calls superclass method
# File lib/gxg/gxg_io.rb, line 1868 def write(data="") if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK # on Winderz, just to a buffer sized spoon full until done ?? # written_bytes = self.write_nonblock(data) # TODO: research *proper* non-blocking reads and writes data = self.transcode_to_external(data) length = data.size written_bytes = 0 # unless IO is some other type, use default buffer size if (self.tty? || self.stat.pipe?) buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i else if self.stat.socket? buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:write][:initial].to_i else # assume file buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i end end until (written_bytes == length) # on Winderz, use self.write(data) ?? chunk = "" chunk.force_encoding(data.encoding) if ((written_bytes) + (buffer_size - 1)) > length chunk << data.slice((written_bytes),(length - 1)) else chunk << data.slice((written_bytes),((written_bytes) + (buffer_size - 1))) end # begin # On some platforms such as Windows, write_nonblock is not supported according # to the kind of the IO object. In such cases, write_nonblock raises # Errno::EBADF. # # write_nonblock writes only 65536 bytes and return 65536. # (The pipe size is 65536 bytes on this environment.) # s = "a" * 100000 # p w.write_nonblock(s) #=> 65536 # # # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN). # p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN) # #If the write buffer is not empty, it is flushed at first. # #When write_nonblock raises an exception kind of IO::WaitWritable, #write_nonblock should not be called until io is writable for avoiding busy #loop. This can be done as follows. # # bytes_written = super(data) written_bytes += write_nonblock(chunk).to_i rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN pause begin selected = GxG::IO::IO::select(nil,[self],nil,0.010) until selected pause selected = GxG::IO::IO::select(nil,[self],nil,0.010) end rescue Exception pause retry end retry end # pause end else written_bytes = super(self.transcode_to_external(data)) end written_bytes end