# An `IO` that wraps another `IO`, setting a limit for the number of bytes that can be read. # # ``` # io = IO::Memory.new "abcde" # sized = IO::Sized.new(io, read_size: 3) # # sized.gets_to_end # => "abc" # sized.gets_to_end # => "" # io.gets_to_end # => "de" # ``` class IO::Sized < IO # If `#sync_close?` is `true`, closing this `IO` will close the underlying `IO`. property? sync_close : Bool # The number of remaining bytes to be read. property read_remaining : UInt64 getter? closed : Bool # Creates a new `IO::Sized` which wraps *io*, and can read a maximum of # *read_size* bytes. If *sync_close* is set, calling `#close` calls # `#close` on the underlying `IO`. def initialize(@io : IO, read_size : Int, @sync_close = false) raise ArgumentError.new "Negative read_size" if read_size < 0 @closed = false @read_remaining = read_size.to_u64 end def read(slice : Bytes) : Int32 check_open count = {slice.size.to_u64, @read_remaining}.min bytes_read = @io.read(slice[0, count]).to_i32 @read_remaining -= bytes_read bytes_read end def read_byte : UInt8? check_open if @read_remaining > 0 byte = @io.read_byte @read_remaining -= 1 if byte byte else nil end end def peek : Bytes? check_open return Bytes.empty if @read_remaining == 0 # EOF peek = @io.peek return nil unless peek if @read_remaining < peek.size peek = peek[0, @read_remaining] end peek end def skip(bytes_count) : Nil check_open if bytes_count <= @read_remaining @io.skip(bytes_count) @read_remaining -= bytes_count else raise IO::EOFError.new end end def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to IO::Sized" end def close : Nil return if @closed @closed = true @io.close if @sync_close end end