lib/lightio/library/io.rb in lightio-0.4.2 vs lib/lightio/library/io.rb in lightio-0.4.3

- old
+ new

@@ -1,5 +1,7 @@ +require 'io/wait' + module LightIO::Library class IO include Base include LightIO::Wrap::IOWrapper @@ -17,69 +19,81 @@ base.send(:wrap_blocking_methods, :read, :write) base.send(:alias_method, :<<, :write) end end + def lightio_initialize + @readbuf = StringIO.new + @readbuf.set_encoding(@obj.external_encoding) if @obj.respond_to?(:external_encoding) + @eof = nil + @seek = 0 + end + def wait(timeout = nil, mode = :read) - io_watcher.wait(timeout, mode) && self + # avoid wait if can immediately return + wait_result = if RUBY_VERSION < '2.4' + if mode == :read + @obj.wait_readable(0) + elsif mode == :write + @obj.wait_writable(0) + end + else + @obj.wait(0, mode) + end + (wait_result || io_watcher.wait(timeout, mode)) && self end def wait_readable(timeout = nil) wait(timeout, :read) && self end def wait_writable(timeout = nil) wait(timeout, :write) && self end - def read(length=nil, outbuf=nil) - raise ArgumentError, "negative length #{length} given" if length && length < 0 - (outbuf ||= "").clear - loop do - readlen = length.nil? ? 4096 : length - outbuf.size - if (data = wait_nonblock(:read_nonblock, readlen)) - outbuf << data - if length == outbuf.size - return outbuf - end - else - return outbuf.empty? && length ? nil : outbuf - end + def read(length = nil, outbuf = nil) + while !fill_read_buf && (length.nil? || length > @readbuf.length - @readbuf.pos) + wait_readable end + @readbuf.read(length, outbuf) end - def readpartial(maxlen, outbuf=nil) - (outbuf ||= "").clear - if (data = wait_nonblock(:read_nonblock, maxlen)) - outbuf << data - else - raise EOFError, 'end of file reached' + def readpartial(maxlen, outbuf = nil) + raise ArgumentError, "negative length #{maxlen} given" if maxlen < 0 + fill_read_buf + while @readbuf.eof? && !io_eof? + wait_readable + fill_read_buf end - outbuf + @readbuf.readpartial(maxlen, outbuf) end def getbyte read(1) end def getc - wait_readable - @obj.getc + fill_read_buf + until (c = @readbuf.getc) + return nil if nonblock_eof? + wait_readable + fill_read_buf + end + c end def readline(*args) line = gets(*args) raise EOFError, 'end of file reached' if line.nil? line end def readlines(*args) - result = [] - until eof? - result << readline(*args) + until fill_read_buf + wait_readable end - result + @readbuf.readlines(*args) end def readchar c = getc raise EOFError, 'end of file reached' if c.nil? @@ -91,33 +105,36 @@ raise EOFError, 'end of file reached' if b.nil? b end def eof - wait_readable - @obj.eof + # until eof have a value + fill_read_buf + while @readbuf.eof? && @eof.nil? + wait_readable + fill_read_buf + end + nonblock_eof? end alias eof? eof def gets(*args) raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2)" if args.size > 2 sep = $/ if args[0].is_a?(Numeric) - limit = args.shift + limit = args[0] else - sep = args.shift if args.size > 0 - limit = args.shift if args.first.is_a?(Numeric) + sep = args[0] if args.size > 0 + limit = args[1] if args[1].is_a?(Numeric) end - s = '' - while (c = getbyte) - s << c - break if limit && s.size == limit - break if c == sep + until fill_read_buf + break if limit && limit <= @readbuf.length + break if sep && @readbuf.string.index(sep) + wait_readable end - s = nil if s.empty? - $_ = s + @readbuf.gets(*args) end def print(*obj) obj.each do |s| write(s) @@ -133,14 +150,82 @@ write(s) write($/) end end + def flush + @obj.flush + self + end + def close(*args) # close watcher before io closed io_watcher.close @obj.close end + + private + + def nonblock_eof? + @readbuf.eof? && io_eof? + end + + def io_eof? + @eof + end + + BUF_CHUNK_SIZE = 1024 * 16 + + def fill_read_buf + return true if @eof + while (data = @obj.read_nonblock(BUF_CHUNK_SIZE, exception: false)) + case data + when :wait_readable, :wait_writable + # set eof to unknown(nil) + @eof = nil + return nil + else + # set eof to false + @eof = false if @eof.nil? + @readbuf.string << data + end + end + # set eof to true + @eof = true + end + end + + def set_encoding(*args) + @readbuf.set_encoding(*args) + super(*args) + end + + def lineno + @readbuf.lineno + end + + def lineno= no + @readbuf.lineno = no + end + + def rewind + # clear buf if seek offset is not zero + unless @seek.zero? + @seek = 0 + @readbuf.string.clear + end + @readbuf.rewind + end + + def seek(*args) + @readbuf.string.clear + @seek = args[0] + @obj.seek(*args) + end + + def binmode + @obj.binmode + self end prepend IOMethods end end