require "c/stdio" require "c/errno" {% unless flag?(:win32) %} require "c/unistd" {% end %} # The `IO` class is the basis for all input and output in Crystal. # # This class is inherited by types like `File`, `Socket` and `IO::Memory` and # provides many useful methods for reading from and writing to an IO, like `print`, `puts`, # `gets` and `printf`. # # The only requirement for a type including the `IO` module is to define # these two methods: # # * `read(slice : Bytes)`: read at most *slice.size* bytes from IO into *slice* and return the number of bytes read # * `write(slice : Bytes)`: write the whole *slice* into the IO # # For example, this is a simple `IO` on top of a `Bytes`: # # ``` # class SimpleSliceIO < IO # def initialize(@slice : Bytes) # end # # def read(slice : Bytes) # slice.size.times { |i| slice[i] = @slice[i] } # @slice += slice.size # slice.size # end # # def write(slice : Bytes) : Nil # slice.size.times { |i| @slice[i] = slice[i] } # @slice += slice.size # end # end # # slice = Slice.new(9) { |i| ('a'.ord + i).to_u8 } # String.new(slice) # => "abcdefghi" # # io = SimpleSliceIO.new(slice) # io.gets(3) # => "abc" # io.print "xyz" # String.new(slice) # => "abcxyzghi" # ``` # # ### Encoding # # An `IO` can be set an encoding with the `#set_encoding` method. When this is # set, all string operations (`gets`, `gets_to_end`, `read_char`, `<<`, `print`, `puts` # `printf`) will write in the given encoding, and read from the given encoding. # Byte operations (`read`, `write`, `read_byte`, `write_byte`, `getb_to_end`) never do # encoding/decoding operations. # # If an encoding is not set, the default one is UTF-8. # # Mixing string and byte operations might not give correct results and should be # avoided, as string operations might need to read extra bytes in order to get characters # in the given encoding. abstract class IO # Default size used for generic stream buffers. DEFAULT_BUFFER_SIZE = 32768 # Argument to a `seek` operation. enum Seek # Seeks to an absolute location Set = 0 # Seeks to a location relative to the current location # in the stream Current = 1 # Seeks to a location relative to the end of the stream # (you probably want a negative value for the amount) End = 2 end @encoding : EncodingOptions? @encoder : Encoder? @decoder : Decoder? # Reads at most *slice.size* bytes from this `IO` into *slice*. # Returns the number of bytes read, which is 0 if and only if there is no # more data to read (so checking for 0 is the way to detect end of file). # # ``` # io = IO::Memory.new "hello" # slice = Bytes.new(4) # io.read(slice) # => 4 # slice # => Bytes[104, 101, 108, 108] # io.read(slice) # => 1 # slice # => Bytes[111, 101, 108, 108] # io.read(slice) # => 0 # ``` abstract def read(slice : Bytes) # Writes the contents of *slice* into this `IO`. # # ``` # io = IO::Memory.new # slice = Bytes.new(4) { |i| ('a'.ord + i).to_u8 } # io.write(slice) # io.to_s # => "abcd" # ``` abstract def write(slice : Bytes) : Nil # Closes this `IO`. # # `IO` defines this is a no-op method, but including types may override. def close end # Returns `true` if this `IO` is closed. # # `IO` defines returns `false`, but including types may override. def closed? : Bool false end protected def check_open raise IO::Error.new "Closed stream" if closed? end # Flushes buffered data, if any. # # `IO` defines this is a no-op method, but including types may override. def flush end # Creates a pair of pipe endpoints (connected to each other) # and returns them as a two-element `Tuple`. # # ``` # reader, writer = IO.pipe # writer.puts "hello" # writer.puts "world" # reader.gets # => "hello" # reader.gets # => "world" # ``` def self.pipe(read_blocking : Bool? = nil, write_blocking : Bool? = nil) : {IO::FileDescriptor, IO::FileDescriptor} r, w = Crystal::EventLoop.current.pipe(read_blocking, write_blocking) w.sync = true {r, w} end # Creates a pair of pipe endpoints (connected to each other) and passes them # to the given block. Both endpoints are closed after the block. # # ``` # IO.pipe do |reader, writer| # writer.puts "hello" # writer.puts "world" # reader.gets # => "hello" # reader.gets # => "world" # end # ``` def self.pipe(read_blocking = nil, write_blocking = nil, &) r, w = IO.pipe(read_blocking, write_blocking) begin yield r, w ensure w.flush r.close w.close end end # Writes the given object into this `IO`. # This ends up calling `to_s(io)` on the object. # # ``` # io = IO::Memory.new # io << 1 # io << '-' # io << "Crystal" # io.to_s # => "1-Crystal" # ``` def <<(obj : _) : self obj.to_s self self end # Same as `<<`. # # ``` # io = IO::Memory.new # io.print 1 # io.print '-' # io.print "Crystal" # io.to_s # => "1-Crystal" # ``` def print(obj : _) : Nil self << obj nil end # Writes the given objects into this `IO` by invoking `to_s(io)` # on each of the objects. # # ``` # io = IO::Memory.new # io.print 1, '-', "Crystal" # io.to_s # => "1-Crystal" # ``` def print(*objects : _) : Nil objects.each do |obj| print obj end nil end # Writes *string* to this `IO`, followed by a newline character # unless the string already ends with one. # # ``` # io = IO::Memory.new # io.puts "hello\n" # io.puts "world" # io.to_s # => "hello\nworld\n" # ``` def puts(string : String) : Nil self << string puts unless string.ends_with?('\n') nil end # Writes *obj* to this `IO`, followed by a newline character. # # ``` # io = IO::Memory.new # io.puts 1 # io.puts "Crystal" # io.to_s # => "1\nCrystal\n" # ``` def puts(obj : _) : Nil self << obj puts end # Writes a newline character. # # ``` # io = IO::Memory.new # io.puts # io.to_s # => "\n" # ``` def puts : Nil print '\n' nil end # Writes *objects* to this `IO`, each followed by a newline character unless # the object is a `String` and already ends with a newline. # # ``` # io = IO::Memory.new # io.puts 1, '-', "Crystal" # io.to_s # => "1\n-\nCrystal\n" # ``` def puts(*objects : _) : Nil objects.each do |obj| puts obj end nil end # Writes a formatted string to this IO. # For details on the format string, see top-level `::printf`. def printf(format_string : String, *args) : Nil printf format_string, args end # :ditto: def printf(format_string : String, args : Array | Tuple) : Nil String::Formatter(typeof(args)).new(format_string, args, self).format end # Reads a single byte from this `IO`. Returns `nil` if there is no more # data to read. # # ``` # io = IO::Memory.new "a" # io.read_byte # => 97 # io.read_byte # => nil # ``` def read_byte : UInt8? byte = uninitialized UInt8 if read(Slice.new(pointerof(byte), 1)) == 1 byte else nil end end # Reads a single `Char` from this `IO`. Returns `nil` if there is no # more data to read. # # ``` # io = IO::Memory.new "あ" # io.read_char # => 'あ' # io.read_char # => nil # ``` def read_char : Char? peek = self.peek unless decoder info = read_char_with_bytesize(peek) info ? info[0] : nil end # :nodoc: # See also: `Char::Reader#decode_char_at`. private def read_char_with_bytesize(peek = nil) first = peek_or_read_utf8(peek, 0) return nil unless first first = first.to_u32 if first < 0x80 return first.unsafe_chr, 1 end if first < 0xc2 raise InvalidByteSequenceError.new("Unexpected byte 0x#{first.to_s(16)} in UTF-8 byte sequence") end second = peek_or_read_utf8_masked(peek, 1) if first < 0xe0 return ((first << 6) &+ (second &- 0x3080)).unsafe_chr, 2 end third = peek_or_read_utf8_masked(peek, 2) if first < 0xf0 if first == 0xe0 && second < 0xa0 raise InvalidByteSequenceError.new("Overlong UTF-8 encoding") end if first == 0xed && second >= 0xa0 raise InvalidByteSequenceError.new("Invalid UTF-8 codepoint") end return ((first << 12) &+ (second << 6) &+ (third &- 0xE2080)).unsafe_chr, 3 end if first < 0xf5 if first == 0xf0 && second < 0x90 raise InvalidByteSequenceError.new("Overlong UTF-8 encoding") end if first == 0xf4 && second >= 0x90 raise InvalidByteSequenceError.new("Invalid UTF-8 codepoint") end fourth = peek_or_read_utf8_masked(peek, 3) return ((first << 18) &+ (second << 12) &+ (third << 6) &+ (fourth &- 0x3C82080)).unsafe_chr, 4 end raise InvalidByteSequenceError.new("Unexpected byte 0x#{first.to_s(16)} in UTF-8 byte sequence") end private def peek_or_read_utf8(peek, index) if peek && (byte = peek[index]?) skip(1) byte else read_utf8_byte end end private def peek_or_read_utf8_masked(peek, index) byte = peek_or_read_utf8(peek, index) || raise InvalidByteSequenceError.new("Incomplete UTF-8 byte sequence") if (byte & 0xc0) != 0x80 raise InvalidByteSequenceError.new("Unexpected continuation byte 0x#{byte.to_s(16)} in UTF-8 byte sequence") end byte.to_u32 end # Reads a single decoded UTF-8 byte from this `IO`. # Returns `nil` if there is no more data to read. # # If no encoding is set, this is the same as `#read_byte`. # # ``` # bytes = "你".encode("GB2312") # => Bytes[196, 227] # # io = IO::Memory.new(bytes) # io.set_encoding("GB2312") # io.read_utf8_byte # => 228 # io.read_utf8_byte # => 189 # io.read_utf8_byte # => 160 # io.read_utf8_byte # => nil # # "你".bytes # => [228, 189, 160] # ``` def read_utf8_byte : UInt8? if decoder = decoder() decoder.read_byte(self) else read_byte end end # Reads UTF-8 decoded bytes into the given *slice*. # Returns the number of UTF-8 bytes read. # # If no encoding is set, this is the same as `#read(slice)`. # # ``` # bytes = "你".encode("GB2312") # => Bytes[196, 227] # # io = IO::Memory.new(bytes) # io.set_encoding("GB2312") # # buffer = uninitialized UInt8[1024] # bytes_read = io.read_utf8(buffer.to_slice) # => 3 # buffer.to_slice[0, bytes_read] # => Bytes[228, 189, 160] # # "你".bytes # => [228, 189, 160] # ``` def read_utf8(slice : Bytes) : Int32 if decoder = decoder() decoder.read_utf8(self, slice) else read(slice) end end # Reads an UTF-8 encoded string of exactly *bytesize* bytes. # Raises `EOFError` if there are not enough bytes to build # the string. # # ``` # io = IO::Memory.new("hello world") # io.read_string(5) # => "hello" # io.read_string(1) # => " " # io.read_string(6) # raises IO::EOFError # ``` def read_string(bytesize : Int) : String return "" if bytesize == 0 String.new(bytesize) do |ptr| if decoder = decoder() read = decoder.read_utf8(self, Slice.new(ptr, bytesize)) if read != bytesize raise IO::EOFError.new end else read_fully(Slice.new(ptr, bytesize)) end {bytesize, 0} end end # Peeks into this IO, if possible. # # It returns: # - `nil` if this IO isn't peekable at this moment or at all # - an empty slice if it is, but EOF was reached # - a non-empty slice if some data can be peeked # # The returned bytes are only valid data until a next call # to any method that reads from this IO is invoked. # # By default this method returns `nil`, but IO implementations # that provide buffering or wrap other IOs should override # this method. def peek : Bytes? nil end # Writes the contents of *slice*, interpreted as a sequence of UTF-8 or ASCII # characters, into this `IO`. The contents are transcoded into this `IO`'s # current encoding. # # ``` # bytes = "你".to_slice # => Bytes[228, 189, 160] # # io = IO::Memory.new # io.set_encoding("GB2312") # io.write_string(bytes) # io.to_slice # => Bytes[196, 227] # # "你".encode("GB2312") # => Bytes[196, 227] # ``` def write_string(slice : Bytes) : Nil if encoder = encoder() encoder.write(self, slice) else write(slice) end nil end # :ditto: @[Deprecated("Use `#write_string` instead.")] def write_utf8(slice : Bytes) : Nil write_string(slice) end private def encoder if encoding = @encoding @encoder ||= Encoder.new(encoding) else nil end end private def decoder if encoding = @encoding @decoder ||= Decoder.new(encoding) else nil end end # Tries to read exactly `slice.size` bytes from this `IO` into *slice*. # Raises `EOFError` if there aren't `slice.size` bytes of data. # # ``` # io = IO::Memory.new "123451234" # slice = Bytes.new(5) # io.read_fully(slice) # => 5 # 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 # Tries to read exactly `slice.size` bytes from this `IO` into *slice*. # Returns `nil` if there aren't `slice.size` bytes of data, otherwise # returns the number of bytes read. # # ``` # io = IO::Memory.new "123451234" # slice = Bytes.new(5) # io.read_fully?(slice) # => 5 # 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 count &- slice.size if read_bytes == 0 slice += read_bytes end count end # Reads the rest of this `IO` data as a `String`. # # ``` # io = IO::Memory.new "hello world" # io.gets_to_end # => "hello world" # io.gets_to_end # => "" # ``` def gets_to_end : String String.build do |str| if decoder = decoder() while true decoder.read(self) break if decoder.out_slice.empty? decoder.write(str) end else IO.copy(self, str) end end end # Reads the rest of this `IO` data as a writable `Bytes`. # # ``` # io = IO::Memory.new Bytes[0, 1, 3, 6, 10, 15] # io.getb_to_end # => Bytes[0, 1, 3, 6, 10, 15] # io.getb_to_end # => Bytes[] # ``` def getb_to_end : Bytes io = IO::Memory.new IO.copy(self, io) io.to_slice end # Reads a line from this `IO`. A line is terminated by the `\n` character. # Returns `nil` if called at the end of this `IO`. # # By default the newline is removed from the returned string, # unless *chomp* is `false`. # # ``` # io = IO::Memory.new "hello\nworld\nfoo\n" # io.gets # => "hello" # io.gets(chomp: false) # => "world\n" # io.gets # => "foo" # io.gets # => nil # ``` def gets(chomp : Bool = true) : String? gets '\n', chomp: chomp end # Reads a line of at most *limit* bytes from this `IO`. # A line is terminated by the `\n` character. # Returns `nil` if called at the end of this `IO`. # # ``` # io = IO::Memory.new "hello\nworld" # io.gets(3) # => "hel" # io.gets(3) # => "lo\n" # io.gets(3) # => "wor" # io.gets(3) # => "ld" # io.gets(3) # => nil # ``` def gets(limit : Int, chomp : Bool = false) : String? gets '\n', limit: limit, chomp: chomp end # Reads until *delimiter* is found, or the end of the `IO` is reached. # Returns `nil` if called at the end of this `IO`. # # ``` # io = IO::Memory.new "hello\nworld" # io.gets('o') # => "hello" # io.gets('r') # => "\nwor" # io.gets('z') # => "ld" # io.gets('w') # => nil # ``` def gets(delimiter : Char, chomp : Bool = false) : String? gets delimiter, Int32::MAX, chomp: chomp end # Reads until *delimiter* is found, *limit* bytes are read, or the end of the `IO` is reached. # Returns `nil` if called at the end of this `IO`. # # ``` # io = IO::Memory.new "hello\nworld" # io.gets('o', 3) # => "hel" # io.gets('r', 10) # => "lo\nwor" # io.gets('z', 10) # => "ld" # io.gets('w', 10) # => nil # ``` def gets(delimiter : Char, limit : Int, chomp = false) : String? raise ArgumentError.new "Negative limit" if limit < 0 ascii = delimiter.ascii? decoder = decoder() # If the char's representation is a single byte and we have an encoding, # search the delimiter in the buffer if ascii && decoder return decoder.gets(self, delimiter.ord.to_u8, limit: limit, chomp: chomp) end # If there's no encoding, the delimiter is ASCII and we can peek, # use a faster algorithm if ascii && !decoder && (peek = self.peek) if peek.empty? nil else gets_peek(delimiter, limit, chomp, peek) end else gets_slow(delimiter, limit, chomp) end end private def gets_peek(delimiter, limit, chomp, peek) limit = Int32::MAX if limit < 0 delimiter_byte = delimiter.ord.to_u8 # We first check, if the delimiter is already in the peek buffer. # In that case it's much faster to create a String from a slice # of the buffer instead of appending to a IO::Memory, # which happens in the other case. index = peek.index(delimiter_byte) if index # If we find it past the limit, limit the result if index >= limit index = limit else index += 1 end advance = index if chomp && index > 0 && peek[index - 1] === delimiter_byte index -= 1 if delimiter == '\n' && index > 0 && peek[index - 1] === '\r' index -= 1 end end string = String.new(peek[0, index]) skip(advance) return string end # We didn't find the delimiter, so we append to a String::Builder # until we find it or we reach the limit, appending what we have # in the peek buffer and peeking again. String.build do |buffer| while peek available = Math.min(peek.size, limit) buffer.write peek[0, available] skip(available) peek += available limit -= available if limit == 0 break end if peek.size == 0 peek = self.peek end unless peek # If for some reason this IO became unpeekable, # default to the slow method. One example where this can # happen is `IO::Delimited`. gets_slow(delimiter, limit, chomp, buffer) break end if peek.empty? if buffer.bytesize == 0 return nil else break end end index = peek.index(delimiter_byte) if index if index >= limit index = limit else index += 1 end buffer.write peek[0, index] skip(index) break end end buffer.chomp!(delimiter_byte) if chomp end end private def gets_slow(delimiter : Char, limit, chomp) buffer = String::Builder.new bytes_read = gets_slow(delimiter, limit, chomp, buffer) buffer.to_s if bytes_read end private def gets_slow(delimiter : Char, limit, chomp, buffer : String::Builder) : Bool bytes_read = false chomp_rn = delimiter == '\n' && chomp while true info = read_char_with_bytesize unless info break end char, char_bytesize = info bytes_read = true # Consider the case of \r\n when the delimiter is \n and chomp = true if chomp_rn && char == '\r' info2 = read_char_with_bytesize unless info2 buffer << char break end char2, char_bytesize2 = info2 if char2 == '\n' break end buffer << '\r' break if char_bytesize >= limit limit -= char_bytesize buffer << char2 break if char_bytesize2 >= limit limit -= char_bytesize2 next elsif char == delimiter buffer << char unless chomp break else buffer << char end break if char_bytesize >= limit limit -= char_bytesize end bytes_read end # Reads until *delimiter* is found or the end of the `IO` is reached. # Returns `nil` if called at the end of this `IO`. # # ``` # io = IO::Memory.new "hello\nworld" # io.gets("wo") # => "hello\nwo" # io.gets("wo") # => "rld" # io.gets("wo") # => nil # ``` def gets(delimiter : String, chomp = false) : String? # Empty string: read all if delimiter.empty? return gets_to_end end # One byte: use gets(Char) if delimiter.bytesize == 1 return gets(delimiter.to_unsafe[0].unsafe_chr, chomp: chomp) end # One char: use gets(Char) if delimiter.size == 1 return gets(delimiter[0], chomp: chomp) end # The 'hard' case: we read until we match the last byte, # and then compare backwards last_byte = delimiter.byte_at(delimiter.bytesize - 1) total_bytes = 0 buffer = String::Builder.new while true unless byte = read_utf8_byte return buffer.empty? ? nil : buffer.to_s end buffer.write_byte(byte) total_bytes += 1 if (byte == last_byte) && (buffer.bytesize >= delimiter.bytesize) && (buffer.buffer + total_bytes - delimiter.bytesize).memcmp(delimiter.to_unsafe, delimiter.bytesize) == 0 buffer.back(delimiter.bytesize) if chomp break end end buffer.to_s end # Same as `gets`, but raises `EOFError` if called at the end of this `IO`. def read_line(*args, **options) : String gets(*args, **options) || raise EOFError.new end # Reads and discards exactly *bytes_count* bytes. # Raises `IO::EOFError` if there aren't at least *bytes_count* bytes. # # ``` # io = IO::Memory.new "hello world" # io.skip(6) # io.gets # => "world" # io.skip(1) # raises IO::EOFError # ``` def skip(bytes_count : Int) : Nil buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] while bytes_count > 0 read_count = read(buffer.to_slice[0, Math.min(bytes_count, buffer.size)]) raise IO::EOFError.new if read_count == 0 bytes_count -= read_count end end # Reads and discards bytes from `self` until there # are no more bytes. def skip_to_end : Nil buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] while read(buffer.to_slice) > 0 end end # Writes a single byte into this `IO`. # # ``` # io = IO::Memory.new # io.write_byte 97_u8 # io.to_s # => "a" # ``` def write_byte(byte : UInt8) : Nil x = byte write Slice.new(pointerof(x), 1) end # Writes the given object to this `IO` using the specified *format*. # # This ends up invoking `object.to_io(self, format)`, so any object defining a # `to_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian)` # method can be written in this way. # # See `Int#to_io` and `Float#to_io`. # # ``` # io = IO::Memory.new # io.write_bytes(0x01020304, IO::ByteFormat::LittleEndian) # io.rewind # io.gets(4) # => "\u{4}\u{3}\u{2}\u{1}" # ``` def write_bytes(object : _, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) : Nil object.to_io(self, format) end # Reads an instance of the given *type* from this `IO` using the specified *format*. # # This ends up invoking `type.from_io(self, format)`, so any type defining a # `from_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian)` # method can be read in this way. # # See `Int.from_io` and `Float.from_io`. # # ``` # io = IO::Memory.new # io.puts "\u{4}\u{3}\u{2}\u{1}" # io.rewind # io.read_bytes(Int32, IO::ByteFormat::LittleEndian) # => 0x01020304 # ``` def read_bytes(type, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) type.from_io(self, format) end # Returns `true` if this `IO` is associated with a terminal device (tty), `false` otherwise. # # IO returns `false`, but including types may override. # # ``` # STDIN.tty? # => true # IO::Memory.new.tty? # => false # ``` def tty? : Bool false end # Invokes the given block with each *line* in this `IO`, where a line # is defined by the arguments passed to this method, which can be the same # ones as in the `gets` methods. # # ``` # io = IO::Memory.new("hello\nworld") # io.each_line do |line| # puts line # end # # output: # # hello # # world # ``` def each_line(*args, **options, &block : String ->) : Nil while line = gets(*args, **options) yield line end end # Returns an `Iterator` for the *lines* in this `IO`, where a line # is defined by the arguments passed to this method, which can be the same # ones as in the `gets` methods. # # ``` # io = IO::Memory.new("hello\nworld") # iter = io.each_line # iter.next # => "hello" # iter.next # => "world" # ``` def each_line(*args, **options) LineIterator.new(self, args, options) end # Invokes the given block with each `Char` in this `IO`. # # ``` # io = IO::Memory.new("あめ") # io.each_char do |char| # puts char # end # ``` # # Output: # # ```text # あ # め # ``` def each_char(&) : Nil while char = read_char yield char end end # Returns an `Iterator` for the chars in this `IO`. # # ``` # io = IO::Memory.new("あめ") # iter = io.each_char # iter.next # => 'あ' # iter.next # => 'め' # ``` def each_char CharIterator.new(self) end # Invokes the given block with each byte (`UInt8`) in this `IO`. # # ``` # io = IO::Memory.new("aあ") # io.each_byte do |byte| # puts byte # end # ``` # # Output: # # ```text # 97 # 227 # 129 # 130 # ``` def each_byte(&) : Nil while byte = read_byte yield byte end end # Returns an `Iterator` for the bytes in this `IO`. # # ``` # io = IO::Memory.new("aあ") # iter = io.each_byte # iter.next # => 97 # iter.next # => 227 # iter.next # => 129 # iter.next # => 130 # ``` def each_byte ByteIterator.new(self) end # Rewinds this `IO`. By default this method raises, but including types # may implement it. def rewind raise IO::Error.new("Can't rewind") end # Sets the encoding of this `IO`. # # The *invalid* argument can be: # * `nil`: an exception is raised on invalid byte sequences # * `:skip`: invalid byte sequences are ignored # # String operations (`gets`, `gets_to_end`, `read_char`, `<<`, `print`, `puts` # `printf`) will use this encoding. def set_encoding(encoding : String, invalid : Symbol? = nil) : Nil if utf8_encoding?(encoding, invalid) @encoding = nil else @encoding = EncodingOptions.new(encoding, invalid) end @encoder.try &.close @decoder.try &.close @encoder = nil @decoder = nil nil end # Returns this `IO`'s encoding. The default is `UTF-8`. def encoding : String @encoding.try(&.name) || "UTF-8" end private def utf8_encoding?(encoding : String, invalid : Symbol? = nil) : Bool invalid.nil? && ( encoding.compare("UTF-8", case_insensitive: true) == 0 || encoding.compare("UTF8", case_insensitive: true) == 0 ) end # :nodoc: def has_non_utf8_encoding? : Bool !!@encoding end # Seeks to a given *offset* (in bytes) according to the *whence* argument. # # The `IO` class raises on this method, but some subclasses, notable # `IO::FileDescriptor` and `IO::Memory` implement it. # # Returns `self`. # # ``` # File.write("testfile", "abc") # # file = File.new("testfile") # file.gets(3) # => "abc" # file.seek(1, IO::Seek::Set) # file.gets(2) # => "bc" # file.seek(-1, IO::Seek::Current) # file.gets(1) # => "c" # ``` def seek(offset, whence : Seek = Seek::Set) raise Error.new "Unable to seek" end # Returns the current position (in bytes) in this `IO`. # # The `IO` class raises on this method, but some subclasses, notable # `IO::FileDescriptor` and `IO::Memory` implement it. # # ``` # File.write("testfile", "hello") # # file = File.new("testfile") # file.pos # => 0 # file.gets(2) # => "he" # file.pos # => 2 # ``` def pos raise Error.new "Unable to pos" end # Sets the current position (in bytes) in this `IO`. # # The `IO` class raises on this method, but some subclasses, notable # `IO::FileDescriptor` and `IO::Memory` implement it. # # ``` # File.write("testfile", "hello") # # file = File.new("testfile") # file.pos = 3 # file.gets_to_end # => "lo" # ``` def pos=(value) raise Error.new "Unable to pos=" end # Same as `pos`. def tell pos end # Yields an `IO` to read a section inside this IO. # # The `IO` class raises on this method, but some subclasses, notable # `File` and `IO::Memory` implement it. # # Multiple sections can be read concurrently. def read_at(offset, bytesize, & : IO ->) raise Error.new "Unable to read_at" end # Copy all contents from *src* to *dst*. # # ``` # io = IO::Memory.new "hello" # io2 = IO::Memory.new # # IO.copy io, io2 # # io2.to_s # => "hello" # ``` def self.copy(src : IO, dst : IO) : Int64 buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] count = 0_i64 while (len = src.read(buffer.to_slice).to_i32) > 0 dst.write buffer.to_slice[0, len] count &+= len end count end # Copy at most *limit* bytes from *src* to *dst*. # # ``` # io = IO::Memory.new "hello" # io2 = IO::Memory.new # # IO.copy io, io2, 3 # # io2.to_s # => "hel" # ``` def self.copy(src : IO, dst : IO, limit : Int) : Int64 raise ArgumentError.new("Negative limit") if limit < 0 limit = limit.to_i64 buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] remaining = limit while (len = src.read(buffer.to_slice[0, Math.min(buffer.size, Math.max(remaining, 0))])) > 0 dst.write buffer.to_slice[0, len] remaining &-= len end limit - remaining end # Compares two streams *stream1* to *stream2* to determine if they are identical. # Returns `true` if content are the same, `false` otherwise. # # ``` # File.write("afile", "123") # stream1 = File.open("afile") # stream2 = IO::Memory.new("123") # IO.same_content?(stream1, stream2) # => true # ``` def self.same_content?(stream1 : IO, stream2 : IO) : Bool buf1 = uninitialized UInt8[1024] buf2 = uninitialized UInt8[1024] while true read1 = stream1.read(buf1.to_slice) if read1.zero? # First stream is EOF, check if the second has more. return stream2.read_byte.nil? end read2 = stream2.read_fully?(buf2.to_slice[0, read1]) return false unless read2 return false if buf1.to_unsafe.memcmp(buf2.to_unsafe, read1) != 0 end end private struct LineIterator(I, A, N) include Iterator(String) def initialize(@io : I, @args : A, @nargs : N) end def next @io.gets(*@args, **@nargs) || stop end end private struct CharIterator(I) include Iterator(Char) def initialize(@io : I) end def next @io.read_char || stop end end private struct ByteIterator(I) include Iterator(UInt8) def initialize(@io : I) end def next @io.read_byte || stop end end end require "./io/*"