module IO

Overview

The IO module is the basis for all input and output in Crystal.

This module is included by types like File, Socket and MemoryIO and provide many useful methods for reading to and writing from an IO, like #print, #puts, #gets and #printf.

The only requirement for a type including the IO module is to define these two methods:

For example, this is a simple IO on top of a Slice(UInt8):

class SimpleSliceIO
  include IO

  def initialize(@slice : Slice(UInt8))
  end

  def read(slice : Slice(UInt8))
    slice.size.times { |i| slice[i] = @slice[i] }
    @slice += slice.size
    count
  end

  def write(slice : Slice(UInt8))
    slice.size.times { |i| @slice[i] = slice[i] }
    @slice += slice.size
    nil
  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) 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.

Included Modules

Direct including types

Defined in:

io.cr
io/delimited.cr
io/encoding.cr
io/error.cr
io/hexdump.cr
io/sized.cr
json/to_json.cr

Class Method Summary

Instance Method Summary

Instance methods inherited from module JSON::Builder

json_array(&block) json_array, json_object(&block) json_object

Class Method Detail

def self.copy(src, dst, limit : Int) #

Copy at most limit bytes from src to dst.

io = MemoryIO.new "hello"
io2 = MemoryIO.new

IO.copy io, io2, 3

io2.to_s # => "hel"

[View source]
def self.copy(src, dst) #

Copy all contents from src to dst.

io = MemoryIO.new "hello"
io2 = MemoryIO.new

IO.copy io, io2

io2.to_s # => "hello"

[View source]
def self.pipe(read_blocking = false, write_blocking = false, &block) #

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

[View source]
def self.pipe(read_blocking = false, write_blocking = false) #

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"

[View source]
def self.select(read_ios, write_ios, error_ios, timeout_sec : LibC::TimeT | Int | Float | Nil) #

Returns an array of all given IOs that are

  • ready to read if they appeared in read_ios
  • ready to write if they appeared in write_ios
  • have an error condition if they appeared in error_ios

If the optional timeout_sec is given, nil is returned if no IO was ready after the specified amount of seconds passed. Fractions are supported.

If timeout_sec is nil, this method blocks until an IO is ready.


[View source]
def self.select(read_ios, write_ios = nil, error_ios = nil) #

[View source]

Instance Method Detail

def <<(obj) : self #

Writes the given object into this IO. This ends up calling to_s(io) on the object.

io = MemoryIO.new
io << 1
io << '-'
io << "Crystal"
io.to_s # => "1-Crystal"

[View source]
def close #

Closes this IO.

IO defines this is a no-op method, but including types may override.


[View source]
def closed? #

Returns true if this IO is closed.

IO defines returns false, but including types may override.


[View source]
def each_byte(&block) #

Inovkes the given block with each byte (UInt8) in this IO.

io = MemoryIO.new("aあ")
io.each_byte do |byte|
  puts byte
end

Output:

97
227
129
130

[View source]
def each_byte #

Returns an Iterator for the bytes in this IO.

io = MemoryIO.new("aあ")
iter = io.each_byte
iter.next # => 97
iter.next # => 227
iter.next # => 129
iter.next # => 130

[View source]
def each_char(&block) #

Inovkes the given block with each Char in this IO.

io = MemoryIO.new("あめ")
io.each_char do |char|
  puts char
end

Output:

あ
め

[View source]
def each_char #

Returns an Iterator for the chars in this IO.

io = MemoryIO.new("あめ")
iter = io.each_char
iter.next # => 'あ'
iter.next # => 'め'

[View source]
def each_line(*args, **options) #

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 = MemoryIO.new("hello\nworld")
iter = io.each_line
iter.next # => "hello\n"
iter.next # => "world"

[View source]
def each_line(*args, **options, &block) #

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 = MemoryIO.new("hello\nworld")
io.each_line do |line|
  puts line.chomp.reverse
end

Output:

olleh
dlrow

[View source]
def encoding : String #

Returns this IO's encoding. The default is UTF-8.


[View source]
def flush #

Flushes buffered data, if any.

IO defines this is a no-op method, but including types may override.


[View source]
def gets(limit : Int) : String | Nil #

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 = MemoryIO.new "hello\nworld"
io.gets(3) # => "hel"
io.gets(3) # => "lo\n"
io.gets(3) # => "wor"
io.gets(3) # => "ld"
io.gets(3) # => nil

[View source]
def gets(delimiter : Char, limit : Int) : String | Nil #

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 = MemoryIO.new "hello\nworld"
io.gets('o', 3)  # => "hel"
io.gets('r', 10) # => "lo\nwor"
io.gets('z', 10) # => "ld"
io.gets('w', 10) # => nil

[View source]
def gets(delimiter : Char) : String | Nil #

Reads until delimiter is found, or the end of the IO is reached. Returns nil if called at the end of this IO.

io = MemoryIO.new "hello\nworld"
io.gets('o') # => "hello"
io.gets('r') # => "\nwor"
io.gets('z') # => "ld"
io.gets('w') # => nil

[View source]
def gets : String | Nil #

Reads a line from this IO. A line is terminated by the \n character. Returns nil if called at the end of this IO.

