gxg-framework/lib/gxg/gxg_io.rb
2021-11-16 08:41:53 -08:00

2717 lines
No EOL
122 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# IO Support Library Module & IO Classes ############################################
module GxG
module Support
module Library
#
module SocketIO
#
def sendmsg(data="", flags=0, destination_socket_address=nil, *controls)
unless self.instance_variable_defined?(:@remote_closed)
@remote_closed = nil
end
unless self.instance_variable_defined?(:@write_latency)
@write_latency = {:low => nil, :last => nil, :high => nil}
end
unless self.instance_variable_defined?(:@before_send_data)
@before_send_data = nil
end
unless self.instance_variable_defined?(:@after_send_data)
@after_send_data = nil
end
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 ??
if self.closed?()
raise IOError, "attempting to sendmsg on a closed stream"
else
if @remote_closed
raise Errno::EPIPE
else
data = data.to_s
written_bytes = data.bytesize()
# process controls:
if controls.size > 0
if controls[0].is_a?(::Socket::AncillaryData)
controls = controls[0]
else
if controls[0].is_a?(::Hash)
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
else
controls.flatten!
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
end
end
else
raise ArgumentError, "you MUST supply ancillary data as a control"
end
#
if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
whatfor = :socket
else
if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
whatfor = :tcp
else
if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
whatfor = :udp
end
end
end
buffer_size = self.buffer_limits(:out,whatfor)[:initial]
if written_bytes >= buffer_size
passes = self.passes_needed(written_bytes, buffer_size)
else
passes = 1
end
#
total = (written_bytes - 1)
starting = 0
ending = ([written_bytes,buffer_size].min - 1)
#
passes.times do
#
begin
# if remote end closes during write, raise error by setting @remote_closed and breaking loop.
reading = self.latency_reading(self.write_latency()) do
self.sendmsg_nonblock(data.slice_bytes(starting..ending),flags,destination_socket_address,controls)
end
@write_latency = reading[:reading]
#
rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN
pause
begin
selected = ::IO::select(nil,[self],nil,0.010)
until selected
pause
selected = ::IO::select(nil,[self],nil,0.010)
end
rescue Exception
pause
retry
end
retry
rescue ::Errno::EPIPE
@remote_closed=true
break
end
#
starting = ending + 1
ending = (starting + (buffer_size - 1))
if ending > total
ending = ending - (ending - total)
end
end
if @remote_closed
raise Errno::EPIPE
end
written_bytes
end
end
else
if self.closed?()
raise IOError, "attempting to sendmsg on a closed stream"
else
if @remote_closed
raise Errno::EPIPE
else
data = self.transcode_to_external(data)
written_bytes = data.bytesize()
# process controls:
if controls.size > 0
if controls[0].is_a?(::Socket::AncillaryData)
controls = controls[0]
else
if controls[0].is_a?(::Hash)
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
else
controls.flatten!
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
end
end
else
raise ArgumentError, "you MUST supply ancillary data as a control"
end
#
if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
whatfor = :socket
else
if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
whatfor = :tcp
else
if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
whatfor = :udp
end
end
end
buffer_size = self.buffer_limits(:out,whatfor)[:initial]
if written_bytes >= buffer_size
passes = self.passes_needed(written_bytes, buffer_size)
else
passes = 1
end
#
total = (written_bytes - 1)
starting = 0
ending = ([written_bytes,buffer_size].min - 1)
#
passes.times do
# if remote end closes during write, raise error.
reading = self.latency_reading(self.write_latency()) do
self.original_sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true
end
if @remote_closed
break
else
@write_latency = reading[:reading]
end
starting = ending + 1
ending = (starting + (buffer_size - 1))
if ending > total
ending = ending - (ending - total)
end
end
if @remote_closed
raise Errno::EPIPE
end
#
end
written_bytes
end
end
end
#
def send(data="", flags=0, destination_socket_address=nil, *controls)
# for now, flags ignored. LATER: study Socket.send message flags in detail
unless self.instance_variable_defined?(:@remote_closed)
@remote_closed = nil
end
unless self.instance_variable_defined?(:@write_latency)
@write_latency = {:low => nil, :last => nil, :high => nil}
end
unless self.instance_variable_defined?(:@before_send_data)
@before_send_data = nil
end
unless self.instance_variable_defined?(:@after_send_data)
@after_send_data = nil
end
if self.closed?()
raise IOError, "attempting to send on a closed stream"
else
if @remote_closed
raise Errno::EPIPE
else
data = data.to_s
length = 0
if data.bytesize() > 0
length = data.bytesize()
# process controls:
if controls.size > 0
if controls[0].is_a?(::Socket::AncillaryData)
controls = controls[0]
else
if controls[0].is_a?(::Hash)
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
else
controls.flatten!
controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
end
end
else
controls = nil
end
#
if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
whatfor = :socket
else
if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
whatfor = :tcp
else
if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
whatfor = :udp
end
end
end
buffer_size = self.buffer_limits(:out,whatfor)[:initial]
if length >= buffer_size
passes = self.passes_needed(length, buffer_size)
else
passes = 1
end
#
total = (length - 1)
starting = 0
ending = ([length,buffer_size].min - 1)
#
passes.times do
# if remote end closes during write, raise error.
if controls
reading = self.latency_reading(self.write_latency()) do
self.sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true
end
else
if destination_socket_address
reading = self.latency_reading(self.write_latency()) do
self.original_send(data.slice_bytes(starting..ending),flags,destination_socket_address) or @remote_closed=true
end
else
reading = self.latency_reading(self.write_latency()) do
self.original_send(data.slice_bytes(starting..ending),flags) or @remote_closed=true
end
end
end
if @remote_closed
break
else
@write_latency = reading[:reading]
end
starting = ending + 1
ending = (starting + (buffer_size - 1))
if ending > total
ending = ending - (ending - total)
end
end
#
if @remote_closed
raise Errno::EPIPE
end
end
length
end
end
end
#
def recvmsg(max_mesg_length=nil, flags=0, max_control_length=nil, options={})
# for now, flags ignored. LATER: study Socket.recv message flags in detail
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 ??
if self.closed?()
raise IOError, "attempting to recvmsg on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
begin
result = self.recvmsg_nonblock(max_mesg_length, flags, max_control_length, options)
#
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
#
if result.is_a?(::Array)
if result[0].is_a?(::String)
result[0].force_encoding(::Encoding::ASCII_8BIT)
end
end
if @after_receive_data.respond_to?(:call)
@after_receive_data.call(result)
end
end
else
if self.closed?()
raise IOError, "attempting to recvmsg on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
result = self.original_recvmsg(max_mesg_length, flags, max_control_length, options)
if result.is_a?(::Array)
if result[0].is_a?(::String)
result[0].force_encoding(::Encoding::ASCII_8BIT)
end
end
if @after_receive_data.respond_to?(:call)
@after_receive_data.call(result)
end
end
end
result
end
#
def recv(bytecount=nil, flags=0)
# for now, flags ignored. LATER: study Socket.recv message flags in detail
unless self.instance_variable_defined?(:@before_receive_data)
@before_receive_data = nil
end
unless self.instance_variable_defined?(:@after_receive_data)
@after_receive_data = nil
end
unless bytecount.is_a?(::Numeric)
bytecount = self.buffer_limits(:in,:socket)[:initial]
end
unless flags.is_a?(::Numeric)
flags = 0
end
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 ??
#
if self.closed?()
raise IOError, "attempting to recv on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
whatfor = :socket
else
if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
whatfor = :tcp
else
if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
whatfor = :udp
end
end
end
buffer_size = self.buffer_limits(:in,whatfor)[:initial]
unless bytecount.is_a?(::Numeric)
bytecount = buffer_size
end
unless flags.is_a?(::Numeric)
flags = 0
end
# millisecond: 1/1000th of a second (reminder)
read_bytes = 0
result = ""
result.force_encoding(::Encoding::ASCII_8BIT)
chunk = "abc"
#
until (read_bytes == bytecount || chunk.bytesize() == 0)
begin
if ((bytecount - read_bytes) < buffer_size)
chunk = self.recv_nonblock((bytecount - read_bytes),flags).to_s
else
chunk = self.recv_nonblock(buffer_size,flags).to_s
end
read_bytes += chunk.bytesize()
if chunk.bytesize() > 0
result << chunk
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
#
if @after_receive_data.respond_to?(:call)
@after_receive_data.call([result,nil,0,nil])
end
result
end
else
if self.closed?()
raise IOError, "attempting to recv on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
result = self.original_recv(bytecount.to_i,flags).to_s
result.force_encoding(::Encoding::ASCII_8BIT)
if @after_receive_data.respond_to?(:call)
@after_receive_data.call([result,nil,0,nil])
end
end
end
result
end
#
end
#
module SocketIORecvFrom
# Note: Allow aliases or existing recvfrom_nonblock to catch method references.
# Do not define recvfrom_nonblock here.
def recvfrom(max_mesg_length=nil, flags=0)
# for now, flags ignored. LATER: study Socket.recv message flags in detail
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 ??
if self.closed?()
raise IOError, "attempting to recvmsg on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
begin
result = self.recvfrom_nonblock(max_mesg_length, flags)
#
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
#
if result.is_a?(::Array)
if result[0].is_a?(::String)
result[0].force_encoding(::Encoding::ASCII_8BIT)
end
end
if @after_receive_data.respond_to?(:call)
@after_receive_data.call(result)
end
end
else
if self.closed?()
raise IOError, "attempting to recvmsg on a closed stream"
else
if @before_receive_data.respond_to?(:call)
@before_receive_data.call()
end
result = self.original_recvfrom(max_mesg_length, flags)
if result.is_a?(::Array)
if result[0].is_a?(::String)
result[0].force_encoding(::Encoding::ASCII_8BIT)
end
end
if @after_receive_data.respond_to?(:call)
@after_receive_data.call(result)
end
end
end
result
end
#
end
#
module SocketRW
#
def eof?()
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.eof?()
end
#
def pos()
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.pos()
end
#
def pos=(*args)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.pos = args[0]
end
#
def lineno()
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.lineno()
end
#
def lineno=(*args)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.lineno = args[0]
end
#
def rewind()
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
@in_buffer.rewind()
end
#
def getbyte()
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.getbyte()
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
#
def getc(*args)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.getc(*args)
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
#
def gets(*args)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.gets(*args)
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
#
def each_char(&block)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.each_char(&block)
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
#
def each_line(*args, &block)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.each_line(*args, &block)
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
#
def read(*args)
unless self.instance_variable_defined?(:@in_buffer)
@in_buffer = ::StringIO.new("",::IO::RDWR)
@in_buffer.set_encoding(::Encoding::ASCII_8BIT)
end
result = @in_buffer.read(*args)
if @in_buffer.eof?()
@in_buffer.rewind
@in_buffer.string.clear
end
result
end
alias :read_nonblock :read
alias :sysread :read
#
def readpartial(*args)
#
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
#
def write(data="")
data = self.transcode_to_external(data)
written_bytes = data.bytesize()
self.send(data,0)
written_bytes
end
alias :write_nonblock :write
alias :syswrite :write
#
def <<(*args)
self.write(*args)
self
end
#
def ungetbyte()
raise NotImplementedError, "unget operations not supported on this type of object"
end
alias :ungetbytes :ungetbyte
alias :ungetc :ungetbyte
alias :ungets :ungetbyte
#
end
#
module StringIOPrepare
#
def process_params_default_object(the_object)
""
end
def process_params_classes_valid()
[::String]
end
def process_params_classes_error()
"you MUST specify a String"
end
end
#
module IOPrepare
#
def process_params_default_object(the_object=nil)
the_object
end
def process_params_classes_valid()
[::IO, ::GxG::IO::IO, ::StringIO, ::GxG::IO::StringIO, ::Numeric]
end
def process_params_classes_error()
"you MUST specify an IO :object or a :file_descriptor Fixnum"
end
#
end
#
module CommonIOPrepare
#
def process_parameters(*args)
default_object = nil
if args[0].is_a?(Hash)
# GxG style parameters
raw_params = args[0]
unless raw_params[:object].is_any?(self.process_params_classes_valid())
unless raw_params[:file_descriptor].is_any?(self.process_params_classes_valid())
default_object = self.process_params_default_object((raw_params[:object] || raw_params[:file_descriptor]))
if default_object
raw_params[:object] = default_object
else
raise ArgumentError, (self.class::process_params_classes_error())
end
end
end
unless raw_params[:mode].is_a?(Array)
raise ArgumentError, "you MUST specify an Array of :mode flags (as symbols)"
end
raw_params[:mode].to_enum.each do |the_mode_flag|
unless [:read, :write, :readwrite, :create, :overwrite, :truncate, :append, :text, :binary].include?(the_mode_flag)
raw_params[:mode].delete(the_mode_flag)
end
end
unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text))
raw_params[:mode] << :text
end
else
# standard MRI style parameters
raw_params = {:mode => []}
if args[0].is_any?(self.process_params_classes_valid())
if args[0].is_a?(::Numeric)
raw_params[:file_descriptor] = args[0].to_i
else
raw_params[:object] = args[0]
end
else
default_object = self.process_params_default_object(args[0])
if default_object
raw_params[:object] = default_object
else
raise ArgumentError, (self.class::process_params_classes_error())
end
end
if args[2].is_a?(Hash)
if args[2][:mode]
raw_params[:raw_mode] = args[2][:mode]
end
if args[2][:binmode]
raw_params[:mode] << :binary
else
unless raw_params[:mode].index(:text)
raw_params[:mode] << :text
end
end
extern_encode = nil
intern_encode = nil
if args[2][:encoding]
extern_encode = args[2][:encoding]
intern_encode = args[2][:encoding]
end
if args[2][:external_encoding]
unless extern_encode
extern_encode = args[2][:external_encoding]
end
end
if args[2][:internal_encoding]
unless intern_encode
intern_encode = args[2][:internal_encoding]
end
end
if extern_encode
raw_params[:external_encoding] = extern_encode
end
if intern_encode
raw_params[:internal_encoding] = intern_encode
end
if args[2][:autoclose]
raw_params[:autoclose] = args[2][:autoclose]
end
end
if args[1].is_any?(String,Numeric)
if raw_params[:raw_mode]
raise ArgumentError, "you specify :mode as: 2nd parameter (String || Numeric) or an option in 3rd parameter Hash, but not in both"
else
raw_params[:raw_mode] = args[1]
end
if raw_params[:raw_mode].is_a?(String)
mode_parse = raw_params[:raw_mode].split(":")
mode_spec = mode_parse[0].numeric_values()
if mode_spec.is_a?(Hash)
raw_params[:raw_mode] = mode_spec[:integer]
else
raw_params[:raw_mode] = mode_parse[0]
end
#
if mode_parse.size > 1
extern_encode = mode_parse[1]
if mode_parse.size > 2
intern_encode = mode_parse[2]
else
intern_encode = nil
end
else
extern_encode = nil
intern_encode = nil
end
# if specified also in options, it is overwritten here by mode inclusion.
if (extern_encode == intern_encode)
if extern_encode
# raw_params[:encoding] = extern_encode
raw_params[:external_encoding] = extern_encode
raw_params[:internal_encoding] = extern_encode
end
else
if extern_encode
raw_params[:external_encoding] = extern_encode
end
if intern_encode
raw_params[:internal_encoding] = intern_encode
end
end
#
else
# numeric mode arg
raw_params[:raw_mode] = args[1].to_i
end
end
#
unless (raw_params[:object] || raw_params[:file_descriptor])
raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum"
end
#
if raw_params[:raw_mode]
# decipher :raw_mode
if raw_params[:raw_mode].is_a?(Numeric)
# Numeric
if raw_params[:file_descriptor]
if (raw_params[:raw_mode] & ::File::RDWR) == ::File::RDWR
raw_params[:mode] << :read
raw_params[:mode] << :write
else
if (raw_params[:raw_mode] & ::File::WRONLY) == ::File::WRONLY
raw_params[:mode] << :write
else
if (raw_params[:raw_mode] & ::File::RDONLY) == ::File::RDONLY
raw_params[:mode] << :read
end
end
end
if (raw_params[:raw_mode] & ::File::TRUNC) == ::File::TRUNC
raw_params[:mode] << :overwrite
end
if (raw_params[:raw_mode] & ::File::APPEND) == ::File::APPEND
raw_params[:mode] << :append
end
if (raw_params[:raw_mode] & ::File::CREAT) == ::File::CREAT
raw_params[:mode] << :append
end
else
if (raw_params[:raw_mode] & ::IO::RDWR) == ::IO::RDWR
raw_params[:mode] << :read
raw_params[:mode] << :write
else
if (raw_params[:raw_mode] & ::IO::WRONLY) == ::IO::WRONLY
raw_params[:mode] << :write
else
if (raw_params[:raw_mode] & ::IO::RDONLY) == ::IO::RDONLY
raw_params[:mode] << :read
end
end
end
if (raw_params[:raw_mode] & ::IO::TRUNC) == ::IO::TRUNC
raw_params[:mode] << :overwrite
end
if (raw_params[:raw_mode] & ::IO::APPEND) == ::IO::APPEND
raw_params[:mode] << :append
end
if (raw_params[:raw_mode] & ::IO::CREAT) == ::IO::CREAT
raw_params[:mode] << :create
end
end
else
# String
unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text))
if raw_params[:raw_mode].to_s.include?("b")
raw_params[:mode] << :binary
else
raw_params[:mode] << :text
end
end
if raw_params[:raw_mode].to_s.include?("r")
raw_params[:mode] << :read
if raw_params[:raw_mode].to_s.include?("+")
raw_params[:mode] << :write
end
else
if raw_params[:raw_mode].to_s.include?("w")
raw_params[:mode] << :write
raw_params[:mode] << :overwrite
if raw_params[:raw_mode].to_s.include?("+")
raw_params[:mode] << :read
end
else
if raw_params[:raw_mode].to_s.include?("a")
raw_params[:mode] << :write
raw_params[:mode] << :append
if raw_params[:raw_mode].to_s.include?("+")
raw_params[:mode] << :read
end
end
end
end
end
raw_params.delete(:raw_mode)
end
#
end
#
if raw_params[:external_encoding]
unless ::Encoding::constants.include?(raw_params[:external_encoding].to_s.to_sym)
raise ArgumentError, ":external_encoding :#{raw_params[:external_encoding].to_s} unsupported"
end
end
if raw_params[:internal_encoding]
unless ::Encoding::constants.include?(raw_params[:internal_encoding].to_s.to_sym)
raise ArgumentError, ":internal_encoding :#{raw_params[:internal_encoding].to_s} unsupported"
end
end
#
raw_params
end
#
end
#
end
end
end
#
module GxG
module 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.
#
class IO < ::IO
# See : http://pleac.sourceforge.net/pleac_ruby/fileaccess.html
# Also See : http://stackoverflow.com/questions/6701103/understanding-ruby-and-os-i-o-buffering
# And See : http://ruby.runpaint.org/io
#
# Valid mode values
@@valid_modes = {:flags => {:io => {}, :tty => {}, :pipe => {}, :socket => {}, :file => {}}, :io =>{}, :tty => {}, :pipe => {}, :socket => {}, :file => {}}
#
@@valid_modes[:flags][:io][:read] = ::IO::RDONLY
@@valid_modes[:flags][:io][:write] = ::IO::WRONLY
@@valid_modes[:flags][:io][:readwrite] = ::IO::RDWR
@@valid_modes[:flags][:io][:binary] = ::IO::BINARY
# ### FIX : blows up on Windows -- not defined - FOR NOW SYNC = DSYNC
@@valid_modes[:flags][:io][:sync] = ::IO::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
@@valid_modes[:flags][:io][:dsync] = ::IO::DSYNC # only normal data be synchronized after each write operation, not metadata.
@@valid_modes[:flags][:io][:rsync] = ::IO::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
@@valid_modes[:flags][:io][:tty] = ::IO::TTY
@@valid_modes[:flags][:io][:noctty] = ::IO::NOCTTY # If the named file is a terminal device, dont make it the controlling terminal for the process.
@@valid_modes[:flags][:io][:duplex] = ::IO::DUPLEX
@@valid_modes[:flags][:io][:append] = ::IO::APPEND
@@valid_modes[:flags][:io][:create] = ::IO::CREAT
@@valid_modes[:flags][:io][:exclusive] = ::IO::EXCL
@@valid_modes[:flags][:io][:wsplit] = ::IO::WSPLIT
@@valid_modes[:flags][:io][:wsplit_initialized] = ::IO::WSPLIT_INITIALIZED
@@valid_modes[:flags][:io][:trunc] = ::IO::TRUNC
@@valid_modes[:flags][:io][:text] = ::IO::TEXT
# Note: need to verify this int value before using:
@@valid_modes[:flags][:io][:setenc_by_bom] = ::IO::SETENC_BY_BOM
@@valid_modes[:flags][:io][:seek_set] = ::IO::SEEK_SET
@@valid_modes[:flags][:io][:seek_current] = ::IO::SEEK_CUR
@@valid_modes[:flags][:io][:seek_end] = ::IO::SEEK_END
# ### file region locking:
@@valid_modes[:flags][:io][:shared_lock] = ::IO::LOCK_SH # for reading
@@valid_modes[:flags][:io][:exclusive_lock] = ::IO::LOCK_EX # for writing (implies blocking)
@@valid_modes[:flags][:io][:nonblock_lock] = ::IO::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
@@valid_modes[:flags][:io][:unlock] = ::IO::LOCK_UN
#
@@valid_modes[:flags][:io][:nonblocking] = ::IO::NONBLOCK # Neither the open() call, nor any other operation will cause the process to block (sleep) on the I/O. This behavior may be defined only for FIFOs.
@@valid_modes[:flags][:io][:ndelay] = ::IO::NONBLOCK
@@valid_modes[:flags][:io][:nofollow] = ::IO::NOFOLLOW # Do not follow symlinks.
@@valid_modes[:flags][:io][:noaccesstime] = ::IO::NOATIME # Do not update the access time (atime) of the file.
@@valid_modes[:flags][:io][:match_noescape] = ::IO::FNM_NOESCAPE #
@@valid_modes[:flags][:io][:match_pathname] = ::IO::FNM_PATHNAME #
@@valid_modes[:flags][:io][:match_dotmatch] = ::IO::FNM_DOTMATCH #
@@valid_modes[:flags][:io][:match_casefold] = ::IO::FNM_CASEFOLD #
@@valid_modes[:flags][:io][:match_systemcase] = ::IO::FNM_SYSCASE #
# ### file modes
@@valid_modes[:flags][:file][:read] = ::File::RDONLY
@@valid_modes[:flags][:file][:write] = ::File::WRONLY
@@valid_modes[:flags][:file][:readwrite] = ::File::RDWR
@@valid_modes[:flags][:file][:binary] = ::File::BINARY
@@valid_modes[:flags][:file][:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
@@valid_modes[:flags][:file][:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata.
@@valid_modes[:flags][:file][:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
@@valid_modes[:flags][:file][:tty] = ::File::TTY
@@valid_modes[:flags][:file][:noctty] = ::File::NOCTTY
@@valid_modes[:flags][:file][:duplex] = ::File::DUPLEX
@@valid_modes[:flags][:file][:append] = ::File::APPEND
@@valid_modes[:flags][:file][:create] = ::File::CREAT
@@valid_modes[:flags][:file][:exclusive] = ::File::EXCL
@@valid_modes[:flags][:file][:wsplit] = ::File::WSPLIT
@@valid_modes[:flags][:file][:wsplit_initialized] = ::File::WSPLIT_INITIALIZED
@@valid_modes[:flags][:file][:trunc] = ::File::TRUNC
@@valid_modes[:flags][:file][:text] = ::File::TEXT
@@valid_modes[:flags][:file][:setenc_by_bom] = ::File::SETENC_BY_BOM
@@valid_modes[:flags][:file][:seek_set] = ::File::SEEK_SET
@@valid_modes[:flags][:file][:seek_current] = ::File::SEEK_CUR
@@valid_modes[:flags][:file][:seek_end] = ::File::SEEK_END
# ### file region locking:
@@valid_modes[:flags][:file][:shared_lock] = ::File::LOCK_SH # for reading
@@valid_modes[:flags][:file][:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking)
@@valid_modes[:flags][:file][:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
@@valid_modes[:flags][:file][:unlock] = ::File::LOCK_UN
#
@@valid_modes[:flags][:file][:nonblocking] = ::File::NONBLOCK
@@valid_modes[:flags][:file][:ndelay] = ::File::NONBLOCK
@@valid_modes[:flags][:file][:nofollow] = ::File::NOFOLLOW # Do not follow symlinks.
@@valid_modes[:flags][:file][:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file.
@@valid_modes[:flags][:file][:match_noescape] = ::File::FNM_NOESCAPE #
@@valid_modes[:flags][:file][:match_pathname] = ::File::FNM_PATHNAME #
@@valid_modes[:flags][:file][:match_dotmatch] = ::File::FNM_DOTMATCH #
@@valid_modes[:flags][:file][:match_casefold] = ::File::FNM_CASEFOLD #
@@valid_modes[:flags][:file][:match_systemcase] = ::File::FNM_SYSCASE #
# Extended IO mode flags
@@valid_modes[:flags][:io][:write_create] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:create] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:write_trunc] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:trunc])
@@valid_modes[:flags][:io][:write_append] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:append])
@@valid_modes[:flags][:io][:readwrite_create] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:create] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:trunc])
@@valid_modes[:flags][:io][:readwrite_append] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:append])
@@valid_modes[:flags][:io][:binary_read] = (@@valid_modes[:flags][:io][:read] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_write] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_write_create] = (@@valid_modes[:flags][:io][:write_create] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:binary_write_trunc] = (@@valid_modes[:flags][:io][:write_trunc] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_write_append] = (@@valid_modes[:flags][:io][:write_append] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_readwrite] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_readwrite_create] = (@@valid_modes[:flags][:io][:readwrite_create] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:binary_readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite_trunc] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:binary_readwrite_append] = (@@valid_modes[:flags][:io][:readwrite_append] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom])
@@valid_modes[:flags][:io][:text_read] = (@@valid_modes[:flags][:io][:read] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_write] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_write_create] = (@@valid_modes[:flags][:io][:write_create] | @@valid_modes[:flags][:io][:text] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:text_write_trunc] = (@@valid_modes[:flags][:io][:write_trunc] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_write_append] = (@@valid_modes[:flags][:io][:write_append] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_readwrite] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_readwrite_create] = (@@valid_modes[:flags][:io][:readwrite_create] | @@valid_modes[:flags][:io][:text] | @@valid_modes[:flags][:io][:exclusive])
@@valid_modes[:flags][:io][:text_readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite_trunc] | @@valid_modes[:flags][:io][:text])
@@valid_modes[:flags][:io][:text_readwrite_append] = (@@valid_modes[:flags][:io][:readwrite_append] | @@valid_modes[:flags][:io][:text])
# File mode flags
@@valid_modes[:flags][:file][:write_create] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:create] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:write_trunc] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:trunc])
@@valid_modes[:flags][:file][:write_append] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:append])
@@valid_modes[:flags][:file][:readwrite_create] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:create] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:trunc])
@@valid_modes[:flags][:file][:readwrite_append] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:append])
@@valid_modes[:flags][:file][:binary_read] = (@@valid_modes[:flags][:file][:read] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_write] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_write_create] = (@@valid_modes[:flags][:file][:write_create] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:binary_write_trunc] = (@@valid_modes[:flags][:file][:write_trunc] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_write_append] = (@@valid_modes[:flags][:file][:write_append] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_readwrite] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_readwrite_create] = (@@valid_modes[:flags][:file][:readwrite_create] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:binary_readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite_trunc] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:binary_readwrite_append] = (@@valid_modes[:flags][:file][:readwrite_append] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom])
@@valid_modes[:flags][:file][:text_read] = (@@valid_modes[:flags][:file][:read] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_write] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_write_create] = (@@valid_modes[:flags][:file][:write_create] | @@valid_modes[:flags][:file][:text] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:text_write_trunc] = (@@valid_modes[:flags][:file][:write_trunc] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_write_append] = (@@valid_modes[:flags][:file][:write_append] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_readwrite] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_readwrite_create] = (@@valid_modes[:flags][:file][:readwrite_create] | @@valid_modes[:flags][:file][:text] | @@valid_modes[:flags][:file][:exclusive])
@@valid_modes[:flags][:file][:text_readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite_trunc] | @@valid_modes[:flags][:file][:text])
@@valid_modes[:flags][:file][:text_readwrite_append] = (@@valid_modes[:flags][:file][:readwrite_append] | @@valid_modes[:flags][:file][:text])
# IO modes
#Mode | Meaning
#-----+--------------------------------------------------------
#"r" | Read-only, starts at beginning of file (default mode).
#-----+--------------------------------------------------------
#"r+" | Read-write, starts at beginning of file.
#-----+--------------------------------------------------------
#"w" | Write-only, truncates existing file
# | to zero length or creates a new file for writing.
#-----+--------------------------------------------------------
#"w+" | Read-write, truncates existing file to zero length
# | or creates a new file for reading and writing.
#-----+--------------------------------------------------------
#"a" | Write-only, starts at end of file if file exists,
# | otherwise creates a new file for writing.
#-----+--------------------------------------------------------
#"a+" | Read-write, starts at end of file if file exists,
# | otherwise creates a new file for reading and
# | writing.
#-----+--------------------------------------------------------
# "b" | Binary file mode (may appear with
# | any of the key letters listed above).
# | Suppresses EOL <-> CRLF conversion on Windows. And
# | sets external encoding to ASCII-8BIT unless explicitly
# | specified.
#-----+--------------------------------------------------------
# "t" | Text file mode (may appear with
# | any of the key letters listed above except "b").
# Valid IO modes
# Things to remember:
# tty operates over an fs pipe and appears to use the exact same file modes as a file therefore.
# Unresolved Questions:
# 1. :setenc_by_bom : set on io and tty also? does tty have binary mode?
# 2. :setenc_by_bom : only set on Windows? or *any* platform?
# 3. :wsplit & :wsplit_initialize : under what conditions is this actually set to what (io, tty, file), and on what platforms?
# 5. :sync : applied to io, tty, file?
# 6. Are :tty, :pipe, and :socket able to use the *exact* same mode codes, or do they differ, and in what ways?
#
# text io modes
@@valid_modes[:io][:text_read] = {:synonyms => ["r", "rt", "tr", :text_read, @@valid_modes[:flags][:io][:text_read]], :if_exist => @@valid_modes[:flags][:io][:text_read]}
@@valid_modes[:io][:text_readwrite] = {:synonyms => ["r+","r+t", "tr+", :text_readwrite, @@valid_modes[:flags][:io][:text_readwrite]], :if_exist => @@valid_modes[:flags][:io][:text_readwrite], :if_create => (@@valid_modes[:flags][:io][:text_readwrite] | @@valid_modes[:flags][:io][:readwrite_create])}
@@valid_modes[:io][:text_write_overwrite] = {:synonyms => ["w","wt", "tw", :text_write, :text_write_overwrite, :text_write_truncate, @@valid_modes[:flags][:io][:text_write_trunc], @@valid_modes[:flags][:io][:text_write_create],(@@valid_modes[:flags][:io][:text_write_trunc] | @@valid_modes[:flags][:io][:text_write_create])], :if_exist => @@valid_modes[:flags][:io][:text_write_trunc], :if_create => @@valid_modes[:flags][:io][:text_write_create]}
@@valid_modes[:io][:text_readwrite_overwrite] = {:synonyms => ["w+","w+t", "tw+", :text_readwrite_overwrite, :text_readwrite_truncate, @@valid_modes[:flags][:io][:text_readwrite_trunc], @@valid_modes[:flags][:io][:text_readwrite_create], (@@valid_modes[:flags][:io][:text_readwrite_trunc] | @@valid_modes[:flags][:io][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:text_readwrite_trunc], :if_create => @@valid_modes[:flags][:io][:text_readwrite_create]}
@@valid_modes[:io][:text_write_append] = {:synonyms => ["a","at", "ta", :text_write_append, @@valid_modes[:flags][:io][:text_write_append], (@@valid_modes[:flags][:io][:text_write_append] | @@valid_modes[:flags][:io][:text_write_create])], :if_exist => @@valid_modes[:flags][:io][:text_write_append], :if_create => @@valid_modes[:flags][:io][:text_write_create]}
@@valid_modes[:io][:text_readwrite_append] = {:synonyms => ["a+","a+t", "ta+", :text_readwrite_append, @@valid_modes[:flags][:io][:text_readwrite_append], (@@valid_modes[:flags][:io][:text_readwrite_append] | @@valid_modes[:flags][:io][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:text_readwrite_append], :if_create => @@valid_modes[:flags][:io][:text_readwrite_create]}
#
# binary io modes
@@valid_modes[:io][:binary_read] = {:synonyms => ["rb", "br", :binary_read, @@valid_modes[:flags][:io][:binary_read]], :if_exist => @@valid_modes[:flags][:io][:binary_read]}
@@valid_modes[:io][:binary_readwrite] = {:synonyms => ["r+b", "br+", :binary_readwrite, @@valid_modes[:flags][:io][:binary_readwrite]], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite], :if_create => (@@valid_modes[:flags][:io][:binary_readwrite] | @@valid_modes[:flags][:io][:readwrite_create])}
@@valid_modes[:io][:binary_write_overwrite] = {:synonyms => ["wb", "bw", :binary_write, :binary_write_overwrite, :binary_write_truncate, @@valid_modes[:flags][:io][:binary_write_trunc], @@valid_modes[:flags][:io][:binary_write_create], (@@valid_modes[:flags][:io][:binary_write_trunc] | @@valid_modes[:flags][:io][:binary_write_create])], :if_exist => @@valid_modes[:flags][:io][:binary_write_trunc], :if_create => @@valid_modes[:flags][:io][:binary_write_create]}
@@valid_modes[:io][:binary_readwrite_overwrite] = {:synonyms => ["w+b", "bw+", :binary_readwrite_overwrite, :binary_readwrite_truncate, @@valid_modes[:flags][:io][:binary_readwrite_trunc], @@valid_modes[:flags][:io][:binary_readwrite_create], (@@valid_modes[:flags][:io][:binary_readwrite_trunc] | @@valid_modes[:flags][:io][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite_trunc], :if_create => @@valid_modes[:flags][:io][:binary_readwrite_create]}
@@valid_modes[:io][:binary_write_append] = {:synonyms => ["ab", "ba", :binary_write_append, @@valid_modes[:flags][:io][:binary_write_append], (@@valid_modes[:flags][:io][:binary_write_append] | @@valid_modes[:flags][:io][:binary_write_create])], :if_exist => @@valid_modes[:flags][:io][:binary_write_append], :if_create => @@valid_modes[:flags][:io][:binary_write_create]}
@@valid_modes[:io][:binary_readwrite_append] = {:synonyms => ["a+b", "ba+", :binary_readwrite_append, @@valid_modes[:flags][:io][:binary_readwrite_append], (@@valid_modes[:flags][:io][:binary_readwrite_append] | @@valid_modes[:flags][:io][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite_append], :if_create => @@valid_modes[:flags][:io][:binary_readwrite_create]}
#
# tty modes
# is there a 'binary' mode for a tty? seems it would only be text - make sure.
# @@valid_modes[:tty][:read]
#
# pipe modes
#
# io and file new valid modes : [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary]
#
# text file modes
@@valid_modes[:file][:text_read] = {:synonyms => ["r", "rt", "tr", :text_read, @@valid_modes[:flags][:file][:text_read]], :if_exist => @@valid_modes[:flags][:file][:text_read]}
@@valid_modes[:file][:text_readwrite] = {:synonyms => ["r+","r+t", "tr+", :text_readwrite, @@valid_modes[:flags][:file][:text_readwrite]], :if_exist => @@valid_modes[:flags][:file][:text_readwrite], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]}
@@valid_modes[:file][:text_write_overwrite] = {:synonyms => ["w","wt", "tw", :text_write, :text_write_overwrite, :text_write_truncate, @@valid_modes[:flags][:file][:text_write_trunc], @@valid_modes[:flags][:file][:text_write_create],(@@valid_modes[:flags][:file][:text_write_trunc] | @@valid_modes[:flags][:file][:text_write_create])], :if_exist => @@valid_modes[:flags][:file][:text_write_trunc], :if_create => @@valid_modes[:flags][:file][:text_write_create]}
@@valid_modes[:file][:text_readwrite_overwrite] = {:synonyms => ["w+","w+t", "tw+", :text_readwrite_overwrite, :text_readwrite_truncate, @@valid_modes[:flags][:file][:text_readwrite_trunc], @@valid_modes[:flags][:file][:text_readwrite_create], (@@valid_modes[:flags][:file][:text_readwrite_trunc] | @@valid_modes[:flags][:file][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:text_readwrite_trunc], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]}
@@valid_modes[:file][:text_write_append] = {:synonyms => ["a","at", "ta", :text_write_append, @@valid_modes[:flags][:file][:text_write_append], (@@valid_modes[:flags][:file][:text_write_append] | @@valid_modes[:flags][:file][:text_write_create])], :if_exist => @@valid_modes[:flags][:file][:text_write_append], :if_create => @@valid_modes[:flags][:file][:text_write_create]}
@@valid_modes[:file][:text_readwrite_append] = {:synonyms => ["a+","a+t", "ta+", :text_readwrite_append, @@valid_modes[:flags][:file][:text_readwrite_append], (@@valid_modes[:flags][:file][:text_readwrite_append] | @@valid_modes[:flags][:file][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:text_readwrite_append], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]}
#
# binary file modes
@@valid_modes[:file][:binary_read] = {:synonyms => ["rb", "br", :binary_read, @@valid_modes[:flags][:file][:binary_read]], :if_exist => @@valid_modes[:flags][:file][:binary_read]}
@@valid_modes[:file][:binary_readwrite] = {:synonyms => ["r+b", "br+", :binary_readwrite, @@valid_modes[:flags][:file][:binary_readwrite]], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]}
@@valid_modes[:file][:binary_write_overwrite] = {:synonyms => ["wb", "bw", :binary_write, :binary_write_overwrite, :binary_write_truncate, @@valid_modes[:flags][:file][:binary_write_trunc], @@valid_modes[:flags][:file][:binary_write_create], (@@valid_modes[:flags][:file][:binary_write_trunc] | @@valid_modes[:flags][:file][:binary_write_create])], :if_exist => @@valid_modes[:flags][:file][:binary_write_trunc], :if_create => @@valid_modes[:flags][:file][:binary_write_create]}
@@valid_modes[:file][:binary_readwrite_overwrite] = {:synonyms => ["w+b", "bw+", :binary_readwrite_overwrite, :binary_readwrite_truncate, @@valid_modes[:flags][:file][:binary_readwrite_trunc], @@valid_modes[:flags][:file][:binary_readwrite_create], (@@valid_modes[:flags][:file][:binary_readwrite_trunc] | @@valid_modes[:flags][:file][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite_trunc], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]}
@@valid_modes[:file][:binary_write_append] = {:synonyms => ["ab", "ba", :binary_write_append, @@valid_modes[:flags][:file][:binary_write_append], (@@valid_modes[:flags][:file][:binary_write_append] | @@valid_modes[:flags][:file][:binary_write_create])], :if_exist => @@valid_modes[:flags][:file][:binary_write_append], :if_create => @@valid_modes[:flags][:file][:binary_write_create]}
@@valid_modes[:file][:binary_readwrite_append] = {:synonyms => ["a+b", "ba+", :binary_readwrite_append, @@valid_modes[:flags][:file][:binary_readwrite_append], (@@valid_modes[:flags][:file][:binary_readwrite_append] | @@valid_modes[:flags][:file][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite_append], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]}
#
# ###
@@valid_modes.freeze
# ###
protected
#
def self.valid_modes()
@@valid_modes
end
def self.interpret_mode(params={})
# [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom]
#
end
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
#
#
include ::GxG::Support::Library::IOPrepare
include ::GxG::Support::Library::CommonIOPrepare
include ::GxG::Support::Library::Transcoding
#
public
#
alias :closed_write? :closed?
alias :closed_read? :closed?
#
include ::GxG::Support::Library::TranscodingIO
#
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
# Public Instance Methods:
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
#
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
# portable, cross-platform facade for Fcntl (even under Winderz)
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 receivers file descriptor. If arg is omitted, it is assumed to be equal to the receivers 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 structs 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
#
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
#
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 doesnt 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 doesnt 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
#
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
#
def <<(*args)
self.write(*args)
self
end
#
#
end
#
class StringIO < ::StringIO
# Wrapper class around ::StringIO ensuring cooperative processing, and optional in-line auto-transcoding, on most methods
#
protected
#
# Protected Instance Methods
include ::GxG::Support::Library::StringIOPrepare
include ::GxG::Support::Library::CommonIOPrepare
include ::GxG::Support::Library::Transcoding
#
public
# Public class methods
include ::GxG::Support::Library::TranscodingIO
#
def self.open(the_string="",mode=::IO::RDWR,&block)
result = nil
the_io = GxG::IO::StringIO.new(the_string,mode)
#
if block.respond_to?(:call)
result = block.call(the_io)
the_io.close
else
result = the_io
end
#
result
end
#
# Public instance methods
def flags()
result = []
current = self.fcntl(::Fcntl::F_GETFL)
flags = {}
if self.binmode?()
result << :binary
if self.external_encoding == ::Encoding::ASCII_8BIT
result << :setenc_by_bom
end
else
result << :text
result << :wsplit
end
if self.tty?
result << :tty
end
#
#flag[:read] = 0x00000001
#flag[:write] = 0x00000002
#flag[:readwrite] = (flag[:read] | flag[:write])
#flag[:binmode] = 0x00000004
#flag[:readwrite] = (flag[:read] | flag[:write])
flags[:read] = ::Fcntl::O_RDONLY
flags[:write] = ::Fcntl::O_WRONLY
flags[:readwrite] =::Fcntl::O_RDWR
#flags[:binary] = 0x00000004
flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata.
flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
#flags[:tty] = 0x00000010
flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY
flags[:duplex] = 0x00000020
flags[:append] = ::Fcntl::O_APPEND
flags[:create] = ::Fcntl::O_CREAT
flags[:exclusive] = ::Fcntl::O_EXCL
# flags[:wsplit] = 0x00000200
flags[:wsplit_initialized] = 0x00000400
flags[:trunc] = ::Fcntl::O_TRUNC
#flags[:text] = 0x00001000
#flags[:setenc_by_bom] = 0x00100000
flags[:seek_set] = ::File::SEEK_SET
flags[:seek_current] = ::File::SEEK_CUR
flags[:seek_end] = ::File::SEEK_END
# ### file region locking:
flags[:shared_lock] = ::File::LOCK_SH # for reading
flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking)
flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
flags[:unlock] = ::File::LOCK_UN
#
flags[:nonblocking] = ::Fcntl::O_NONBLOCK
flags[:ndelay] = ::Fcntl::O_NDELAY
# unrelated to in-memory IO-like objects
# flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks.
# flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file.
# flags[:match_noescape] = ::File::FNM_NOESCAPE #
# flags[:match_pathname] = ::File::FNM_PATHNAME #
# flags[:match_dotmatch] = ::File::FNM_DOTMATCH #
# flags[:match_casefold] = ::File::FNM_CASEFOLD #
# flags[:match_systemcase] = ::File::FNM_SYSCASE #
#
flags.keys.to_enum.each do |the_flag_key|
if (current & flags[(the_flag_key)]) == flags[(the_flag_key)]
case the_flag_key
when :readwrite
if result.index(:write)
result << :read
else
if result.index(:read)
result << :write
end
end
when :wsplit_initialized
if result.index(:wsplit)
result[(result.index(:wsplit))] = :wsplit_initialized
else
result << :wsplit_initialized
end
else
result << the_flag_key
end
end
end
#
result
end
#
def initialize(*args)
# TODO: override methods to operate in cooperative processing event system
# TODO: provide a REAL external:internal encoding translation aspect (stock version is poorly designed/wrong, it would act *exactly* like a real IO)
# TODO: optimize this class for memory and call efficiency (see Question at super() call)
# LATER: GxG::StringIO : create/learn/whatever an optimized any-encoding-to-any-encoding transcoding/normalization matrix : possibly extend Encoding Class? This should include a conversion newline/field_separator/record_separator for each pair of Encodings.
# Question: is there some possible benefit to using a ByteArray instead of a stock StringIO ?
params = self.process_parameters(*args)
unless params[:mode].size > 0
params[:mode] << :read
params[:mode] << :write
end
if params[:external_encoding] == ::Encoding::ASCII_8BIT
unless params[:mode].include?(:binary)
params[:mode] << :binary
end
end
#
mode_string = ""
mode_numeric = 0
@fcntl_mode = ::Fcntl::O_NONBLOCK
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})
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])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR)
else
mode_string = "binary_read"
mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY)
end
else
if params[:mode].index(:write)
mode_string = "binary_write"
mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY)
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])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR)
else
mode_string = "text_read"
mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY)
end
else
if params[:mode].index(:write)
mode_string = "text_write"
mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY)
end
end
end
if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) )
mode_string << "_overwrite"
mode_numeric = (mode_numeric | mode_flags[:overwrite])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_TRUNC)
else
if params[:mode].index(:append)
mode_string << "_append"
mode_numeric = (mode_numeric | mode_flags[:append])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_APPEND)
else
if params[:mode].index(:create)
mode_string << "_create"
mode_numeric = (mode_numeric | mode_flags[:create])
@fcntl_mode = (@fcntl_mode | ::Fcntl::O_CREAT)
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)
options[:mode] = the_mode_used[:if_exist].to_i
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
#
mode_numeric = options.delete(:mode)
#
@conversion_options = {:external => {}, :internal => {}}
# init-text clobber work-around : defer :write of init-text until the end.
# init_text = params[:object].dup
# params[:object].clear
# FORNOW: just use default behavior
super(params[:object],mode_numeric)
#
if params[:mode].include?(:binary)
self.set_encoding(::Encoding::ASCII_8BIT)
else
if params[:external_encoding].is_a?(::Encoding)
self.set_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
self.set_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)
@internal_encoding = params.delete(:internal_encoding)
else
@internal_encoding = ::Encoding.default_internal
end
#
if @internal_encoding
if @internal_encoding != self.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(self.external_encoding,@internal_encoding,params[:external_conversion])
else
@conversion_options[:external] = ::String::transcode_options(self.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,self.external_encoding,params[:internal_conversion])
else
@conversion_options[:internal] = ::String::transcode_options(@internal_encoding,self.external_encoding)
end
end
#
end
end
#
end
#
@external_field_separator = nil
if params[:external_field_separator]
self.external_field_separator = params[:external_field_separator]
end
@external_record_separator = nil
if params[:external_record_separator]
self.external_record_separator = params[:external_record_separator]
end
@internal_field_separator = nil
if params[:internal_field_separator]
self.external_field_separator = params[:internal_field_separator]
end
@internal_record_separator = nil
if params[:internal_record_separator]
self.external_field_separator = params[:internal_record_separator]
end
#
# init-text clobber workaround : deferred :write
#self.write(init_text)
self
end
#
# :reopen, :string, :string, :lineno, :lineno, :close_read, :close_write, :closed?, :closed_read?, :closed_write?, :eof, :eof?, :fcntl, :flush, :fsync, :pos, :pos, :rewind, :seek, :sync, :sync, :tell, :each, :each_line, :lines, :each_codepoint, :codepoints, :getc, :ungetc, :ungetbyte, :readchar, :getbyte, :readbyte, :gets, :readline, :readlines, :read, :sysread, :readpartial, :read_nonblock, :write, :, :print, :printf, :putc, :puts, :syswrite, :write_nonblock, :isatty, :tty?, :pid, :fileno, :size, :length, :truncate, :external_encoding, :internal_encoding, :set_encoding
#
def inspect()
("<#" << "#{self.class}:#{super().split(" ")[0].split(":").last}")
end
#
def binmode?()
if self.external_encoding == ::Encoding::ASCII_8BIT
true
else
false
end
end
#
def binmode()
if self.binmode?
self
else
@internal_encoding = nil
@conversion_options[:internal] = {}
@conversion_options[:external] = {}
self.set_encoding(::Encoding::ASCII_8BIT)
super()
end
end
#
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
#
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
#
def read(*args)
# read length bytes from StringIO and optionally route to a buffer object
if self.closed_read?
raise IOError, "not open for reading"
else
if args[0].is_a?(Numeric)
# length
data = super(args[0].to_i)
else
# LATER: provide for StringIO buffer size manipulation??? what is default read length??
data = super()
end
if data.is_a?(::String)
data = self.transcode_to_internal(data)
# buffer
if args[1].is_any?(::String, ::IO, ::StringIO, ::GxG::ByteArray)
# check which classes this can use and in what ways.
data.to_enum(:chars).each do |the_character|
args[1] << the_character
end
# return buffer object
args[1]
else
# return string
data
end
else
# return nil
nil
end
#
end
end
alias :read_nonblock :read
alias :sysread :read
#
def write(data="")
# writes from (self.pos) forward : this will clobber any init string that was passed.
written = 0
if self.closed_write?
raise IOError, "not open for writing"
else
if data.is_a?(::String)
data = self.transcode_to_external(data.dup)
else
data = self.transcode_to_external(data)
end
if data.size > 0
written = super(data)
end
end
written
end
alias :write_nonblock :write
alias :syswrite :write
#
def <<(*args)
self.write(*args)
self
end
#
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 doesnt 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 doesnt 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
#
def close()
super()
end
#
def close_read()
super()
end
#
def close_write()
super()
end
#
end
#
class Input < GxG::IO::IO
# ensure mode is read-only, else raise Exception
def initialize(*args)
params = self.process_parameters(*args)
if params[:mode].index(:write)
params[:mode].delete(:write)
end
if params[:mode].index(:readwrite)
params[:mode].delete(:readwrite)
end
super(params)
self
end
end
#
class Output < GxG::IO::IO
# ensure mode is write-only, else raise Exception
def initialize(*args)
params = self.process_parameters(*args)
if params[:mode].index(:read)
params[:mode].delete(:read)
end
if params[:mode].index(:readwrite)
params[:mode].delete(:readwrite)
end
unless params[:mode].index(:write)
params[:mode] << :write
end
super(params)
self
end
end
#
class Duplex < GxG::IO::IO
def initialize(*args)
params = self.process_parameters(*args)
if params[:mode].index(:read)
params[:mode].delete(:read)
end
if params[:mode].index(:write)
params[:mode].delete(:write)
end
unless params[:mode].index(:readwrite)
params[:mode] << :readwrite
end
super(params)
self
end
end
#
class File < GxG::IO::IO
# As of Ruby 1.9.2: if a file is not set to :binary mode, it defaults to :text mode.
# See: http://www.tutorialspoint.com/ruby/ruby_input_output.htm
# <file>.stat table: (mode example : 0100664)
# 0700 rwx mask for owner
# 0400 r for owner
# 0200 w for owner
# 0100 x for owner
# 0070 rwx mask for group
# 0040 r for group
# 0020 w for group
# 0010 x for group
# 0007 rwx mask for other
# 0004 r for other
# 0002 w for other
# 0001 x for other
# 4000 Set user ID on execution
# 2000 Set group ID on execution
# 1000 Save swapped text, even after use
#
def initialize(*args)
if args[0].is_a?(String)
the_path = args[0]
the_mode = (args[1] || "r")
the_permissions = args[2]
the_options = args[3]
else
raise ArgumentError, "filename or path must be passed as a String"
end
#
if the_options
params = self.process_parameters(0,the_mode,the_options)
else
params = self.process_parameters(0,the_mode)
end
#
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})
the_mode_used = nil
mode_string = ""
mode_numeric = 0
#
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])
end
end
# [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary]
modes.to_enum.each do |mode_entry|
if (mode_entry[:synonyms].include?(mode_string.to_sym) && mode_entry[:synonyms].include?(mode_numeric))
the_mode_used = mode_entry
break
end
#
end
if (the_mode_used)
options = {}
the_mode = nil
if params[:external_encoding]
options[:external_encoding] = params.delete(:external_encoding)
end
if params[:internal_encoding]
#If the value is nil no conversion occurs.
if params[:internal_encoding] == params[:external_encoding]
options[:internal_encoding] = nil
else
options[:internal_encoding] = params.delete(:internal_encoding)
end
end
if params[:autoclose]
options[:autoclose] = params.delete(:autoclose)
end
if ::File::exist?(the_path)
the_mode = the_mode_used[:if_exist].to_i
else
if the_mode_used[:if_create]
the_mode = the_mode_used[:if_create].to_i
else
raise ArgumentError, "you cannot operate upon a non-existent file in read-only mode"
end
end
if the_mode
file_descriptor = GxG::IO::IO::sysopen(the_path,the_mode)
else
file_descriptor = nil
end
#
if file_descriptor
#
if ::File::directory?(the_path)
raise ArgumentError, "you cannot operate upon a directory as if it were a file"
else
if the_permissions
super(file_descriptor, the_mode, the_permissions, options)
else
super(file_descriptor, the_mode, nil, options)
end
end
#
else
raise Exception, "unable to create a valid file descriptor with: #{args.inspect}"
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
#
@path = the_path
self
end
#
def to_path()
@path.dup
end
end
# End IO Module
end
# End GxG Module
end