diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index abff1cef0a7e..dfe944e98000 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -486,6 +486,17 @@ describe IO do IO.same_content?(io1, io2).should be_false end end + + it "combines multiple reads using #read_greedy" do + bytes = Bytes.new 7 + + io = SimpleIOMemory.new("Hello World", max_read: 2) + io.read_greedy(bytes).should eq(7) + bytes.should eq("Hello W".to_slice) + io.read_greedy(bytes).should eq(4) + bytes[0, 4].should eq("orld".to_slice) + io.read_greedy(bytes).should eq(0) + end end describe "write operations" do diff --git a/src/io.cr b/src/io.cr index 4b94d2f29baa..ea13b4204b34 100644 --- a/src/io.cr +++ b/src/io.cr @@ -523,6 +523,9 @@ abstract class IO # slice # => Bytes[49, 50, 51, 52, 53] # io.read_fully(slice) # raises IO::EOFError # ``` + # + # `#read_greedy` also tries to fill the entire buffer if possible, + # but still allows the partially filled slice to be used if an early EOF was reached. def read_fully(slice : Bytes) : Int32 read_fully?(slice) || raise(EOFError.new) end @@ -538,11 +541,34 @@ abstract class IO # slice # => Bytes[49, 50, 51, 52, 53] # io.read_fully?(slice) # => nil # ``` + # + # `#read_greedy` also tries to fill the entire buffer if possible, + # but still allows the partially filled slice to be used if an early EOF was reached. def read_fully?(slice : Bytes) : Int32? + count = read_greedy(slice) + return nil if count != slice.size + count + end + + # Similar to `#read`, but with the additional guarantee that either + # the buffer will be entirely filled or the EOF will be reached while trying. + # + # Calling this method may result in multiple calls to `#read` if necessary. + # + # ``` + # io = IO::Memory.new "123451234" + # slice = Bytes.new(5) + # io.read_greedy(slice) # => 5 + # slice # => Bytes[49, 50, 51, 52, 53] + # io.read_greedy(slice) # => 4 + # ``` + # + # `#read_fully` and `#read_fully?` also try to fill the entire buffer but error on unexpected EOF. + def read_greedy(slice : Bytes) : Int32 count = slice.size while slice.size > 0 - read_bytes = read slice - return nil if read_bytes == 0 + read_bytes = read(slice) + return count &- slice.size if read_bytes == 0 slice += read_bytes end count