io = MemoryIO.new "hello\nworld"
io.gets # => "hello\n"
io.gets # => "world"
io.gets # => nil

[View source]
def gets(delimiter : String) : String | Nil #

Reads until delimiter is found or the end of the IO is reached. Returns nil if called at the end of this IO.

io = MemoryIO.new "hello\nworld"
io.gets("wo") # => "hello\nwo"
io.gets("wo") # => "rld"
io.gets("wo") # => nil

[View source]
def gets_to_end : String #

Reads the rest of this IO data as a String.

io = MemoryIO.new "hello world"
io.gets_to_end # => "hello world"
io.gets_to_end # => ""

[View source]
def print(obj) : Nil #

Same as #<<

io = MemoryIO.new
io.print 1
io.print '-'
io.print "Crystal"
io.to_s # => "1-Crystal"

[View source]
def print(*objects : _) : Nil #

Writes the given objects into this IO by invoking to_s(io) on each of the objects.

io = MemoryIO.new
io.print 1, '-', "Crystal"
io.to_s # => "1-Crystal"

[View source]
def printf(format_string, *args) : Nil #

[View source]
def printf(format_string, args : Array | Tuple) : Nil #

[View source]
def puts(*objects : _) : Nil #

Writes the given objects, each followed by a newline character.

io = MemoryIO.new
io.puts 1, '-', "Crystal"
io.to_s # => "1\n-\nCrystal\n"

[View source]
def puts : Nil #

Writes a newline character.

io = MemoryIO.new
io.puts
io.to_s # => "\n"

[View source]
def puts(string : String) : Nil #

Writes the given string to this IO followed by a newline character unless the string already ends with one.

io = MemoryIO.new
io.puts "hello\n"
io.puts "world"
io.to_s # => "hello\nworld\n"

[View source]
def puts(obj) : Nil #

Writes the given object to this IO followed by a newline character.

io = MemoryIO.new
io.puts 1
io.puts "Crystal"
io.to_s # => "1\nCrystal\n"

[View source]
abstract def read(slice : Slice(UInt8)) #

Reads at most slice.size bytes from this IO into slice. Returns the number of bytes read.

io = MemoryIO.new "hello"
slice = Slice(UInt8).new(4)
io.read(slice) # => 4
slice          # => [104, 101, 108, 108]
io.read(slice) # => 1
slice          # => [111, 101, 108, 108]

[View source]
def read_byte : UInt8 | Nil #

Reads a single byte from this IO. Returns nil if there is no more data to read.

io = MemoryIO.new "a"
io.read_byte # => 97
io.read_byte # => nil

[View source]
def read_bytes(type, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) #

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 = MemoryIO.new
io.puts "\u{4}\u{3}\u{2}\u{1}"
io.rewind
io.read_bytes(Int32, IO::ByteFormat::LittleEndian) # => 0x01020304

[View source]
def read_char : Char | Nil #

Reads a single Char from this IO. Returns nil if there is no more data to read.

io = MemoryIO.new "あ"
io.read_char # => 'あ'
io.read_char # => nil

[View source]
def read_fully(slice : Slice(UInt8)) #

Tries to read exactly slice.size bytes from this IO into slice. Raises EOFError if there aren't slice.size bytes of data.

io = MemoryIO.new "123451234"
slice = Slice(UInt8).new(5)
io.read_fully(slice)
slice         # => [49, 50, 51, 52, 53]
io.read_fully # => EOFError

[View source]
def read_line(*args, **options) : String | Nil #

Same as #gets, but raises EOFError if called at the end of this IO.


[View source]
def read_utf8(slice : Slice(UInt8)) #

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") # => [196, 227]

io = MemoryIO.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]             # => [228, 189, 160]

"你".bytes # => [228, 189, 160]

[View source]
def read_utf8_byte #

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") # => [196, 227]

io = MemoryIO.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]

[View source]
def rewind #

Rewinds this IO. By default this method raises, but including types mayb implement it.


[View source]
def set_encoding(encoding : String, invalid : Symbol | Nil = nil) #

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.


[View source]
def skip(bytes_count : Int) : Nil #

Reads and discards bytes_count bytes.

io = MemoryIO.new "hello world"
io.skip(6)
io.gets # => "world"

[View source]
def tty? : Bool #

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
MemoryIO.new.tty? # => false

[View source]
abstract def write(slice : Slice(UInt8)) : Nil #

Writes the contents of slice into this IO.

io = MemoryIO.new
slice = Slice(UInt8).new(4) { |i| ('a'.ord + i).to_u8 }
io.write(slice)
io.to_s #=> "abcd"

[View source]
def write_byte(byte : UInt8) #

Writes a single byte into this IO.

io = MemoryIO.new
io.write_byte 97_u8
io.to_s # => "a"

[View source]
def write_bytes(object, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) #

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 = MemoryIO.new
io.write_bytes(0x01020304, IO::ByteFormat::LittleEndian)
io.rewind
io.gets(4) # => "\u{4}\u{3}\u{2}\u{1}"

[View source]
def write_utf8(slice : Slice(UInt8)) #

Writes a slice of UTF-8 encoded bytes to this IO, using the current encoding.


[View source]