diff --git a/core/builtin.rbs b/core/builtin.rbs index ad1253bc0..524641726 100644 --- a/core/builtin.rbs +++ b/core/builtin.rbs @@ -182,6 +182,7 @@ interface _Reader def read: (?int? length, ?string outbuf) -> String? end +%a{steep:deprecated} # now lives in `IO` interface _ReaderPartial def readpartial: (int maxlen, ?string outbuf) -> String end @@ -192,6 +193,7 @@ interface _Writer def write: (*_ToS data) -> Integer end +%a{steep:deprecated} # not used at all interface _Rewindable # Positions the stream to the beginning of input, resetting `lineno` to zero. # diff --git a/core/encoding.rbs b/core/encoding.rbs index 8f211474c..8b18f4b82 100644 --- a/core/encoding.rbs +++ b/core/encoding.rbs @@ -28,6 +28,13 @@ # useful when you use other ASCII-compatible encodings. # class Encoding + # The `_EncodeFallbackAref` is used in functions such as `String::encode` to get the fallback + # value when a charcater can't be converted between encodings. + interface _EncodeFallbackAref + # Returns a string to use as a replacement, or `nil` if a replacement isn't possible. + def []: (String) -> string? + end + def self._load: [T] (T) -> T # - # Writes the given `object` to `self`, which must be opened for writing (see - # [Access Modes](rdoc-ref:File@Access+Modes)); returns `self`; if `object` is - # not a string, it is converted via method `to_s`: + # open_mode is used to represent the modes that can be used when opening files/streams, such as + # `"w"`, `"r"`, `IO::RDONLY`, etc. + type open_mode = int | string | nil + + READABLE: Integer + + WRITABLE: Integer + + PRIORITY: Integer + + # + # Set I/O position from the current position # - # $stdout << 'Hello' << ', ' << 'World!' << "\n" - # $stdout << 'foo' << :bar << 2 << "\n" + SEEK_CUR: Integer + + # + # Set I/O position to the next location containing data # - # Output: + SEEK_DATA: Integer + + # + # Set I/O position from the end # - # Hello, World! - # foobar2 + SEEK_END: Integer + + # + # Set I/O position to the next hole # - def <<: (_ToS obj) -> self + SEEK_HOLE: Integer - # - # Invokes Posix system call - # [posix_fadvise(2)](https://linux.die.net/man/2/posix_fadvise), which announces - # an intention to access data from the current file in a particular manner. + # + # Set I/O position from the beginning # - # The arguments and results are platform-dependent. + SEEK_SET: Integer + + # + # exception to wait for reading. see IO.select. # - # The relevant data is specified by: + module WaitReadable + end + + # + # exception to wait for writing. see IO.select. # - # * `offset`: The offset of the first byte of data. - # * `len`: The number of bytes to be accessed; if `len` is zero, or is larger - # than the number of bytes remaining, all remaining bytes will be accessed. + module WaitWritable + end + + # + # exception to wait for reading by EAGAIN. see IO.select. # + class EAGAINWaitReadable < Errno::EAGAIN + include WaitReadable + + Errno: Integer + end + + # + # exception to wait for writing by EAGAIN. see IO.select. # - # Argument `advice` is one of the following symbols: + class EAGAINWaitWritable < Errno::EAGAIN + include WaitWritable + + Errno: Integer + end + + # + # exception to wait for reading by EWOULDBLOCK. see IO.select. # - # * `:normal`: The application has no advice to give about its access pattern - # for the specified data. If no advice is given for an open file, this is - # the default assumption. - # * `:sequential`: The application expects to access the specified data - # sequentially (with lower offsets read before higher ones). - # * `:random`: The specified data will be accessed in random order. - # * `:noreuse`: The specified data will be accessed only once. - # * `:willneed`: The specified data will be accessed in the near future. - # * `:dontneed`: The specified data will not be accessed in the near future. + class EWOULDBLOCKWaitReadable < Errno + include WaitReadable + + Errno: Integer + end + + # + # exception to wait for writing by EWOULDBLOCK. see IO.select. # + class EWOULDBLOCKWaitWriteable < Errno + include WaitWriteable + + Errno: Integer + end + + # + # exception to wait for reading by EINPROGRESS. see IO.select. # - # Not implemented on all platforms. + class EINPROGRESSWaitReadable < Errno::EINPROGRESS + include WaitReadable + + Errno: Integer + end + + # + # exception to wait for writing by EINPROGRESS. see IO.select. # - def advise: (:normal | :sequential | :random | :willneed | :dontneed | :noreuse advise, ?Integer offset, ?Integer len) -> nil + class EINPROGRESSWaitWritable < Errno::EINPROGRESS + include WaitWritable + + Errno: Integer + end # - # Sets auto-close flag. - # - # f = File.open(File::NULL) - # IO.for_fd(f.fileno).close - # f.gets # raises Errno::EBADF + # Behaves like IO.read, except that the stream is opened in binary mode with + # ASCII-8BIT encoding. # - # f = File.open(File::NULL) - # g = IO.for_fd(f.fileno) - # g.autoclose = false - # g.close - # f.gets # won't cause Errno::EBADF + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). # - def autoclose=: (boolish bool) -> boolish + def self.binread: (String name, ?Integer length, ?Integer offset) -> String # - # Returns `true` if the underlying file descriptor of *ios* will be closed at - # its finalization or at calling #close, otherwise `false`. + # Behaves like IO.write, except that the stream is opened in binary mode with + # ASCII-8BIT encoding. # - def autoclose?: () -> bool + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). + # + def self.binwrite: (String name, _ToS string, ?Integer offset, ?mode: String mode) -> Integer # - # Sets the stream's data mode as binary (see [Data - # Mode](rdoc-ref:File@Data+Mode)). + # Copies from the given `src` to the given `dst`, returning the number of bytes + # copied. # - # A stream's data mode may not be changed from binary to text. + # * The given `src` must be one of the following: # - def binmode: () -> self - - # - # Returns `true` if the stream is on binary mode, `false` otherwise. See [Data - # Mode](rdoc-ref:File@Data+Mode). + # * The path to a readable file, from which source data is to be read. + # * An IO-like object, opened for reading and capable of responding to + # method `:readpartial` or method `:read`. # - def binmode?: () -> bool - - # - # Closes the stream for both reading and writing if open for either or both; - # returns `nil`. See [Open and Closed - # Streams](rdoc-ref:IO@Open+and+Closed+Streams). # - # If the stream is open for writing, flushes any buffered writes to the - # operating system before closing. + # * The given `dst` must be one of the following: # - # If the stream was opened by IO.popen, sets global variable `$?` (child exit - # status). + # * The path to a writable file, to which data is to be written. + # * An IO-like object, opened for writing and capable of responding to + # method `:write`. # - # Example: # - # IO.popen('ruby', 'r+') do |pipe| - # puts pipe.closed? - # pipe.close - # puts $? - # puts pipe.closed? - # end # - # Output: + # The examples here use file `t.txt` as source: # - # false - # pid 13760 exit 0 - # true + # File.read('t.txt') + # # => "First line\nSecond line\n\nThird line\nFourth line\n" + # File.read('t.txt').size # => 47 # - # Related: IO#close_read, IO#close_write, IO#closed?. + # If only arguments `src` and `dst` are given, the entire source stream is + # copied: # - def close: () -> nil - - # - # Sets a close-on-exec flag. + # # Paths. + # IO.copy_stream('t.txt', 't.tmp') # => 47 # - # f = File.open(File::NULL) - # f.close_on_exec = true - # system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory - # f.closed? #=> false + # # IOs (recall that a File is also an IO). + # src_io = File.open('t.txt', 'r') # => # + # dst_io = File.open('t.tmp', 'w') # => # + # IO.copy_stream(src_io, dst_io) # => 47 + # src_io.close + # dst_io.close # - # Ruby sets close-on-exec flags of all file descriptors by default since Ruby - # 2.0.0. So you don't need to set by yourself. Also, unsetting a close-on-exec - # flag can cause file descriptor leak if another thread use fork() and exec() - # (via system() method for example). If you really needs file descriptor - # inheritance to child process, use spawn()'s argument such as fd=>fd. + # With argument `src_length` a non-negative integer, no more than that many + # bytes are copied: # - def close_on_exec=: (boolish bool) -> nil - - # - # Returns `true` if the stream will be closed on exec, `false` otherwise: + # IO.copy_stream('t.txt', 't.tmp', 10) # => 10 + # File.read('t.tmp') # => "First line" # - # f = File.open('t.txt') - # f.close_on_exec? # => true - # f.close_on_exec = false - # f.close_on_exec? # => false - # f.close + # With argument `src_offset` also given, the source stream is read beginning at + # that offset: # - def close_on_exec?: () -> bool + # IO.copy_stream('t.txt', 't.tmp', 11, 11) # => 11 + # IO.read('t.tmp') # => "Second line" + # + def self.copy_stream: (String | _Reader | _Readpartial src, String | _Writer dst, ?Integer copy_length, ?Integer src_offset) -> Integer + + # An interface used for reading only parts of a stream at a time; used in `IO#copy_stream`. + # + # The `readpartial` function should concat at most `maxlen` bytes into `outbuf` + interface _Readpartial + # Read at most `maxlen` bytes, and concat them onto the end of `String`. + # + # Unlike almost every other standard lib function, the return value of this is entirely ignored + # by ruby, and is `void`. + def readpartial: (Integer maxlen, String outbuf) -> void + end # - # Closes the stream for reading if open for reading; returns `nil`. See [Open - # and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). + # Executes the given command `cmd` as a subprocess whose $stdin and $stdout are + # connected to a new stream `io`. # - # If the stream was opened by IO.popen and is also closed for writing, sets - # global variable `$?` (child exit status). + # This method has potential security vulnerabilities if called with untrusted + # input; see [Command Injection](rdoc-ref:command_injection.rdoc). # - # Example: + # If no block is given, returns the new stream, which depending on given `mode` + # may be open for reading, writing, or both. The stream should be explicitly + # closed (eventually) to avoid resource leaks. # - # IO.popen('ruby', 'r+') do |pipe| - # puts pipe.closed? - # pipe.close_write - # puts pipe.closed? - # pipe.close_read - # puts $? - # puts pipe.closed? - # end + # If a block is given, the stream is passed to the block (again, open for + # reading, writing, or both); when the block exits, the stream is closed, and + # the block's value is assigned to global variable `$?` and returned. # - # Output: + # Optional argument `mode` may be any valid IO mode. See [Access + # Modes](rdoc-ref:File@Access+Modes). # - # false - # false - # pid 14748 exit 0 - # true + # Required argument `cmd` determines which of the following occurs: # - # Related: IO#close, IO#close_write, IO#closed?. + # * The process forks. + # * A specified program runs in a shell. + # * A specified program runs with specified arguments. + # * A specified program runs with specified arguments and a specified `argv0`. # - def close_read: () -> nil - - # - # Closes the stream for writing if open for writing; returns `nil`. See [Open - # and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). # - # Flushes any buffered writes to the operating system before closing. + # Each of these is detailed below. # - # If the stream was opened by IO.popen and is also closed for reading, sets - # global variable `$?` (child exit status). + # The optional hash argument `env` specifies name/value pairs that are to be + # added to the environment variables for the subprocess: # - # IO.popen('ruby', 'r+') do |pipe| - # puts pipe.closed? - # pipe.close_read - # puts pipe.closed? + # IO.popen({'FOO' => 'bar'}, 'ruby', 'r+') do |pipe| + # pipe.puts 'puts ENV["FOO"]' # pipe.close_write - # puts $? - # puts pipe.closed? - # end + # pipe.gets + # end => "bar\n" # - # Output: + # Optional keyword arguments `opts` specify: # - # false - # false - # pid 15044 exit 0 - # true + # * [Open options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # * Options for Kernel#spawn. # - # Related: IO#close, IO#close_read, IO#closed?. # - def close_write: () -> nil - - # - # Returns `true` if the stream is closed for both reading and writing, `false` - # otherwise. See [Open and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). + # **Forked \Process** # - # IO.popen('ruby', 'r+') do |pipe| - # puts pipe.closed? - # pipe.close_read - # puts pipe.closed? - # pipe.close_write - # puts pipe.closed? + # When argument `cmd` is the 1-character string `'-'`, causes the process to + # fork: + # IO.popen('-') do |pipe| + # if pipe + # $stderr.puts "In parent, child pid is #{pipe.pid}\n" + # else + # $stderr.puts "In child, pid is #{$$}\n" + # end # end # # Output: # - # false - # false - # true + # In parent, child pid is 26253 + # In child, pid is 26253 # - # Related: IO#close_read, IO#close_write, IO#close. + # Note that this is not supported on all platforms. # - def closed?: () -> bool - - # - # Calls the given block with each byte (0..255) in the stream; returns `self`. - # See [Byte IO](rdoc-ref:IO@Byte+IO). + # **Shell Subprocess** # - # f = File.new('t.rus') - # a = [] - # f.each_byte {|b| a << b } - # a # => [209, 130, 208, 181, 209, 129, 209, 130] - # f.close + # When argument `cmd` is a single string (but not `'-'`), the program named + # `cmd` is run as a shell command: # - # Returns an Enumerator if no block is given. + # IO.popen('uname') do |pipe| + # pipe.readlines + # end # - # Related: IO#each_char, IO#each_codepoint. + # Output: # - def each_byte: () { (Integer byte) -> void } -> self - | () -> ::Enumerator[Integer, self] - - # - # Calls the given block with each character in the stream; returns `self`. See - # [Character IO](rdoc-ref:IO@Character+IO). + # ["Linux\n"] # - # f = File.new('t.rus') - # a = [] - # f.each_char {|c| a << c.ord } - # a # => [1090, 1077, 1089, 1090] - # f.close + # Another example: # - # Returns an Enumerator if no block is given. + # IO.popen('/bin/sh', 'r+') do |pipe| + # pipe.puts('ls') + # pipe.close_write + # $stderr.puts pipe.readlines.size + # end # - # Related: IO#each_byte, IO#each_codepoint. + # Output: # - def each_char: () { (String c) -> void } -> self - | () -> ::Enumerator[String, self] - - # - # Calls the given block with each codepoint in the stream; returns `self`: + # 213 # - # f = File.new('t.rus') - # a = [] - # f.each_codepoint {|c| a << c } - # a # => [1090, 1077, 1089, 1090] - # f.close + # **Program Subprocess** # - # Returns an Enumerator if no block is given. + # When argument `cmd` is an array of strings, the program named `cmd[0]` is run + # with all elements of `cmd` as its arguments: # - # Related: IO#each_byte, IO#each_char. + # IO.popen(['du', '..', '.']) do |pipe| + # $stderr.puts pipe.readlines.size + # end # - def each_codepoint: () { (Integer c) -> void } -> self - | () -> ::Enumerator[Integer, self] - - # - # Returns `true` if the stream is positioned at its end, `false` otherwise; see - # [Position](rdoc-ref:IO@Position): + # Output: # - # f = File.open('t.txt') - # f.eof # => false - # f.seek(0, :END) # => 0 - # f.eof # => true - # f.close + # 1111 # - # Raises an exception unless the stream is opened for reading; see - # [Mode](rdoc-ref:File@Access+Modes). + # **Program Subprocess with `argv0`** # - # If `self` is a stream such as pipe or socket, this method blocks until the - # other end sends some data or closes it: + # When argument `cmd` is an array whose first element is a 2-element string + # array and whose remaining elements (if any) are strings: # - # r, w = IO.pipe - # Thread.new { sleep 1; w.close } - # r.eof? # => true # After 1-second wait. + # * `cmd[0][0]` (the first string in the nested array) is the name of a + # program that is run. + # * `cmd[0][1]` (the second string in the nested array) is set as the + # program's `argv[0]`. + # * `cmd[1..-1]` (the strings in the outer array) are the program's arguments. # - # r, w = IO.pipe - # Thread.new { sleep 1; w.puts "a" } - # r.eof? # => false # After 1-second wait. # - # r, w = IO.pipe - # r.eof? # blocks forever + # Example (sets `$0` to 'foo'): # - # Note that this method reads data to the input byte buffer. So IO#sysread may - # not behave as you intend with IO#eof?, unless you call IO#rewind first (which - # is not available for some streams). + # IO.popen([['/bin/sh', 'foo'], '-c', 'echo $0']).read # => "foo\n" # - def eof: () -> bool - - # - # Invokes Posix system call [fcntl(2)](https://linux.die.net/man/2/fcntl), which - # provides a mechanism for issuing low-level commands to control or query a - # file-oriented I/O stream. Arguments and results are platform dependent. + # **Some Special Examples** # - # If `argument` is a number, its value is passed directly; if it is a string, it - # is interpreted as a binary sequence of bytes. (Array#pack might be a useful - # way to build this string.) + # # Set IO encoding. + # IO.popen("nkf -e filename", :external_encoding=>"EUC-JP") {|nkf_io| + # euc_jp_string = nkf_io.read + # } # - # Not implemented on all platforms. + # # Merge standard output and standard error using Kernel#spawn option. See Kernel#spawn. + # IO.popen(["ls", "/", :err=>[:child, :out]]) do |io| + # ls_result_with_error = io.read + # end # - def fcntl: (Integer integer_cmd, String | Integer argument) -> Integer - - # - # Immediately writes to disk all data buffered in the stream, via the operating - # system's: `fdatasync(2)`, if supported, otherwise via `fsync(2)`, if - # supported; otherwise raises an exception. + # # Use mixture of spawn options and IO options. + # IO.popen(["ls", "/"], :err=>[:child, :out]) do |io| + # ls_result_with_error = io.read + # end # - def fdatasync: () -> Integer? - - # - # Returns the integer file descriptor for the stream: + # f = IO.popen("uname") + # p f.readlines + # f.close + # puts "Parent is #{Process.pid}" + # IO.popen("date") {|f| puts f.gets } + # IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f.inspect}"} + # p $? + # IO.popen(%w"sed -e s|^|| -e s&$&;zot;&", "r+") {|f| + # f.puts "bar"; f.close_write; puts f.gets + # } # - # $stdin.fileno # => 0 - # $stdout.fileno # => 1 - # $stderr.fileno # => 2 - # File.open('t.txt').fileno # => 10 - # f.close + # Output (from last section): # - def fileno: () -> Integer + # ["Linux\n"] + # Parent is 21346 + # Thu Jan 15 22:41:19 JST 2009 + # 21346 is here, f is # + # 21352 is here, f is nil + # # + # bar;zot; + # + # Raises exceptions that IO.pipe and Kernel.spawn raise. + # + def self.popen: (string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) -> instance + | (Hash[string, string?] env, string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) -> instance + | [X] (string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) { (instance) -> X } -> X + | [X] (Hash[string, string?] env, string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) { (instance) -> X } -> X - # - # Flushes data buffered in `self` to the operating system (but does not - # necessarily flush data buffered in the operating system): + # The command can be given as: # - # $stdout.print 'no newline' # Not necessarily flushed. - # $stdout.flush # Flushed. + # * Array of string `["ruby", "-v"]`, or + # * Array of string with the first element of array `[["ruby", "RUBY"], "-v"]` # - def flush: () -> self + # But RBS cannot define such a type. So this is simply a union of `string` or `[String, String]`. + # + type cmd_array = array[string | [String, String]] # - # Immediately writes to disk all data buffered in the stream, via the operating - # system's `fsync(2)`. + # Calls the block with each successive line read from the stream. # - # Note this difference: + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). # - # * IO#sync=: Ensures that data is flushed from the stream's internal buffers, - # but does not guarantee that the operating system actually writes the data - # to disk. - # * IO#fsync: Ensures both that data is flushed from internal buffers, and - # that data is written to disk. + # The first argument must be a string that is the path to a file. # + # With only argument `path` given, parses lines from the file at the given + # `path`, as determined by the default line separator, and calls the block with + # each successive line: # - # Raises an exception if the operating system does not support `fsync(2)`. + # File.foreach('t.txt') {|line| p line } # - def fsync: () -> Integer? - - # - # Reads and returns the next byte (in range 0..255) from the stream; returns - # `nil` if already at end-of-stream. See [Byte IO](rdoc-ref:IO@Byte+IO). - # - # f = File.open('t.txt') - # f.getbyte # => 70 - # f.close - # f = File.open('t.rus') - # f.getbyte # => 209 - # f.close + # Output: the same as above. # - # Related: IO#readbyte (may raise EOFError). + # For both forms, command and path, the remaining arguments are the same. # - def getbyte: () -> Integer? - - # - # Reads and returns the next 1-character string from the stream; returns `nil` - # if already at end-of-stream. See [Character IO](rdoc-ref:IO@Character+IO). + # With argument `sep` given, parses lines as determined by that line separator + # (see [Line Separator](rdoc-ref:IO@Line+Separator)): # - # f = File.open('t.txt') - # f.getc # => "F" - # f.close - # f = File.open('t.rus') - # f.getc.ord # => 1090 - # f.close + # File.foreach('t.txt', 'li') {|line| p line } # - # Related: IO#readchar (may raise EOFError). + # Output: # - def getc: () -> String? - - # - # Reads and returns a line from the stream; assigns the return value to `$_`. - # See [Line IO](rdoc-ref:IO@Line+IO). + # "First li" + # "ne\nSecond li" + # "ne\n\nThird li" + # "ne\nFourth li" + # "ne\n" # - # With no arguments given, returns the next line as determined by line separator - # `$/`, or `nil` if none: + # Each paragraph: # - # f = File.open('t.txt') - # f.gets # => "First line\n" - # $_ # => "First line\n" - # f.gets # => "\n" - # f.gets # => "Fourth line\n" - # f.gets # => "Fifth line\n" - # f.gets # => nil - # f.close + # File.foreach('t.txt', '') {|paragraph| p paragraph } # - # With only string argument `sep` given, returns the next line as determined by - # line separator `sep`, or `nil` if none; see [Line - # Separator](rdoc-ref:IO@Line+Separator): + # Output: # - # f = File.new('t.txt') - # f.gets('l') # => "First l" - # f.gets('li') # => "ine\nSecond li" - # f.gets('lin') # => "ne\n\nFourth lin" - # f.gets # => "e\n" - # f.close + # "First line\nSecond line\n\n" + # "Third line\nFourth line\n" # - # The two special values for `sep` are honored: + # With argument `limit` given, parses lines as determined by the default line + # separator and the given line-length limit (see [Line + # Limit](rdoc-ref:IO@Line+Limit)): # - # f = File.new('t.txt') - # # Get all. - # f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" - # f.rewind - # # Get paragraph (up to two line separators). - # f.gets('') # => "First line\nSecond line\n\n" - # f.close + # File.foreach('t.txt', 7) {|line| p line } # - # With only integer argument `limit` given, limits the number of bytes in the - # line; see [Line Limit](rdoc-ref:IO@Line+Limit): + # Output: # - # # No more than one line. - # File.open('t.txt') {|f| f.gets(10) } # => "First line" - # File.open('t.txt') {|f| f.gets(11) } # => "First line\n" - # File.open('t.txt') {|f| f.gets(12) } # => "First line\n" + # "First l" + # "ine\n" + # "Second " + # "line\n" + # "\n" + # "Third l" + # "ine\n" + # "Fourth l" + # "line\n" # - # With arguments `sep` and `limit` given, combines the two behaviors: + # With arguments `sep` and `limit` given, parses lines as determined by the + # given line separator and the given line-length limit (see [Line Separator and + # Line Limit](rdoc-ref:IO@Line+Separator+and+Line+Limit)): # - # * Returns the next line as determined by line separator `sep`, or `nil` if - # none. - # * But returns no more bytes than are allowed by the limit. + # Optional keyword arguments `opts` specify: # + # * [Open Options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # * [Line Options](rdoc-ref:IO@Line+IO). # - # Optional keyword argument `chomp` specifies whether line separators are to be - # omitted: # - # f = File.open('t.txt') - # # Chomp the lines. - # f.gets(chomp: true) # => "First line" - # f.gets(chomp: true) # => "Second line" - # f.gets(chomp: true) # => "" - # f.gets(chomp: true) # => "Fourth line" - # f.gets(chomp: true) # => "Fifth line" - # f.gets(chomp: true) # => nil - # f.close + # Returns an Enumerator if no block is given. # - def gets: (string? sep, ?int limit, ?chomp: boolish) -> String? - | (?int limit, ?chomp: boolish) -> String? + def self.foreach: (string | _ToPath path, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) { (String line) -> void } -> nil + | (string | _ToPath path, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> ::Enumerator[String, nil] # - # Creates and returns a new IO object (file stream) from a file descriptor. - # - # IO.new may be useful for interaction with low-level libraries. For - # higher-level interactions, it may be simpler to create the file stream using - # File.open. + # Creates a pair of pipe endpoints, `read_io` and `write_io`, connected to each + # other. # - # Argument `fd` must be a valid file descriptor (integer): + # If argument `enc_string` is given, it must be a string containing one of: # - # path = 't.tmp' - # fd = IO.sysopen(path) # => 3 - # IO.new(fd) # => # + # * The name of the encoding to be used as the external encoding. + # * The colon-separated names of two encodings to be used as the external and + # internal encodings. # - # The new IO object does not inherit encoding (because the integer file - # descriptor does not have an encoding): # - # fd = IO.sysopen('t.rus', 'rb') - # io = IO.new(fd) - # io.external_encoding # => # # Not ASCII-8BIT. + # If argument `int_enc` is given, it must be an Encoding object or encoding name + # string that specifies the internal encoding to be used; if argument `ext_enc` + # is also given, it must be an Encoding object or encoding name string that + # specifies the external encoding to be used. # - # Optional argument `mode` (defaults to 'r') must specify a valid mode; see - # [Access Modes](rdoc-ref:File@Access+Modes): + # The string read from `read_io` is tagged with the external encoding; if an + # internal encoding is also specified, the string is converted to, and tagged + # with, that encoding. # - # IO.new(fd, 'w') # => # - # IO.new(fd, File::WRONLY) # => # + # If any encoding is specified, optional hash arguments specify the conversion + # option. # # Optional keyword arguments `opts` specify: # # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # * [Encoding Options](rdoc-ref:encodings.rdoc@Encoding+Options). # # - # Examples: + # With no block given, returns the two endpoints in an array: # - # IO.new(fd, internal_encoding: nil) # => # - # IO.new(fd, autoclose: true) # => # + # IO.pipe # => [#, #] # - def initialize: ( int fd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> void - - # - # Returns a string representation of `self`: + # With a block given, calls the block with the two endpoints; closes both + # endpoints and returns the value of the block: # - # f = File.open('t.txt') - # f.inspect # => "#" - # f.close + # IO.pipe {|read_io, write_io| p read_io; p write_io } # - def inspect: () -> String - - # - # Returns the Encoding object that represents the encoding of the internal - # string, if conversion is specified, or `nil` otherwise. + # Output: # - # See [Encodings](rdoc-ref:File@Encodings). + # # + # # # - def internal_encoding: () -> Encoding - - # - # Invokes Posix system call [ioctl(2)](https://linux.die.net/man/2/ioctl), which - # issues a low-level command to an I/O device. + # Not available on all platforms. # - # Issues a low-level command to an I/O device. The arguments and returned value - # are platform-dependent. The effect of the call is platform-dependent. + # In the example below, the two processes close the ends of the pipe that they + # are not using. This is not just a cosmetic nicety. The read end of a pipe will + # not generate an end of file condition if there are any writers with the pipe + # still open. In the case of the parent process, the `rd.read` will never return + # if it does not first issue a `wr.close`: # - # If argument `argument` is an integer, it is passed directly; if it is a - # string, it is interpreted as a binary sequence of bytes. + # rd, wr = IO.pipe # - # Not implemented on all platforms. + # if fork + # wr.close + # puts "Parent got: <#{rd.read}>" + # rd.close + # Process.wait + # else + # rd.close + # puts 'Sending message to parent' + # wr.write "Hi Dad" + # wr.close + # end # - def ioctl: (Integer integer_cmd, String | Integer argument) -> Integer - - # - # Returns `true` if the stream is associated with a terminal device (tty), - # `false` otherwise: + # *produces:* # - # f = File.new('t.txt').isatty #=> false - # f.close - # f = File.new('/dev/tty').isatty #=> true - # f.close + # Sending message to parent + # Parent got: # - def isatty: () -> bool + def self.pipe: (?String | Encoding | nil ext_or_ext_int_enc, ?String | Encoding | nil int_enc, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> [IO, IO] + | [X] (?String | Encoding | nil ext_or_ext_int_enc, ?String | Encoding | nil int_enc, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) { (IO read_io, IO write_io) -> X } -> X # - # Returns the current line number for the stream; see [Line - # Number](rdoc-ref:IO@Line+Number). + # Opens the stream, reads and returns some or all of its content, and closes the + # stream; returns `nil` if no bytes were read. # - def lineno: () -> Integer - - # - # Sets and returns the line number for the stream; see [Line - # Number](rdoc-ref:IO@Line+Number). + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). # - def lineno=: (Integer integer) -> Integer - - # - # Returns the path associated with the IO, or `nil` if there is no path - # associated with the IO. It is not guaranteed that the path exists on the - # filesystem. + # The first argument must be a string that is the path to a file. # - # $stdin.path # => "" + # With only argument `path` given, reads in text mode and returns the entire + # content of the file at the given path: # - # File.open("testfile") {|f| f.path} # => "testfile" + # IO.read('t.txt') + # # => "First line\nSecond line\n\nThird line\nFourth line\n" # - def path: () -> String? - - # - # Returns the process ID of a child process associated with the stream, which - # will have been set by IO#popen, or `nil` if the stream was not created by - # IO#popen: + # On Windows, text mode can terminate reading and leave bytes in the file unread + # when encountering certain special bytes. Consider using IO.binread if all + # bytes in the file should be read. # - # pipe = IO.popen("-") - # if pipe - # $stderr.puts "In parent, child pid is #{pipe.pid}" - # else - # $stderr.puts "In child, pid is #{$$}" - # end + # With argument `length`, returns `length` bytes if available: # - # Output: + # IO.read('t.txt', 7) # => "First l" + # IO.read('t.txt', 700) + # # => "First line\r\nSecond line\r\n\r\nFourth line\r\nFifth line\r\n" # - # In child, pid is 26209 - # In parent, child pid is 26209 + # With arguments `length` and `offset`, returns `length` bytes if available, + # beginning at the given `offset`: # - def pid: () -> Integer - - # - # Seeks to the given `new_position` (in bytes); see - # [Position](rdoc-ref:IO@Position): + # IO.read('t.txt', 10, 2) # => "rst line\nS" + # IO.read('t.txt', 10, 200) # => nil # - # f = File.open('t.txt') - # f.tell # => 0 - # f.pos = 20 # => 20 - # f.tell # => 20 - # f.close + # Optional keyword arguments `opts` specify: # - # Related: IO#seek, IO#tell. + # * [Open Options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). # - def pos=: (Integer new_position) -> Integer + def self.read: (String name, ?Integer length, ?Integer offset, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> String # - # Writes the given objects to the stream; returns `nil`. Appends the output - # record separator `$OUTPUT_RECORD_SEPARATOR` (`$\`), if it is not `nil`. See - # [Line IO](rdoc-ref:IO@Line+IO). - # - # With argument `objects` given, for each object: - # - # * Converts via its method `to_s` if not a string. - # * Writes to the stream. - # * If not the last object, writes the output field separator - # `$OUTPUT_FIELD_SEPARATOR` (`$,`) if it is not `nil`. - # - # - # With default separators: + # Returns an array of all lines read from the stream. # - # f = File.open('t.tmp', 'w+') - # objects = [0, 0.0, Rational(0, 1), Complex(0, 0), :zero, 'zero'] - # p $OUTPUT_RECORD_SEPARATOR - # p $OUTPUT_FIELD_SEPARATOR - # f.print(*objects) - # f.rewind - # p f.read - # f.close + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). # - # Output: + # The first argument must be a string that is the path to a file. # - # nil - # nil - # "00.00/10+0izerozero" + # With only argument `path` given, parses lines from the file at the given + # `path`, as determined by the default line separator, and returns those lines + # in an array: # - # With specified separators: + # IO.readlines('t.txt') + # # => ["First line\n", "Second line\n", "\n", "Third line\n", "Fourth line\n"] # - # $\ = "\n" - # $, = ',' - # f.rewind - # f.print(*objects) - # f.rewind - # p f.read + # With argument `sep` given, parses lines as determined by that line separator + # (see [Line Separator](rdoc-ref:IO@Line+Separator)): # - # Output: + # # Ordinary separator. + # IO.readlines('t.txt', 'li') + # # =>["First li", "ne\nSecond li", "ne\n\nThird li", "ne\nFourth li", "ne\n"] + # # Get-paragraphs separator. + # IO.readlines('t.txt', '') + # # => ["First line\nSecond line\n\n", "Third line\nFourth line\n"] + # # Get-all separator. + # IO.readlines('t.txt', nil) + # # => ["First line\nSecond line\n\nThird line\nFourth line\n"] # - # "0,0.0,0/1,0+0i,zero,zero\n" + # With argument `limit` given, parses lines as determined by the default line + # separator and the given line-length limit (see [Line + # Limit](rdoc-ref:IO@Line+Limit)): # - # With no argument given, writes the content of `$_` (which is usually the most - # recent user input): + # IO.readlines('t.txt', 7) + # # => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"] # - # f = File.open('t.tmp', 'w+') - # gets # Sets $_ to the most recent user input. - # f.print - # f.close + # With arguments `sep` and `limit` given, parses lines as determined by the + # given line separator and the given line-length limit (see [Line Separator and + # Line Limit](rdoc-ref:IO@Line+Separator+and+Line+Limit)): # - def print: (*untyped objects) -> nil - - # - # Formats and writes `objects` to the stream. + # Optional keyword arguments `opts` specify: # - # For details on `format_string`, see [Format - # Specifications](rdoc-ref:format_specifications.rdoc). + # * [Open Options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # * [Line Options](rdoc-ref:IO@Line+IO). # - def printf: (String format_string, *untyped objects) -> nil + def self.readlines: (String | _ToPath name, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> ::Array[String] # - # Writes a character to the stream. See [Character - # IO](rdoc-ref:IO@Character+IO). + # Invokes system call [select(2)](https://linux.die.net/man/2/select), which + # monitors multiple file descriptors, waiting until one or more of the file + # descriptors becomes ready for some class of I/O operation. # - # If `object` is numeric, converts to integer if necessary, then writes the - # character whose code is the least significant byte; if `object` is a string, - # writes the first character: + # Not implemented on all platforms. # - # $stdout.putc "A" - # $stdout.putc 65 + # Each of the arguments `read_ios`, `write_ios`, and `error_ios` is an array of + # IO objects. # - # Output: + # Argument `timeout` is an integer timeout interval in seconds. # - # AA + # The method monitors the IO objects given in all three arrays, waiting for some + # to be ready; returns a 3-element array whose elements are: # - def putc: (Numeric | String object) -> (Numeric | String) - - # - # Writes the given `objects` to the stream, which must be open for writing; - # returns `nil`.\ Writes a newline after each that does not already end with a - # newline sequence. If called without arguments, writes a newline. See [Line - # IO](rdoc-ref:IO@Line+IO). + # * An array of the objects in `read_ios` that are ready for reading. + # * An array of the objects in `write_ios` that are ready for writing. + # * An array of the objects in `error_ios` have pending exceptions. # - # Note that each added newline is the character `"\n", not the output - # record separator ($\`). # - # Treatment for each object: + # If no object becomes ready within the given `timeout`, `nil` is returned. # - # * String: writes the string. - # * Neither string nor array: writes `object.to_s`. - # * Array: writes each element of the array; arrays may be nested. + # IO.select peeks the buffer of IO objects for testing readability. If the IO + # buffer is not empty, IO.select immediately notifies readability. This "peek" + # only happens for IO objects. It does not happen for IO-like objects such as + # OpenSSL::SSL::SSLSocket. # + # The best way to use IO.select is invoking it after non-blocking methods such + # as #read_nonblock, #write_nonblock, etc. The methods raise an exception which + # is extended by IO::WaitReadable or IO::WaitWritable. The modules notify how + # the caller should wait with IO.select. If IO::WaitReadable is raised, the + # caller should wait for reading. If IO::WaitWritable is raised, the caller + # should wait for writing. # - # To keep these examples brief, we define this helper method: + # So, blocking read (#readpartial) can be emulated using #read_nonblock and + # IO.select as follows: # - # def show(*objects) - # # Puts objects to file. - # f = File.new('t.tmp', 'w+') - # f.puts(objects) - # # Return file content. - # f.rewind - # p f.read - # f.close + # begin + # result = io_like.read_nonblock(maxlen) + # rescue IO::WaitReadable + # IO.select([io_like]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [io_like]) + # retry # end # - # # Strings without newlines. - # show('foo', 'bar', 'baz') # => "foo\nbar\nbaz\n" - # # Strings, some with newlines. - # show("foo\n", 'bar', "baz\n") # => "foo\nbar\nbaz\n" - # - # # Neither strings nor arrays: - # show(0, 0.0, Rational(0, 1), Complex(9, 0), :zero) - # # => "0\n0.0\n0/1\n9+0i\nzero\n" + # Especially, the combination of non-blocking methods and IO.select is preferred + # for IO like objects such as OpenSSL::SSL::SSLSocket. It has #to_io method to + # return underlying IO object. IO.select calls #to_io to obtain the file + # descriptor to wait. # - # # Array of strings. - # show(['foo', "bar\n", 'baz']) # => "foo\nbar\nbaz\n" - # # Nested arrays. - # show([[[0, 1], 2, 3], 4, 5]) # => "0\n1\n2\n3\n4\n5\n" + # This means that readability notified by IO.select doesn't mean readability + # from OpenSSL::SSL::SSLSocket object. # - def puts: (*untyped objects) -> nil - - # - # Reads bytes from the stream; the stream must be opened for reading (see - # [Access Modes](rdoc-ref:File@Access+Modes)): + # The most likely situation is that OpenSSL::SSL::SSLSocket buffers some data. + # IO.select doesn't see the buffer. So IO.select can block when + # OpenSSL::SSL::SSLSocket#readpartial doesn't block. # - # * If `maxlen` is `nil`, reads all bytes using the stream's data mode. - # * Otherwise reads up to `maxlen` bytes in binary mode. + # However, several more complicated situations exist. # + # SSL is a protocol which is sequence of records. The record consists of + # multiple bytes. So, the remote side of SSL sends a partial record, IO.select + # notifies readability but OpenSSL::SSL::SSLSocket cannot decrypt a byte and + # OpenSSL::SSL::SSLSocket#readpartial will block. # - # Returns a string (either a new string or the given `out_string`) containing - # the bytes read. The encoding of the string depends on both `maxLen` and - # `out_string`: + # Also, the remote side can request SSL renegotiation which forces the local SSL + # engine to write some data. This means OpenSSL::SSL::SSLSocket#readpartial may + # invoke #write system call and it can block. In such a situation, + # OpenSSL::SSL::SSLSocket#read_nonblock raises IO::WaitWritable instead of + # blocking. So, the caller should wait for ready for writability as above + # example. # - # * `maxlen` is `nil`: uses internal encoding of `self` (regardless of whether - # `out_string` was given). - # * `maxlen` not `nil`: + # The combination of non-blocking methods and IO.select is also useful for + # streams such as tty, pipe socket socket when multiple processes read from a + # stream. # - # * `out_string` given: encoding of `out_string` not modified. - # * `out_string` not given: ASCII-8BIT is used. + # Finally, Linux kernel developers don't guarantee that readability of select(2) + # means readability of following read(2) even for a single process; see + # [select(2)](https://linux.die.net/man/2/select) # + # Invoking IO.select before IO#readpartial works well as usual. However it is + # not the best way to use IO.select. # + # The writability notified by select(2) doesn't show how many bytes are + # writable. IO#write method blocks until given whole string is written. So, + # `IO#write(two or more bytes)` can block after writability is notified by + # IO.select. IO#write_nonblock is required to avoid the blocking. # - # **Without Argument `out_string`** + # Blocking write (#write) can be emulated using #write_nonblock and IO.select as + # follows: IO::WaitReadable should also be rescued for SSL renegotiation in + # OpenSSL::SSL::SSLSocket. # - # When argument `out_string` is omitted, the returned value is a new string: + # while 0 < string.bytesize + # begin + # written = io_like.write_nonblock(string) + # rescue IO::WaitReadable + # IO.select([io_like]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [io_like]) + # retry + # end + # string = string.byteslice(written..-1) + # end # - # f = File.new('t.txt') - # f.read - # # => "First line\nSecond line\n\nFourth line\nFifth line\n" - # f.rewind - # f.read(30) # => "First line\r\nSecond line\r\n\r\nFou" - # f.read(30) # => "rth line\r\nFifth line\r\n" - # f.read(30) # => nil - # f.close + # Example: # - # If `maxlen` is zero, returns an empty string. + # rp, wp = IO.pipe + # mesg = "ping " + # 100.times { + # # IO.select follows IO#read. Not the best way to use IO.select. + # rs, ws, = IO.select([rp], [wp]) + # if r = rs[0] + # ret = r.read(5) + # print ret + # case ret + # when /ping/ + # mesg = "pong\n" + # when /pong/ + # mesg = "ping " + # end + # end + # if w = ws[0] + # w.write(mesg) + # end + # } # - # ** With Argument `out_string`** + # Output: # - # When argument `out_string` is given, the returned value is `out_string`, whose - # content is replaced: + # ping pong + # ping pong + # ping pong + # (snipped) + # ping # - # f = File.new('t.txt') - # s = 'foo' # => "foo" - # f.read(nil, s) # => "First line\nSecond line\n\nFourth line\nFifth line\n" - # s # => "First line\nSecond line\n\nFourth line\nFifth line\n" - # f.rewind - # s = 'bar' - # f.read(30, s) # => "First line\r\nSecond line\r\n\r\nFou" - # s # => "First line\r\nSecond line\r\n\r\nFou" - # s = 'baz' - # f.read(30, s) # => "rth line\r\nFifth line\r\n" - # s # => "rth line\r\nFifth line\r\n" - # s = 'bat' - # f.read(30, s) # => nil - # s # => "" - # f.close + def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] + | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Numeric? timeout) -> [ Array[X], Array[Y], Array[Z] ]? + + # + # Opens the file at the given path with the given mode and permissions; returns + # the integer file descriptor. # - # Note that this method behaves like the fread() function in C. This means it - # retries to invoke read(2) system calls to read data with the specified maxlen - # (or until EOF). + # If the file is to be readable, it must exist; if the file is to be writable + # and does not exist, it is created with the given permissions: # - # This behavior is preserved even if the stream is in non-blocking mode. (This - # method is non-blocking-flag insensitive as other methods.) + # File.write('t.tmp', '') # => 0 + # IO.sysopen('t.tmp') # => 8 + # IO.sysopen('t.tmp', 'w') # => 9 # - # If you need the behavior like a single read(2) system call, consider - # #readpartial, #read_nonblock, and #sysread. + def self.sysopen: (String path, ?String mode, ?String perm) -> Integer + + # + # Attempts to convert `object` into an IO object via method `to_io`; returns the + # new IO object if successful, or `nil` otherwise: # - # Related: IO#write. + # IO.try_convert(STDOUT) # => #> + # IO.try_convert(ARGF) # => #> + # IO.try_convert('STDOUT') # => nil # - def read: (?nil, ?string outbuf) -> String - | (int? length, ?string outbuf) -> String? + def self.try_convert: (_ToIO obj) -> IO + | (untyped obj) -> IO? # - # Reads at most *maxlen* bytes from *ios* using the read(2) system call after - # O_NONBLOCK is set for the underlying file descriptor. + # Opens the stream, writes the given `data` to it, and closes the stream; + # returns the number of bytes written. # - # If the optional *outbuf* argument is present, it must reference a String, - # which will receive the data. The *outbuf* will contain only the received data - # after the method call even if it is not empty at the beginning. + # When called from class IO (but not subclasses of IO), this method has + # potential security vulnerabilities if called with untrusted input; see + # [Command Injection](rdoc-ref:command_injection.rdoc). # - # read_nonblock just calls the read(2) system call. It causes all errors the - # read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The caller - # should care such errors. + # The first argument must be a string that is the path to a file. # - # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by - # IO::WaitReadable. So IO::WaitReadable can be used to rescue the exceptions for - # retrying read_nonblock. + # With only argument `path` given, writes the given `data` to the file at that + # path: # - # read_nonblock causes EOFError on EOF. + # IO.write('t.tmp', 'abc') # => 3 + # File.read('t.tmp') # => "abc" # - # On some platforms, such as Windows, non-blocking mode is not supported on IO - # objects other than sockets. In such cases, Errno::EBADF will be raised. + # If `offset` is zero (the default), the file is overwritten: # - # If the read byte buffer is not empty, read_nonblock reads from the buffer like - # readpartial. In this case, the read(2) system call is not called. + # IO.write('t.tmp', 'A') # => 1 + # File.read('t.tmp') # => "A" # - # When read_nonblock raises an exception kind of IO::WaitReadable, read_nonblock - # should not be called until io is readable for avoiding busy loop. This can be - # done as follows. + # If `offset` in within the file content, the file is partly overwritten: # - # # emulates blocking read (readpartial). - # begin - # result = io.read_nonblock(maxlen) - # rescue IO::WaitReadable - # IO.select([io]) - # retry - # end + # IO.write('t.tmp', 'abcdef') # => 3 + # File.read('t.tmp') # => "abcdef" + # # Offset within content. + # IO.write('t.tmp', '012', 2) # => 3 + # File.read('t.tmp') # => "ab012f" # - # Although IO#read_nonblock doesn't raise IO::WaitWritable. - # OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. If IO and SSL - # should be used polymorphically, IO::WaitWritable should be rescued too. See - # the document of OpenSSL::Buffering#read_nonblock for sample code. + # If `offset` is outside the file content, the file is padded with null + # characters `"\u0000"`: # - # Note that this method is identical to readpartial except the non-blocking flag - # is set. + # IO.write('t.tmp', 'xyz', 10) # => 3 + # File.read('t.tmp') # => "ab012f\u0000\u0000\u0000\u0000xyz" # - # By specifying a keyword argument *exception* to `false`, you can indicate that - # read_nonblock should not raise an IO::WaitReadable exception, but return the - # symbol `:wait_readable` instead. At EOF, it will return nil instead of raising - # EOFError. + # Optional keyword arguments `opts` specify: # - def read_nonblock: (int len, ?string buf, ?exception: true) -> String - | (int len, ?string buf, exception: false) -> (String | :wait_readable | nil) + # * [Open Options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # + def self.write: (String path, _ToS data, ?Integer offset, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> Integer # - # Reads and returns the next byte (in range 0..255) from the stream; raises - # EOFError if already at end-of-stream. See [Byte IO](rdoc-ref:IO@Byte+IO). - # - # f = File.open('t.txt') - # f.readbyte # => 70 - # f.close - # f = File.open('t.rus') - # f.readbyte # => 209 - # f.close - # - # Related: IO#getbyte (will not raise EOFError). + # Synonym for IO.new. # - def readbyte: () -> Integer + alias self.for_fd self.new # - # Reads and returns the next 1-character string from the stream; raises EOFError - # if already at end-of-stream. See [Character IO](rdoc-ref:IO@Character+IO). - # - # f = File.open('t.txt') - # f.readchar # => "F" - # f.close - # f = File.open('t.rus') - # f.readchar.ord # => 1090 - # f.close - # - # Related: IO#getc (will not raise EOFError). + # Creates a new IO object, via IO.new with the given arguments. # - def readchar: () -> String - - # - # Reads a line as with IO#gets, but raises EOFError if already at end-of-stream. + # With no block given, returns the IO object. # - # Optional keyword argument `chomp` specifies whether line separators are to be - # omitted. + # With a block given, calls the block with the IO object and returns the block's + # value. # - def readline: (?String sep, ?Integer limit) -> String + def self.open: (int fd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> instance + | [X] (int fd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) { (instance) -> X } -> X # - # Reads and returns all remaining line from the stream; does not modify `$_`. - # See [Line IO](rdoc-ref:IO@Line+IO). + # Creates and returns a new IO object (file stream) from a file descriptor. # - # With no arguments given, returns lines as determined by line separator `$/`, - # or `nil` if none: + # IO.new may be useful for interaction with low-level libraries. For + # higher-level interactions, it may be simpler to create the file stream using + # File.open. # - # f = File.new('t.txt') - # f.readlines - # # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] - # f.readlines # => [] - # f.close - # - # With only string argument `sep` given, returns lines as determined by line - # separator `sep`, or `nil` if none; see [Line - # Separator](rdoc-ref:IO@Line+Separator): + # Argument `fd` must be a valid file descriptor (integer): # - # f = File.new('t.txt') - # f.readlines('li') - # # => ["First li", "ne\nSecond li", "ne\n\nFourth li", "ne\nFifth li", "ne\n"] - # f.close + # path = 't.tmp' + # fd = IO.sysopen(path) # => 3 + # IO.new(fd) # => # # - # The two special values for `sep` are honored: + # The new IO object does not inherit encoding (because the integer file + # descriptor does not have an encoding): # - # f = File.new('t.txt') - # # Get all into one string. - # f.readlines(nil) - # # => ["First line\nSecond line\n\nFourth line\nFifth line\n"] - # # Get paragraphs (up to two line separators). - # f.rewind - # f.readlines('') - # # => ["First line\nSecond line\n\n", "Fourth line\nFifth line\n"] - # f.close + # fd = IO.sysopen('t.rus', 'rb') + # io = IO.new(fd) + # io.external_encoding # => # # Not ASCII-8BIT. # - # With only integer argument `limit` given, limits the number of bytes in each - # line; see [Line Limit](rdoc-ref:IO@Line+Limit): + # Optional argument `mode` (defaults to 'r') must specify a valid mode; see + # [Access Modes](rdoc-ref:File@Access+Modes): # - # f = File.new('t.txt') - # f.readlines(8) - # # => ["First li", "ne\n", "Second l", "ine\n", "\n", "Fourth l", "ine\n", "Fifth li", "ne\n"] - # f.close + # IO.new(fd, 'w') # => # + # IO.new(fd, File::WRONLY) # => # # - # With arguments `sep` and `limit` given, combines the two behaviors: + # Optional keyword arguments `opts` specify: # - # * Returns lines as determined by line separator `sep`. - # * But returns no more bytes in a line than are allowed by the limit. + # * [Open Options](rdoc-ref:IO@Open+Options). + # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). # # - # Optional keyword argument `chomp` specifies whether line separators are to be - # omitted: + # Examples: # - # f = File.new('t.txt') - # f.readlines(chomp: true) - # # => ["First line", "Second line", "", "Fourth line", "Fifth line"] - # f.close + # IO.new(fd, internal_encoding: nil) # => # + # IO.new(fd, autoclose: true) # => # # - def readlines: (?String sep, ?Integer limit) -> ::Array[String] + def initialize: ( + int fd, + ?open_mode mode, + ?autoclose: boolish, # NOTE: only `false`, not `nil`, disables autoclose + ?path: string?, + ?mode: open_mode, + ?flags: int?, + ?textmode: boolish, + ?binmode: boolish, + ?encoding: encoding?, + ?external_encoding: encoding?, + ?internal_encoding: encoding?, + ?replace: string?, + ?fallback: Proc | Method | Encoding::_EncodeFallbackAref, + ?invalid: :replace | nil, + ?undef: :replace | nil, + ?xml: :text | :attr | nil, + ?newline: :universal | :crlf | :cr | :lf | nil, + ?universal_newline: boolish, + ?crlf_newline: boolish, + ?cr_newline: boolish, + ?lf_newline: boolish, + **untyped + ) -> self + + def initialize_copy: (io stream) -> self # - # Reads up to `maxlen` bytes from the stream; returns a string (either a new - # string or the given `out_string`). Its encoding is: - # - # * The unchanged encoding of `out_string`, if `out_string` is given. - # * ASCII-8BIT, otherwise. - # - # * Contains `maxlen` bytes from the stream, if available. - # * Otherwise contains all available bytes, if any available. - # * Otherwise is an empty string. - # - # - # With the single non-negative integer argument `maxlen` given, returns a new - # string: - # - # f = File.new('t.txt') - # f.readpartial(20) # => "First line\nSecond l" - # f.readpartial(20) # => "ine\n\nFourth line\n" - # f.readpartial(20) # => "Fifth line\n" - # f.readpartial(20) # Raises EOFError. - # f.close + # Writes the given `object` to `self`, which must be opened for writing (see + # [Access Modes](rdoc-ref:File@Access+Modes)); returns `self`; if `object` is + # not a string, it is converted via method `to_s`: # - # With both argument `maxlen` and string argument `out_string` given, returns - # modified `out_string`: + # $stdout << 'Hello' << ', ' << 'World!' << "\n" + # $stdout << 'foo' << :bar << 2 << "\n" # - # f = File.new('t.txt') - # s = 'foo' - # f.readpartial(20, s) # => "First line\nSecond l" - # s = 'bar' - # f.readpartial(0, s) # => "" - # f.close + # Output: # - # This method is useful for a stream such as a pipe, a socket, or a tty. It - # blocks only when no data is immediately available. This means that it blocks - # only when *all* of the following are true: + # Hello, World! + # foobar2 # - # * The byte buffer in the stream is empty. - # * The content of the stream is empty. - # * The stream is not at EOF. + def <<: (_ToS obj) -> self + + # + # Invokes Posix system call + # [posix_fadvise(2)](https://linux.die.net/man/2/posix_fadvise), which announces + # an intention to access data from the current file in a particular manner. # + # The arguments and results are platform-dependent. # - # When blocked, the method waits for either more data or EOF on the stream: + # The relevant data is specified by: # - # * If more data is read, the method returns the data. - # * If EOF is reached, the method raises EOFError. + # * `offset`: The offset of the first byte of data. + # * `len`: The number of bytes to be accessed; if `len` is zero, or is larger + # than the number of bytes remaining, all remaining bytes will be accessed. # # - # When not blocked, the method responds immediately: + # Argument `advice` is one of the following symbols: # - # * Returns data from the buffer if there is any. - # * Otherwise returns data from the stream if there is any. - # * Otherwise raises EOFError if the stream has reached EOF. + # * `:normal`: The application has no advice to give about its access pattern + # for the specified data. If no advice is given for an open file, this is + # the default assumption. + # * `:sequential`: The application expects to access the specified data + # sequentially (with lower offsets read before higher ones). + # * `:random`: The specified data will be accessed in random order. + # * `:noreuse`: The specified data will be accessed only once. + # * `:willneed`: The specified data will be accessed in the near future. + # * `:dontneed`: The specified data will not be accessed in the near future. # # - # Note that this method is similar to sysread. The differences are: + # Not implemented on all platforms. # - # * If the byte buffer is not empty, read from the byte buffer instead of - # "sysread for buffered IO (IOError)". - # * It doesn't cause Errno::EWOULDBLOCK and Errno::EINTR. When readpartial - # meets EWOULDBLOCK and EINTR by read system call, readpartial retries the - # system call. + def advise: (advice, ?int? offset, ?int? len) -> nil + + # How an `IO` intends to access its data; used in `IO#advise`. + type advice = :normal | :sequential | :random | :noreuse | :willneed | :dontneed + + # + # Sets auto-close flag. # + # f = open("/dev/null") + # IO.for_fd(f.fileno) + # # ... + # f.gets # may cause Errno::EBADF # - # The latter means that readpartial is non-blocking-flag insensitive. It blocks - # on the situation IO#sysread causes Errno::EWOULDBLOCK as if the fd is blocking - # mode. + # f = open("/dev/null") + # IO.for_fd(f.fileno).autoclose = false + # # ... + # f.gets # won't cause Errno::EBADF # - # Examples: + def autoclose=: [T] (T enabled) -> T + + # + # Returns `true` if the underlying file descriptor of *ios* will be closed + # automatically at its finalization, otherwise `false`. # - # # # Returned Buffer Content Pipe Content - # r, w = IO.pipe # - # w << 'abc' # "" "abc". - # r.readpartial(4096) # => "abc" "" "" - # r.readpartial(4096) # (Blocks because buffer and pipe are empty.) + def autoclose?: () -> bool + + # + # Sets the stream's data mode as binary (see [Data + # Mode](rdoc-ref:File@Data+Mode)). # - # # # Returned Buffer Content Pipe Content - # r, w = IO.pipe # - # w << 'abc' # "" "abc" - # w.close # "" "abc" EOF - # r.readpartial(4096) # => "abc" "" EOF - # r.readpartial(4096) # raises EOFError + # A stream's data mode may not be changed from binary to text. # - # # # Returned Buffer Content Pipe Content - # r, w = IO.pipe # - # w << "abc\ndef\n" # "" "abc\ndef\n" - # r.gets # => "abc\n" "def\n" "" - # w << "ghi\n" # "def\n" "ghi\n" - # r.readpartial(4096) # => "def\n" "" "ghi\n" - # r.readpartial(4096) # => "ghi\n" "" "" + def binmode: () -> self + + # + # Returns `true` if the stream is on binary mode, `false` otherwise. See [Data + # Mode](rdoc-ref:File@Data+Mode). # - def readpartial: (int maxlen, ?string outbuf) -> String + def binmode?: () -> bool # - # Reassociates the stream with another stream, which may be of a different - # class. This method may be used to redirect an existing stream to a new - # destination. + # Closes the stream for both reading and writing if open for either or both; + # returns `nil`. See [Open and Closed + # Streams](rdoc-ref:IO@Open+and+Closed+Streams). # - # With argument `other_io` given, reassociates with that stream: + # If the stream is open for writing, flushes any buffered writes to the + # operating system before closing. # - # # Redirect $stdin from a file. - # f = File.open('t.txt') - # $stdin.reopen(f) - # f.close + # If the stream was opened by IO.popen, sets global variable `$?` (child exit + # status). # - # # Redirect $stdout to a file. - # f = File.open('t.tmp', 'w') - # $stdout.reopen(f) - # f.close + # Example: # - # With argument `path` given, reassociates with a new stream to that file path: + # IO.popen('ruby', 'r+') do |pipe| + # puts pipe.closed? + # pipe.close + # puts $? + # puts pipe.closed? + # end # - # $stdin.reopen('t.txt') - # $stdout.reopen('t.tmp', 'w') + # Output: # - # Optional keyword arguments `opts` specify: + # false + # pid 13760 exit 0 + # true # - # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # Related: IO#close_read, IO#close_write, IO#closed?. # - def reopen: (IO other_IO_or_path) -> IO - | (String other_IO_or_path, ?String mode_str) -> IO + def close: () -> nil # - # Repositions the stream to its beginning, setting both the position and the - # line number to zero; see [Position](rdoc-ref:IO@Position) and [Line - # Number](rdoc-ref:IO@Line+Number): - # - # f = File.open('t.txt') - # f.tell # => 0 - # f.lineno # => 0 - # f.gets # => "First line\n" - # f.tell # => 12 - # f.lineno # => 1 - # f.rewind # => 0 - # f.tell # => 0 - # f.lineno # => 0 - # f.close + # Sets a close-on-exec flag. # - # Note that this method cannot be used with streams such as pipes, ttys, and - # sockets. + # f = open("/dev/null") + # f.close_on_exec = true + # system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory + # f.closed? #=> false # - def rewind: () -> Integer - + # Ruby sets close-on-exec flags of all file descriptors by default since Ruby + # 2.0.0. So you don't need to set by yourself. Also, unsetting a close-on-exec + # flag can cause file descriptor leak if another thread use fork() and exec() + # (via system() method for example). If you really needs file descriptor + # inheritance to child process, use spawn()'s argument such as fd=>fd. + # + def close_on_exec=: (boolish enabled) -> nil + # - # Seeks to the position given by integer `offset` (see - # [Position](rdoc-ref:IO@Position)) and constant `whence`, which is one of: - # - # * `:CUR` or `IO::SEEK_CUR`: Repositions the stream to its current position - # plus the given `offset`: - # - # f = File.open('t.txt') - # f.tell # => 0 - # f.seek(20, :CUR) # => 0 - # f.tell # => 20 - # f.seek(-10, :CUR) # => 0 - # f.tell # => 10 - # f.close - # - # * `:END` or `IO::SEEK_END`: Repositions the stream to its end plus the given - # `offset`: - # - # f = File.open('t.txt') - # f.tell # => 0 - # f.seek(0, :END) # => 0 # Repositions to stream end. - # f.tell # => 52 - # f.seek(-20, :END) # => 0 - # f.tell # => 32 - # f.seek(-40, :END) # => 0 - # f.tell # => 12 - # f.close - # - # * `:SET` or `IO:SEEK_SET`: Repositions the stream to the given `offset`: - # - # f = File.open('t.txt') - # f.tell # => 0 - # f.seek(20, :SET) # => 0 - # f.tell # => 20 - # f.seek(40, :SET) # => 0 - # f.tell # => 40 - # f.close - # + # Returns `true` if the stream will be closed on exec, `false` otherwise: # - # Related: IO#pos=, IO#tell. + # f = File.open('t.txt') + # f.close_on_exec? # => true + # f.close_on_exec = false + # f.close_on_exec? # => false + # f.close # - def seek: (Integer amount, ?Integer whence) -> Integer + def close_on_exec?: () -> bool # - # See [Encodings](rdoc-ref:File@Encodings). + # Closes the stream for reading if open for reading; returns `nil`. See [Open + # and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). + # + # If the stream was opened by IO.popen and is also closed for writing, sets + # global variable `$?` (child exit status). # - # Argument `ext_enc`, if given, must be an Encoding object or a String with the - # encoding name; it is assigned as the encoding for the stream. + # Example: # - # Argument `int_enc`, if given, must be an Encoding object or a String with the - # encoding name; it is assigned as the encoding for the internal string. + # IO.popen('ruby', 'r+') do |pipe| + # puts pipe.closed? + # pipe.close_write + # puts pipe.closed? + # pipe.close_read + # puts $? + # puts pipe.closed? + # end # - # Argument `'ext_enc:int_enc'`, if given, is a string containing two - # colon-separated encoding names; corresponding Encoding objects are assigned as - # the external and internal encodings for the stream. + # Output: # - # If the external encoding of a string is binary/ASCII-8BIT, the internal - # encoding of the string is set to nil, since no transcoding is needed. + # false + # false + # pid 14748 exit 0 + # true # - # Optional keyword arguments `enc_opts` specify [Encoding - # options](rdoc-ref:encodings.rdoc@Encoding+Options). + # Related: IO#close, IO#close_write, IO#closed?. # - def set_encoding: (?String | Encoding ext_or_ext_int_enc) -> self - | (?String | Encoding ext_or_ext_int_enc, ?String | Encoding int_enc) -> self + def close_read: () -> nil # - # If the stream begins with a BOM ([byte order - # marker](https://en.wikipedia.org/wiki/Byte_order_mark)), consumes the BOM and - # sets the external encoding accordingly; returns the result encoding if found, - # or `nil` otherwise: - # - # File.write('t.tmp', "\u{FEFF}abc") - # io = File.open('t.tmp', 'rb') - # io.set_encoding_by_bom # => # - # io.close + # Closes the stream for writing if open for writing; returns `nil`. See [Open + # and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). # - # File.write('t.tmp', 'abc') - # io = File.open('t.tmp', 'rb') - # io.set_encoding_by_bom # => nil - # io.close + # Flushes any buffered writes to the operating system before closing. # - # Raises an exception if the stream is not binmode or its encoding has already - # been set. + # If the stream was opened by IO.popen and is also closed for reading, sets + # global variable `$?` (child exit status). # - def set_encoding_by_bom: () -> Encoding? - - # - # Returns status information for *ios* as an object of type File::Stat. + # IO.popen('ruby', 'r+') do |pipe| + # puts pipe.closed? + # pipe.close_read + # puts pipe.closed? + # pipe.close_write + # puts $? + # puts pipe.closed? + # end # - # f = File.new("testfile") - # s = f.stat - # "%o" % s.mode #=> "100644" - # s.blksize #=> 4096 - # s.atime #=> Wed Apr 09 08:53:54 CDT 2003 + # Output: # - def stat: () -> File::Stat - - # - # Returns the current sync mode of the stream. When sync mode is true, all - # output is immediately flushed to the underlying operating system and is not - # buffered by Ruby internally. See also #fsync. + # false + # false + # pid 15044 exit 0 + # true # - # f = File.open('t.tmp', 'w') - # f.sync # => false - # f.sync = true - # f.sync # => true - # f.close + # Related: IO#close, IO#close_read, IO#closed?. # - def sync: () -> bool + def close_write: () -> nil # - # Sets the *sync* *mode* for the stream to the given value; returns the given - # value. - # - # Values for the sync mode: - # - # * `true`: All output is immediately flushed to the underlying operating - # system and is not buffered internally. - # * `false`: Output may be buffered internally. + # Returns `true` if the stream is closed for both reading and writing, `false` + # otherwise. See [Open and Closed Streams](rdoc-ref:IO@Open+and+Closed+Streams). # + # IO.popen('ruby', 'r+') do |pipe| + # puts pipe.closed? + # pipe.close_read + # puts pipe.closed? + # pipe.close_write + # puts pipe.closed? + # end # - # Example; + # Output: # - # f = File.open('t.tmp', 'w') - # f.sync # => false - # f.sync = true - # f.sync # => true - # f.close + # false + # false + # true # - # Related: IO#fsync. + # Related: IO#close_read, IO#close_write, IO#close. # - def sync=: (boolish boolean) -> boolish + def closed?: () -> bool + + def each: (?string? delim, ?int? limit, ?chomp: boolish) -> Enumerator[String, self] + | (int limit, ?chomp: boolish) -> Enumerator[String, self] + | (?string? delim, ?int? limit, ?chomp: boolish) { (String line) -> void } -> self + | (int limit, ?chomp: boolish) { (String line) -> void } -> self # - # Behaves like IO#readpartial, except that it uses low-level system functions. + # Calls the given block with each byte (0..255) in the stream; returns `self`. + # See [Byte IO](rdoc-ref:IO@Byte+IO). # - # This method should not be used with other stream-reader methods. + # f = File.new('t.rus') + # a = [] + # f.each_byte {|b| a << b } + # a # => [209, 130, 208, 181, 209, 129, 209, 130] + # f.close # - def sysread: (Integer maxlen, String outbuf) -> String - - # - # Behaves like IO#seek, except that it: + # Returns an Enumerator if no block is given. # - # * Uses low-level system functions. - # * Returns the new position. + # Related: IO#each_char, IO#each_codepoint. # - def sysseek: (Integer amount, ?Integer whence) -> Integer + def each_byte: () -> Enumerator[Integer, self] + | () { (Integer byte) -> void } -> self # - # Writes the given `object` to self, which must be opened for writing (see - # Modes); returns the number bytes written. If `object` is not a string is - # converted via method to_s: + # Calls the given block with each character in the stream; returns `self`. See + # [Character IO](rdoc-ref:IO@Character+IO). # - # f = File.new('t.tmp', 'w') - # f.syswrite('foo') # => 3 - # f.syswrite(30) # => 2 - # f.syswrite(:foo) # => 3 + # f = File.new('t.rus') + # a = [] + # f.each_char {|c| a << c.ord } + # a # => [1090, 1077, 1089, 1090] # f.close # - # This methods should not be used with other stream-writer methods. + # Returns an Enumerator if no block is given. # - def syswrite: (_ToS object) -> Integer + # Related: IO#each_byte, IO#each_codepoint. + # + def each_char: () -> Enumerator[String, self] + | () { (String c) -> void } -> self # - # Returns the current position (in bytes) in `self` (see - # [Position](rdoc-ref:IO@Position)): + # Calls the given block with each codepoint in the stream; returns `self`: # - # f = File.open('t.txt') - # f.tell # => 0 - # f.gets # => "First line\n" - # f.tell # => 12 + # f = File.new('t.rus') + # a = [] + # f.each_codepoint {|c| a << c } + # a # => [1090, 1077, 1089, 1090] # f.close # - # Related: IO#pos=, IO#seek. + # Returns an Enumerator if no block is given. # - def tell: () -> Integer + # Related: IO#each_byte, IO#each_char. + # + def each_codepoint: () -> Enumerator[Integer, self] + | () { (Integer c) -> void } -> self # - # Returns the current position (in bytes) in `self` (see - # [Position](rdoc-ref:IO@Position)): + # Calls the block with each remaining line read from the stream; returns `self`. + # Does nothing if already at end-of-stream; See [Line IO](rdoc-ref:IO@Line+IO). # - # f = File.open('t.txt') - # f.tell # => 0 - # f.gets # => "First line\n" - # f.tell # => 12 - # f.close + # With no arguments given, reads lines as determined by line separator `$/`: # - # Related: IO#pos=, IO#seek. - # - alias pos tell - - # - # Get the internal timeout duration or nil if it was not set. + # f = File.new('t.txt') + # f.each_line {|line| p line } + # f.each_line {|line| fail 'Cannot happen' } + # f.close # - def timeout: () -> io_timeout - - # The type used for timeouts in `IO`. + # Output: # - # Technically, this type should be `Time::_Timeout?`. However, in the vast majority of use-cases, - # people aren't going to pass their own `_Timeout` in, so `Numeric` is returned for ergonomics - # (eg `io.timeout += 10`). - type io_timeout = Numeric? - - # - # Sets the internal timeout to the specified duration or nil. The timeout - # applies to all blocking operations where possible. + # "First line\n" + # "Second line\n" + # "\n" + # "Fourth line\n" + # "Fifth line\n" # - # When the operation performs longer than the timeout set, IO::TimeoutError is - # raised. + # With only string argument `sep` given, reads lines as determined by line + # separator `sep`; see [Line Separator](rdoc-ref:IO@Line+Separator): # - # This affects the following methods (but is not limited to): #gets, #puts, - # #read, #write, #wait_readable and #wait_writable. This also affects blocking - # socket operations like Socket#accept and Socket#connect. + # f = File.new('t.txt') + # f.each_line('li') {|line| p line } + # f.close # - # Some operations like File#open and IO#close are not affected by the timeout. A - # timeout during a write operation may leave the IO in an inconsistent state, - # e.g. data was partially written. Generally speaking, a timeout is a last ditch - # effort to prevent an application from hanging on slow I/O operations, such as - # those that occur during a slowloris attack. + # Output: # - def timeout=: (io_timeout duration) -> void - - # - # Returns `self`. + # "First li" + # "ne\nSecond li" + # "ne\n\nFourth li" + # "ne\nFifth li" + # "ne\n" # - def to_io: () -> self - - # - # Returns `true` if the stream is associated with a terminal device (tty), - # `false` otherwise: + # The two special values for `sep` are honored: # - # f = File.new('t.txt').isatty #=> false - # f.close - # f = File.new('/dev/tty').isatty #=> true + # f = File.new('t.txt') + # # Get all into one string. + # f.each_line(nil) {|line| p line } # f.close # - alias tty? isatty - - # - # Pushes back ("unshifts") the given data onto the stream's buffer, placing the - # data so that it is next to be read; returns `nil`. See [Byte - # IO](rdoc-ref:IO@Byte+IO). - # - # Note that: + # Output: # - # * Calling the method has no effect with unbuffered reads (such as - # IO#sysread). - # * Calling #rewind on the stream discards the pushed-back data. + # "First line\nSecond line\n\nFourth line\nFifth line\n" # + # f.rewind + # # Get paragraphs (up to two line separators). + # f.each_line('') {|line| p line } # - # When argument `integer` is given, uses only its low-order byte: + # Output: # - # File.write('t.tmp', '012') - # f = File.open('t.tmp') - # f.ungetbyte(0x41) # => nil - # f.read # => "A012" - # f.rewind - # f.ungetbyte(0x4243) # => nil - # f.read # => "C012" - # f.close + # "First line\nSecond line\n\n" + # "Fourth line\nFifth line\n" # - # When argument `string` is given, uses all bytes: + # With only integer argument `limit` given, limits the number of bytes in each + # line; see [Line Limit](rdoc-ref:IO@Line+Limit): # - # File.write('t.tmp', '012') - # f = File.open('t.tmp') - # f.ungetbyte('A') # => nil - # f.read # => "A012" - # f.rewind - # f.ungetbyte('BCDE') # => nil - # f.read # => "BCDE012" + # f = File.new('t.txt') + # f.each_line(8) {|line| p line } # f.close # - def ungetbyte: (String | Integer object) -> nil - - # - # Pushes back ("unshifts") the given data onto the stream's buffer, placing the - # data so that it is next to be read; returns `nil`. See [Character - # IO](rdoc-ref:IO@Character+IO). - # - # Note that: + # Output: # - # * Calling the method has no effect with unbuffered reads (such as - # IO#sysread). - # * Calling #rewind on the stream discards the pushed-back data. + # "First li" + # "ne\n" + # "Second l" + # "ine\n" + # "\n" + # "Fourth l" + # "ine\n" + # "Fifth li" + # "ne\n" # + # With arguments `sep` and `limit` given, combines the two behaviors: # - # When argument `integer` is given, interprets the integer as a character: + # * Calls with the next line as determined by line separator `sep`. + # * But returns no more bytes than are allowed by the limit. # - # File.write('t.tmp', '012') - # f = File.open('t.tmp') - # f.ungetc(0x41) # => nil - # f.read # => "A012" - # f.rewind - # f.ungetc(0x0442) # => nil - # f.getc.ord # => 1090 - # f.close # - # When argument `string` is given, uses all characters: + # Optional keyword argument `chomp` specifies whether line separators are to be + # omitted: # - # File.write('t.tmp', '012') - # f = File.open('t.tmp') - # f.ungetc('A') # => nil - # f.read # => "A012" - # f.rewind - # f.ungetc("\u0442\u0435\u0441\u0442") # => nil - # f.getc.ord # => 1090 - # f.getc.ord # => 1077 - # f.getc.ord # => 1089 - # f.getc.ord # => 1090 + # f = File.new('t.txt') + # f.each_line(chomp: true) {|line| p line } # f.close # - def ungetc: (String object) -> nil - - # - # Writes each of the given `objects` to `self`, which must be opened for writing - # (see [Access Modes](rdoc-ref:File@Access+Modes)); returns the total number - # bytes written; each of `objects` that is not a string is converted via method - # `to_s`: - # - # $stdout.write('Hello', ', ', 'World!', "\n") # => 14 - # $stdout.write('foo', :bar, 2, "\n") # => 8 - # # Output: # - # Hello, World! - # foobar2 + # "First line" + # "Second line" + # "" + # "Fourth line" + # "Fifth line" # - # Related: IO#read. + # Returns an Enumerator if no block is given. + # + # IO#each is an alias for IO#each_line. # - def write: (*_ToS string) -> Integer + alias each_line each # - # Writes the given string to *ios* using the write(2) system call after - # O_NONBLOCK is set for the underlying file descriptor. + # Returns `true` if the stream is positioned at its end, `false` otherwise; see + # [Position](rdoc-ref:IO@Position): # - # It returns the number of bytes written. + # f = File.open('t.txt') + # f.eof # => false + # f.seek(0, :END) # => 0 + # f.eof # => true + # f.close # - # write_nonblock just calls the write(2) system call. It causes all errors the - # write(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The result - # may also be smaller than string.length (partial write). The caller should care - # such errors and partial write. + # Raises an exception unless the stream is opened for reading; see + # [Mode](rdoc-ref:File@Access+Modes). # - # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by - # IO::WaitWritable. So IO::WaitWritable can be used to rescue the exceptions for - # retrying write_nonblock. + # If `self` is a stream such as pipe or socket, this method blocks until the + # other end sends some data or closes it: # - # # Creates a pipe. # r, w = IO.pipe + # Thread.new { sleep 1; w.close } + # r.eof? # => true # After 1-second wait. # - # # write_nonblock writes only 65536 bytes and return 65536. - # # (The pipe size is 65536 bytes on this environment.) - # s = "a" * 100000 - # p w.write_nonblock(s) #=> 65536 + # r, w = IO.pipe + # Thread.new { sleep 1; w.puts "a" } + # r.eof? # => false # After 1-second wait. # - # # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN). - # p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN) + # r, w = IO.pipe + # r.eof? # blocks forever # - # If the write buffer is not empty, it is flushed at first. + # Note that this method reads data to the input byte buffer. So IO#sysread may + # not behave as you intend with IO#eof?, unless you call IO#rewind first (which + # is not available for some streams). # - # When write_nonblock raises an exception kind of IO::WaitWritable, - # write_nonblock should not be called until io is writable for avoiding busy - # loop. This can be done as follows. + # IO#eof? is an alias for IO#eof. # - # begin - # result = io.write_nonblock(string) - # rescue IO::WaitWritable, Errno::EINTR - # IO.select(nil, [io]) - # retry - # end + def eof: () -> bool + + # + # Returns `true` if the stream is positioned at its end, `false` otherwise; see + # [Position](rdoc-ref:IO@Position): # - # Note that this doesn't guarantee to write all data in string. The length - # written is reported as result and it should be checked later. + # f = File.open('t.txt') + # f.eof # => false + # f.seek(0, :END) # => 0 + # f.eof # => true + # f.close # - # On some platforms such as Windows, write_nonblock is not supported according - # to the kind of the IO object. In such cases, write_nonblock raises - # `Errno::EBADF`. + # Raises an exception unless the stream is opened for reading; see + # [Mode](rdoc-ref:File@Access+Modes). # - # By specifying a keyword argument *exception* to `false`, you can indicate that - # write_nonblock should not raise an IO::WaitWritable exception, but return the - # symbol `:wait_writable` instead. + # If `self` is a stream such as pipe or socket, this method blocks until the + # other end sends some data or closes it: + # + # r, w = IO.pipe + # Thread.new { sleep 1; w.close } + # r.eof? # => true # After 1-second wait. + # + # r, w = IO.pipe + # Thread.new { sleep 1; w.puts "a" } + # r.eof? # => false # After 1-second wait. + # + # r, w = IO.pipe + # r.eof? # blocks forever # - def write_nonblock: (_ToS s, ?exception: true) -> Integer - | (_ToS s, exception: false) -> (Integer | :wait_writable | nil) + # Note that this method reads data to the input byte buffer. So IO#sysread may + # not behave as you intend with IO#eof?, unless you call IO#rewind first (which + # is not available for some streams). + # + alias eof? eof + + def external_encoding: () -> Encoding? # - # Behaves like IO.read, except that the stream is opened in binary mode with - # ASCII-8BIT encoding. + # Invokes Posix system call [fcntl(2)](https://linux.die.net/man/2/fcntl), which + # provides a mechanism for issuing low-level commands to control or query a + # file-oriented I/O stream. Arguments and results are platform dependent. # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). + # If +argument is a number, its value is passed directly; if it is a string, it + # is interpreted as a binary sequence of bytes. (Array#pack might be a useful + # way to build this string.) # - def self.binread: (String name, ?Integer length, ?Integer offset) -> String + # Not implemented on all platforms. + # + def fcntl: (int integer_cmd, int | string | bool | nil argument) -> Integer # - # Behaves like IO.write, except that the stream is opened in binary mode with - # ASCII-8BIT encoding. - # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). + # Immediately writes to disk all data buffered in the stream, via the operating + # system's: `fdatasync(2)`, if supported, otherwise via `fsync(2)`, if + # supported; otherwise raises an exception. # - def self.binwrite: (String name, _ToS string, ?Integer offset, ?mode: String mode) -> Integer + def fdatasync: () -> 0 # - # Copies from the given `src` to the given `dst`, returning the number of bytes - # copied. - # - # * The given `src` must be one of the following: - # - # * The path to a readable file, from which source data is to be read. - # * An IO-like object, opened for reading and capable of responding to - # method `:readpartial` or method `:read`. - # - # - # * The given `dst` must be one of the following: + # Returns the integer file descriptor for the stream: # - # * The path to a writable file, to which data is to be written. - # * An IO-like object, opened for writing and capable of responding to - # method `:write`. + # $stdin.fileno # => 0 + # $stdout.fileno # => 1 + # $stderr.fileno # => 2 + # File.open('t.txt').fileno # => 10 + # f.close # + # IO#to_i is an alias for IO#fileno. # + def fileno: () -> Integer + + # + # Flushes data buffered in `self` to the operating system (but does not + # necessarily flush data buffered in the operating system): # - # The examples here use file `t.txt` as source: + # $stdout.print 'no newline' # Not necessarily flushed. + # $stdout.flush # Flushed. # - # File.read('t.txt') - # # => "First line\nSecond line\n\nThird line\nFourth line\n" - # File.read('t.txt').size # => 47 + def flush: () -> self + + # + # Immediately writes to disk all data buffered in the stream, via the operating + # system's `fsync(2)`. # - # If only arguments `src` and `dst` are given, the entire source stream is - # copied: + # Note this difference: # - # # Paths. - # IO.copy_stream('t.txt', 't.tmp') # => 47 + # * IO#sync=: Ensures that data is flushed from the stream's internal buffers, + # but does not guarantee that the operating system actually writes the data + # to disk. + # * IO#fsync: Ensures both that data is flushed from internal buffers, and + # that data is written to disk. # - # # IOs (recall that a File is also an IO). - # src_io = File.open('t.txt', 'r') # => # - # dst_io = File.open('t.tmp', 'w') # => # - # IO.copy_stream(src_io, dst_io) # => 47 - # src_io.close - # dst_io.close # - # With argument `src_length` a non-negative integer, no more than that many - # bytes are copied: + # Raises an exception if the operating system does not support `fsync(2)`. # - # IO.copy_stream('t.txt', 't.tmp', 10) # => 10 - # File.read('t.tmp') # => "First line" + def fsync: () -> 0 + + # + # Reads and returns the next byte (in range 0..255) from the stream; returns + # `nil` if already at end-of-stream. See [Byte IO](rdoc-ref:IO@Byte+IO). # - # With argument `src_offset` also given, the source stream is read beginning at - # that offset: + # f = File.open('t.txt') + # f.getbyte # => 70 + # f.close + # f = File.open('t.rus') + # f.getbyte # => 209 + # f.close # - # IO.copy_stream('t.txt', 't.tmp', 11, 11) # => 11 - # IO.read('t.tmp') # => "Second line" + # Related: IO#readbyte (may raise EOFError). # - def self.copy_stream: (String | _Reader | _ReaderPartial src, String | _Writer dst, ?Integer copy_length, ?Integer src_offset) -> Integer + def getbyte: () -> Integer? # - # Executes the given command `cmd` as a subprocess whose $stdin and $stdout are - # connected to a new stream `io`. + # Reads and returns the next 1-character string from the stream; returns `nil` + # if already at end-of-stream. See [Character IO](rdoc-ref:IO@Character+IO). # - # This method has potential security vulnerabilities if called with untrusted - # input; see [Command Injection](rdoc-ref:command_injection.rdoc). + # f = File.open('t.txt') + # f.getc # => "F" + # f.close + # f = File.open('t.rus') + # f.getc.ord # => 1090 + # f.close # - # If no block is given, returns the new stream, which depending on given `mode` - # may be open for reading, writing, or both. The stream should be explicitly - # closed (eventually) to avoid resource leaks. + # Related: IO#readchar (may raise EOFError). # - # If a block is given, the stream is passed to the block (again, open for - # reading, writing, or both); when the block exits, the stream is closed, and - # the block's value is assigned to global variable `$?` and returned. + def getc: () -> String? + + # + # Reads and returns a line from the stream; assigns the return value to `$_`. + # See [Line IO](rdoc-ref:IO@Line+IO). # - # Optional argument `mode` may be any valid IO mode. See [Access - # Modes](rdoc-ref:File@Access+Modes). + # With no arguments given, returns the next line as determined by line separator + # `$/`, or `nil` if none: # - # Required argument `cmd` determines which of the following occurs: + # f = File.open('t.txt') + # f.gets # => "First line\n" + # $_ # => "First line\n" + # f.gets # => "\n" + # f.gets # => "Fourth line\n" + # f.gets # => "Fifth line\n" + # f.gets # => nil + # f.close # - # * The process forks. - # * A specified program runs in a shell. - # * A specified program runs with specified arguments. - # * A specified program runs with specified arguments and a specified `argv0`. + # With only string argument `sep` given, returns the next line as determined by + # line separator `sep`, or `nil` if none; see [Line + # Separator](rdoc-ref:IO@Line+Separator): # + # f = File.new('t.txt') + # f.gets('l') # => "First l" + # f.gets('li') # => "ine\nSecond li" + # f.gets('lin') # => "ne\n\nFourth lin" + # f.gets # => "e\n" + # f.close # - # Each of these is detailed below. + # The two special values for `sep` are honored: # - # The optional hash argument `env` specifies name/value pairs that are to be - # added to the environment variables for the subprocess: + # f = File.new('t.txt') + # # Get all. + # f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" + # f.rewind + # # Get paragraph (up to two line separators). + # f.gets('') # => "First line\nSecond line\n\n" + # f.close # - # IO.popen({'FOO' => 'bar'}, 'ruby', 'r+') do |pipe| - # pipe.puts 'puts ENV["FOO"]' - # pipe.close_write - # pipe.gets - # end => "bar\n" + # With only integer argument `limit` given, limits the number of bytes in the + # line; see [Line Limit](rdoc-ref:IO@Line+Limit): # - # Optional keyword arguments `opts` specify: + # # No more than one line. + # File.open('t.txt') {|f| f.gets(10) } # => "First line" + # File.open('t.txt') {|f| f.gets(11) } # => "First line\n" + # File.open('t.txt') {|f| f.gets(12) } # => "First line\n" # - # * [Open options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). - # * Options for Kernel#spawn. + # With arguments `sep` and `limit` given, combines the two behaviors: # + # * Returns the next line as determined by line separator `sep`, or `nil` if + # none. + # * But returns no more bytes than are allowed by the limit. # - # **Forked \Process** # - # When argument `cmd` is the 1-character string `'-'`, causes the process to - # fork: - # IO.popen('-') do |pipe| - # if pipe - # $stderr.puts "In parent, child pid is #{pipe.pid}\n" - # else - # $stderr.puts "In child, pid is #{$$}\n" - # end - # end + # Optional keyword argument `chomp` specifies whether line separators are to be + # omitted: # - # Output: + # f = File.open('t.txt') + # # Chomp the lines. + # f.gets(chomp: true) # => "First line" + # f.gets(chomp: true) # => "Second line" + # f.gets(chomp: true) # => "" + # f.gets(chomp: true) # => "Fourth line" + # f.gets(chomp: true) # => "Fifth line" + # f.gets(chomp: true) # => nil + # f.close # - # In parent, child pid is 26253 - # In child, pid is 26253 + def gets: (?string? delim, ?int? limit, ?chomp: boolish) -> String? + | (int limit, ?chomp: boolish) -> String? + + # + # Returns a string representation of `self`: # - # Note that this is not supported on all platforms. + # f = File.open('t.txt') + # f.inspect # => "#" + # f.close # - # **Shell Subprocess** - # - # When argument `cmd` is a single string (but not `'-'`), the program named - # `cmd` is run as a shell command: - # - # IO.popen('uname') do |pipe| - # pipe.readlines - # end - # - # Output: - # - # ["Linux\n"] - # - # Another example: - # - # IO.popen('/bin/sh', 'r+') do |pipe| - # pipe.puts('ls') - # pipe.close_write - # $stderr.puts pipe.readlines.size - # end - # - # Output: - # - # 213 + def inspect: () -> String + + # + # Returns the Encoding object that represents the encoding of the internal + # string, if conversion is specified, or `nil` otherwise. # - # **Program Subprocess** + # See [Encodings](rdoc-ref:File@Encodings). # - # When argument `cmd` is an array of strings, the program named `cmd[0]` is run - # with all elements of `cmd` as its arguments: + def internal_encoding: () -> Encoding? + + # + # Invokes Posix system call [ioctl(2)](https://linux.die.net/man/2/ioctl), which + # issues a low-level command to an I/O device. # - # IO.popen(['du', '..', '.']) do |pipe| - # $stderr.puts pipe.readlines.size - # end + # Issues a low-level command to an I/O device. The arguments and returned value + # are platform-dependent. The effect of the call is platform-dependent. # - # Output: + # If argument `argument` is an integer, it is passed directly; if it is a + # string, it is interpreted as a binary sequence of bytes. # - # 1111 + # Not implemented on all platforms. # - # **Program Subprocess with `argv0`** + def ioctl: (int integer_cmd, int | string | bool | nil argument) -> Integer + + # + # Returns `true` if the stream is associated with a terminal device (tty), + # `false` otherwise: # - # When argument `cmd` is an array whose first element is a 2-element string - # array and whose remaining elements (if any) are strings: + # f = File.new('t.txt').isatty #=> false + # f.close + # f = File.new('/dev/tty').isatty #=> true + # f.close # - # * `cmd[0][0]` (the first string in the nested array) is the name of a - # program that is run. - # * `cmd[0][1]` (the second string in the nested array) is set as the - # program's `argv[0]`. - # * `cmd[1..-1]` (the strings in the outer array) are the program's arguments. + # IO#tty? is an alias for IO#isatty. # + def isatty: () -> bool + + # + # Returns the current line number for the stream; see [Line + # Number](rdoc-ref:IO@Line+Number). # - # Example (sets `$0` to 'foo'): + def lineno: () -> Integer + + # + # Sets and returns the line number for the stream; see [Line + # Number](rdoc-ref:IO@Line+Number). # - # IO.popen([['/bin/sh', 'foo'], '-c', 'echo $0']).read # => "foo\n" + def lineno=: [T < _ToInt] (T integer) -> T + + # + # Returns the path associated with the IO, or `nil` if there is no path + # associated with the IO. It is not guaranteed that the path exists on the + # filesystem. # - # **Some Special Examples** + # $stdin.path # => "" # - # # Set IO encoding. - # IO.popen("nkf -e filename", :external_encoding=>"EUC-JP") {|nkf_io| - # euc_jp_string = nkf_io.read - # } + # File.open("testfile") {|f| f.path} # => "testfile" # - # # Merge standard output and standard error using Kernel#spawn option. See Kernel#spawn. - # IO.popen(["ls", "/", :err=>[:child, :out]]) do |io| - # ls_result_with_error = io.read - # end + %a{ruby:since:3.2.0} + def path: () -> String? + + # + # Returns the process ID of a child process associated with the stream, which + # will have been set by IO#popen, or `nil` if the stream was not created by + # IO#popen: # - # # Use mixture of spawn options and IO options. - # IO.popen(["ls", "/"], :err=>[:child, :out]) do |io| - # ls_result_with_error = io.read + # pipe = IO.popen("-") + # if pipe + # $stderr.puts "In parent, child pid is #{pipe.pid}" + # else + # $stderr.puts "In child, pid is #{$$}" # end # - # f = IO.popen("uname") - # p f.readlines - # f.close - # puts "Parent is #{Process.pid}" - # IO.popen("date") {|f| puts f.gets } - # IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f.inspect}"} - # p $? - # IO.popen(%w"sed -e s|^|| -e s&$&;zot;&", "r+") {|f| - # f.puts "bar"; f.close_write; puts f.gets - # } - # - # Output (from last section): - # - # ["Linux\n"] - # Parent is 21346 - # Thu Jan 15 22:41:19 JST 2009 - # 21346 is here, f is # - # 21352 is here, f is nil - # # - # bar;zot; + # Output: # - # Raises exceptions that IO.pipe and Kernel.spawn raise. + # In child, pid is 26209 + # In parent, child pid is 26209 # - def self.popen: (string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) -> instance - | (Hash[string, string?] env, string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) -> instance - | [X] (string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) { (instance) -> X } -> X - | [X] (Hash[string, string?] env, string | cmd_array cmd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: Kernel::redirect_fd, ?out: Kernel::redirect_fd, ?err: Kernel::redirect_fd, ?close_others: boolish, ?chdir: String) { (instance) -> X } -> X + def pid: () -> Integer? - # The command can be given as: + # + # Returns the current position (in bytes) in `self` (see + # [Position](rdoc-ref:IO@Position)): # - # * Array of string `["ruby", "-v"]`, or - # * Array of string with the first element of array `[["ruby", "RUBY"], "-v"]` + # f = File.open('t.txt') + # f.tell # => 0 + # f.gets # => "First line\n" + # f.tell # => 12 + # f.close # - # But RBS cannot define such a type. So this is simply a union of `string` or `[String, String]`. + # Related: IO#pos=, IO#seek. # - type cmd_array = array[string | [String, String]] + # IO#pos is an alias for IO#tell. + # + alias pos tell # - # Calls the block with each successive line read from the stream. + # Seeks to the given `new_position` (in bytes); see + # [Position](rdoc-ref:IO@Position): # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). + # f = File.open('t.txt') + # f.tell # => 0 + # f.pos = 20 # => 20 + # f.tell # => 20 + # f.close # - # The first argument must be a string that is the path to a file. + # Related: IO#seek, IO#tell. # - # With only argument `path` given, parses lines from the file at the given - # `path`, as determined by the default line separator, and calls the block with - # each successive line: + def pos=: (int new_position) -> Integer + + def pread: (int len, int offset, ?string? out_string) -> String + + # + # Writes the given objects to the stream; returns `nil`. Appends the output + # record separator `$OUTPUT_RECORD_SEPARATOR` (`$\`), if it is not `nil`. See + # [Line IO](rdoc-ref:IO@Line+IO). # - # File.foreach('t.txt') {|line| p line } + # With argument `objects` given, for each object: # - # Output: the same as above. + # * Converts via its method `to_s` if not a string. + # * Writes to the stream. + # * If not the last object, writes the output field separator + # `$OUTPUT_FIELD_SEPARATOR` (`$,`) if it is not `nil`. # - # For both forms, command and path, the remaining arguments are the same. # - # With argument `sep` given, parses lines as determined by that line separator - # (see [Line Separator](rdoc-ref:IO@Line+Separator)): + # With default separators: # - # File.foreach('t.txt', 'li') {|line| p line } + # f = File.open('t.tmp', 'w+') + # objects = [0, 0.0, Rational(0, 1), Complex(0, 0), :zero, 'zero'] + # p $OUTPUT_RECORD_SEPARATOR + # p $OUTPUT_FIELD_SEPARATOR + # f.print(*objects) + # f.rewind + # p f.read + # f.close # # Output: # - # "First li" - # "ne\nSecond li" - # "ne\n\nThird li" - # "ne\nFourth li" - # "ne\n" + # nil + # nil + # "00.00/10+0izerozero" # - # Each paragraph: + # With specified separators: # - # File.foreach('t.txt', '') {|paragraph| p paragraph } + # $\ = "\n" + # $, = ',' + # f.rewind + # f.print(*objects) + # f.rewind + # p f.read # # Output: # - # "First line\nSecond line\n\n" - # "Third line\nFourth line\n" + # "0,0.0,0/1,0+0i,zero,zero\n" # - # With argument `limit` given, parses lines as determined by the default line - # separator and the given line-length limit (see [Line - # Limit](rdoc-ref:IO@Line+Limit)): + # With no argument given, writes the content of `$_` (which is usually the most + # recent user input): # - # File.foreach('t.txt', 7) {|line| p line } + # f = File.open('t.tmp', 'w+') + # gets # Sets $_ to the most recent user input. + # f.print + # f.close # - # Output: + def print: (*_ToS objects) -> nil + + # + # Formats and writes `objects` to the stream. # - # "First l" - # "ine\n" - # "Second " - # "line\n" - # "\n" - # "Third l" - # "ine\n" - # "Fourth l" - # "line\n" - # - # With arguments `sep` and `limit` given, parses lines as determined by the - # given line separator and the given line-length limit (see [Line Separator and - # Line Limit](rdoc-ref:IO@Line+Separator+and+Line+Limit)): + # For details on `format_string`, see [Format + # Specifications](rdoc-ref:format_specifications.rdoc). # - # Optional keyword arguments `opts` specify: + def printf: (string format_string, *untyped objects) -> nil + + # + # Writes a character to the stream. See [Character + # IO](rdoc-ref:IO@Character+IO). # - # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). - # * [Line Options](rdoc-ref:IO@Line+IO). + # If `object` is numeric, converts to integer if necessary, then writes the + # character whose code is the least significant byte; if `object` is a string, + # writes the first character: # + # $stdout.putc "A" + # $stdout.putc 65t4 # - # Returns an Enumerator if no block is given. + # AA # - def self.foreach: (string | _ToPath path, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) { (String line) -> void } -> nil - | (string | _ToPath path, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> ::Enumerator[String, nil] + def putc: (String str) -> String + | [T < _ToInt] (T chr) -> T # - # Creates a pair of pipe endpoints, `read_io` and `write_io`, connected to each - # other. + # Writes the given `objects` to the stream, which must be open for writing; + # returns `nil`.\ Writes a newline after each that does not already end with a + # newline sequence. If called without arguments, writes a newline. See [Line + # IO](rdoc-ref:IO@Line+IO). # - # If argument `enc_string` is given, it must be a string containing one of: + # Note that each added newline is the character `"\n", not the output + # record separator ($\`). # - # * The name of the encoding to be used as the external encoding. - # * The colon-separated names of two encodings to be used as the external and - # internal encodings. + # Treatment for each object: # + # * String: writes the string. + # * Neither string nor array: writes `object.to_s`. + # * Array: writes each element of the array; arrays may be nested. # - # If argument `int_enc` is given, it must be an Encoding object or encoding name - # string that specifies the internal encoding to be used; if argument `ext_enc` - # is also given, it must be an Encoding object or encoding name string that - # specifies the external encoding to be used. # - # The string read from `read_io` is tagged with the external encoding; if an - # internal encoding is also specified, the string is converted to, and tagged - # with, that encoding. + # To keep these examples brief, we define this helper method: # - # If any encoding is specified, optional hash arguments specify the conversion - # option. + # def show(*objects) + # # Puts objects to file. + # f = File.new('t.tmp', 'w+') + # f.puts(objects) + # # Return file content. + # f.rewind + # p f.read + # f.close + # end # - # Optional keyword arguments `opts` specify: + # # Strings without newlines. + # show('foo', 'bar', 'baz') # => "foo\nbar\nbaz\n" + # # Strings, some with newlines. + # show("foo\n", 'bar', "baz\n") # => "foo\nbar\nbaz\n" # - # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding Options](rdoc-ref:encodings.rdoc@Encoding+Options). + # # Neither strings nor arrays: + # show(0, 0.0, Rational(0, 1), Complex(9, 0), :zero) + # # => "0\n0.0\n0/1\n9+0i\nzero\n" # + # # Array of strings. + # show(['foo', "bar\n", 'baz']) # => "foo\nbar\nbaz\n" + # # Nested arrays. + # show([[[0, 1], 2, 3], 4, 5]) # => "0\n1\n2\n3\n4\n5\n" # - # With no block given, returns the two endpoints in an array: + def puts: (*_ToS objects) -> nil + + def pwrite: (_ToS object, int offset) -> Integer + + # + # Reads bytes from the stream; the stream must be opened for reading (see + # [Access Modes](rdoc-ref:File@Access+Modes)): # - # IO.pipe # => [#, #] + # * If `maxlen` is `nil`, reads all bytes using the stream's data mode. + # * Otherwise reads up to `maxlen` bytes in binary mode. # - # With a block given, calls the block with the two endpoints; closes both - # endpoints and returns the value of the block: # - # IO.pipe {|read_io, write_io| p read_io; p write_io } + # Returns a string (either a new string or the given `out_string`) containing + # the bytes read. The encoding of the string depends on both `maxLen` and + # `out_string`: # - # Output: + # * `maxlen` is `nil`: uses internal encoding of `self` (regardless of whether + # `out_string` was given). + # * `maxlen` not `nil`: # - # # - # # + # * `out_string` given: encoding of `out_string` not modified. + # * `out_string` not given: ASCII-8BIT is used. # - # Not available on all platforms. # - # In the example below, the two processes close the ends of the pipe that they - # are not using. This is not just a cosmetic nicety. The read end of a pipe will - # not generate an end of file condition if there are any writers with the pipe - # still open. In the case of the parent process, the `rd.read` will never return - # if it does not first issue a `wr.close`: # - # rd, wr = IO.pipe + # **Without Argument `out_string`** # - # if fork - # wr.close - # puts "Parent got: <#{rd.read}>" - # rd.close - # Process.wait - # else - # rd.close - # puts 'Sending message to parent' - # wr.write "Hi Dad" - # wr.close - # end + # When argument `out_string` is omitted, the returned value is a new string: # - # *produces:* + # f = File.new('t.txt') + # f.read + # # => "First line\nSecond line\n\nFourth line\nFifth line\n" + # f.rewind + # f.read(30) # => "First line\r\nSecond line\r\n\r\nFou" + # f.read(30) # => "rth line\r\nFifth line\r\n" + # f.read(30) # => nil + # f.close # - # Sending message to parent - # Parent got: + # If `maxlen` is zero, returns an empty string. # - def self.pipe: (?String | Encoding | nil ext_or_ext_int_enc, ?String | Encoding | nil int_enc, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> [IO, IO] - | [X] (?String | Encoding | nil ext_or_ext_int_enc, ?String | Encoding | nil int_enc, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) { (IO read_io, IO write_io) -> X } -> X + # ** With Argument `out_string`** + # + # When argument `out_string` is given, the returned value is `out_string`, whose + # content is replaced: + # + # f = File.new('t.txt') + # s = 'foo' # => "foo" + # f.read(nil, s) # => "First line\nSecond line\n\nFourth line\nFifth line\n" + # s # => "First line\nSecond line\n\nFourth line\nFifth line\n" + # f.rewind + # s = 'bar' + # f.read(30, s) # => "First line\r\nSecond line\r\n\r\nFou" + # s # => "First line\r\nSecond line\r\n\r\nFou" + # s = 'baz' + # f.read(30, s) # => "rth line\r\nFifth line\r\n" + # s # => "rth line\r\nFifth line\r\n" + # s = 'bat' + # f.read(30, s) # => nil + # s # => "" + # f.close + # + # Note that this method behaves like the fread() function in C. This means it + # retries to invoke read(2) system calls to read data with the specified maxlen + # (or until EOF). + # + # This behavior is preserved even if the stream is in non-blocking mode. (This + # method is non-blocking-flag insensitive as other methods.) + # + # If you need the behavior like a single read(2) system call, consider + # #readpartial, #read_nonblock, and #sysread. + # + # Related: IO#write. + # + def read: (?int length, ?string? outbuf) -> String? + | (nil, ?string? outbuf) -> String # - # Opens the stream, reads and returns some or all of its content, and closes the - # stream; returns `nil` if no bytes were read. + # Reads at most *maxlen* bytes from *ios* using the read(2) system call after + # O_NONBLOCK is set for the underlying file descriptor. # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). + # If the optional *outbuf* argument is present, it must reference a String, + # which will receive the data. The *outbuf* will contain only the received data + # after the method call even if it is not empty at the beginning. # - # The first argument must be a string that is the path to a file. + # read_nonblock just calls the read(2) system call. It causes all errors the + # read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The caller + # should care such errors. # - # With only argument `path` given, reads in text mode and returns the entire - # content of the file at the given path: + # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by + # IO::WaitReadable. So IO::WaitReadable can be used to rescue the exceptions for + # retrying read_nonblock. # - # IO.read('t.txt') - # # => "First line\nSecond line\n\nThird line\nFourth line\n" + # read_nonblock causes EOFError on EOF. # - # On Windows, text mode can terminate reading and leave bytes in the file unread - # when encountering certain special bytes. Consider using IO.binread if all - # bytes in the file should be read. + # On some platforms, such as Windows, non-blocking mode is not supported on IO + # objects other than sockets. In such cases, Errno::EBADF will be raised. # - # With argument `length`, returns `length` bytes if available: + # If the read byte buffer is not empty, read_nonblock reads from the buffer like + # readpartial. In this case, the read(2) system call is not called. # - # IO.read('t.txt', 7) # => "First l" - # IO.read('t.txt', 700) - # # => "First line\r\nSecond line\r\n\r\nFourth line\r\nFifth line\r\n" + # When read_nonblock raises an exception kind of IO::WaitReadable, read_nonblock + # should not be called until io is readable for avoiding busy loop. This can be + # done as follows. # - # With arguments `length` and `offset`, returns `length` bytes if available, - # beginning at the given `offset`: + # # emulates blocking read (readpartial). + # begin + # result = io.read_nonblock(maxlen) + # rescue IO::WaitReadable + # IO.select([io]) + # retry + # end # - # IO.read('t.txt', 10, 2) # => "rst line\nS" - # IO.read('t.txt', 10, 200) # => nil + # Although IO#read_nonblock doesn't raise IO::WaitWritable. + # OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. If IO and SSL + # should be used polymorphically, IO::WaitWritable should be rescued too. See + # the document of OpenSSL::Buffering#read_nonblock for sample code. # - # Optional keyword arguments `opts` specify: + # Note that this method is identical to readpartial except the non-blocking flag + # is set. # - # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). + # By specifying a keyword argument *exception* to `false`, you can indicate that + # read_nonblock should not raise an IO::WaitReadable exception, but return the + # symbol `:wait_readable` instead. At EOF, it will return nil instead of raising + # EOFError. # - def self.read: (String name, ?Integer length, ?Integer offset, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> String + def read_nonblock: (int len, ?string? outbuf, ?exception: true) -> String + | (int len, ?string? outbuf, exception: bool) -> (String | :wait_readable | nil) # - # Returns an array of all lines read from the stream. + # Reads and returns the next byte (in range 0..255) from the stream; raises + # EOFError if already at end-of-stream. See [Byte IO](rdoc-ref:IO@Byte+IO). # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). - # - # The first argument must be a string that is the path to a file. - # - # With only argument `path` given, parses lines from the file at the given - # `path`, as determined by the default line separator, and returns those lines - # in an array: - # - # IO.readlines('t.txt') - # # => ["First line\n", "Second line\n", "\n", "Third line\n", "Fourth line\n"] - # - # With argument `sep` given, parses lines as determined by that line separator - # (see [Line Separator](rdoc-ref:IO@Line+Separator)): + # f = File.open('t.txt') + # f.readbyte # => 70 + # f.close + # f = File.open('t.rus') + # f.readbyte # => 209 + # f.close # - # # Ordinary separator. - # IO.readlines('t.txt', 'li') - # # =>["First li", "ne\nSecond li", "ne\n\nThird li", "ne\nFourth li", "ne\n"] - # # Get-paragraphs separator. - # IO.readlines('t.txt', '') - # # => ["First line\nSecond line\n\n", "Third line\nFourth line\n"] - # # Get-all separator. - # IO.readlines('t.txt', nil) - # # => ["First line\nSecond line\n\nThird line\nFourth line\n"] + # Related: IO#getbyte (will not raise EOFError). # - # With argument `limit` given, parses lines as determined by the default line - # separator and the given line-length limit (see [Line - # Limit](rdoc-ref:IO@Line+Limit)): + def readbyte: () -> Integer + + # + # Reads and returns the next 1-character string from the stream; raises EOFError + # if already at end-of-stream. See [Character IO](rdoc-ref:IO@Character+IO). # - # IO.readlines('t.txt', 7) - # # => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"] + # f = File.open('t.txt') + # f.readchar # => "F" + # f.close + # f = File.open('t.rus') + # f.readchar.ord # => 1090 + # f.close # - # With arguments `sep` and `limit` given, parses lines as determined by the - # given line separator and the given line-length limit (see [Line Separator and - # Line Limit](rdoc-ref:IO@Line+Separator+and+Line+Limit)): + # Related: IO#getc (will not raise EOFError). # - # Optional keyword arguments `opts` specify: + def readchar: () -> String + + # + # Reads a line as with IO#gets, but raises EOFError if already at end-of-stream. # - # * [Open Options](rdoc-ref:IO@Open+Options). - # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). - # * [Line Options](rdoc-ref:IO@Line+IO). + # Optional keyword argument `chomp` specifies whether line separators are to be + # omitted. # - def self.readlines: (String | _ToPath name, ?String sep, ?Integer limit, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String, ?chomp: boolish) -> ::Array[String] + def readline: (?string? delim, ?int? limit, ?chomp: boolish) -> String + | (int limit, ?chomp: boolish) -> String # - # Invokes system call [select(2)](https://linux.die.net/man/2/select), which - # monitors multiple file descriptors, waiting until one or more of the file - # descriptors becomes ready for some class of I/O operation. + # Reads and returns all remaining line from the stream; does not modify `$_`. + # See [Line IO](rdoc-ref:IO@Line+IO). # - # Not implemented on all platforms. + # With no arguments given, returns lines as determined by line separator `$/`, + # or `nil` if none: # - # Each of the arguments `read_ios`, `write_ios`, and `error_ios` is an array of - # IO objects. + # f = File.new('t.txt') + # f.readlines + # # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] + # f.readlines # => [] + # f.close # - # Argument `timeout` is an integer timeout interval in seconds. + # With only string argument `sep` given, returns lines as determined by line + # separator `sep`, or `nil` if none; see [Line + # Separator](rdoc-ref:IO@Line+Separator): # - # The method monitors the IO objects given in all three arrays, waiting for some - # to be ready; returns a 3-element array whose elements are: + # f = File.new('t.txt') + # f.readlines('li') + # # => ["First li", "ne\nSecond li", "ne\n\nFourth li", "ne\nFifth li", "ne\n"] + # f.close # - # * An array of the objects in `read_ios` that are ready for reading. - # * An array of the objects in `write_ios` that are ready for writing. - # * An array of the objects in `error_ios` have pending exceptions. + # The two special values for `sep` are honored: # + # f = File.new('t.txt') + # # Get all into one string. + # f.readlines(nil) + # # => ["First line\nSecond line\n\nFourth line\nFifth line\n"] + # # Get paragraphs (up to two line separators). + # f.rewind + # f.readlines('') + # # => ["First line\nSecond line\n\n", "Fourth line\nFifth line\n"] + # f.close # - # If no object becomes ready within the given `timeout`, `nil` is returned. + # With only integer argument `limit` given, limits the number of bytes in each + # line; see [Line Limit](rdoc-ref:IO@Line+Limit): # - # IO.select peeks the buffer of IO objects for testing readability. If the IO - # buffer is not empty, IO.select immediately notifies readability. This "peek" - # only happens for IO objects. It does not happen for IO-like objects such as - # OpenSSL::SSL::SSLSocket. + # f = File.new('t.txt') + # f.readlines(8) + # # => ["First li", "ne\n", "Second l", "ine\n", "\n", "Fourth l", "ine\n", "Fifth li", "ne\n"] + # f.close # - # The best way to use IO.select is invoking it after non-blocking methods such - # as #read_nonblock, #write_nonblock, etc. The methods raise an exception which - # is extended by IO::WaitReadable or IO::WaitWritable. The modules notify how - # the caller should wait with IO.select. If IO::WaitReadable is raised, the - # caller should wait for reading. If IO::WaitWritable is raised, the caller - # should wait for writing. + # With arguments `sep` and `limit` given, combines the two behaviors: # - # So, blocking read (#readpartial) can be emulated using #read_nonblock and - # IO.select as follows: + # * Returns lines as determined by line separator `sep`. + # * But returns no more bytes in a line than are allowed by the limit. # - # begin - # result = io_like.read_nonblock(maxlen) - # rescue IO::WaitReadable - # IO.select([io_like]) - # retry - # rescue IO::WaitWritable - # IO.select(nil, [io_like]) - # retry - # end # - # Especially, the combination of non-blocking methods and IO.select is preferred - # for IO like objects such as OpenSSL::SSL::SSLSocket. It has #to_io method to - # return underlying IO object. IO.select calls #to_io to obtain the file - # descriptor to wait. + # Optional keyword argument `chomp` specifies whether line separators are to be + # omitted: # - # This means that readability notified by IO.select doesn't mean readability - # from OpenSSL::SSL::SSLSocket object. + # f = File.new('t.txt') + # f.readlines(chomp: true) + # # => ["First line", "Second line", "", "Fourth line", "Fifth line"] + # f.close # - # The most likely situation is that OpenSSL::SSL::SSLSocket buffers some data. - # IO.select doesn't see the buffer. So IO.select can block when - # OpenSSL::SSL::SSLSocket#readpartial doesn't block. + def readlines: (?string? delim, ?int? limit, ?chomp: boolish) -> Array[String] + | (int limit, ?chomp: boolish) -> Array[String] + + # + # Reads up to `maxlen` bytes from the stream; returns a string (either a new + # string or the given `out_string`). Its encoding is: # - # However, several more complicated situations exist. + # * The unchanged encoding of `out_string`, if `out_string` is given. + # * ASCII-8BIT, otherwise. # - # SSL is a protocol which is sequence of records. The record consists of - # multiple bytes. So, the remote side of SSL sends a partial record, IO.select - # notifies readability but OpenSSL::SSL::SSLSocket cannot decrypt a byte and - # OpenSSL::SSL::SSLSocket#readpartial will block. + # * Contains `maxlen` bytes from the stream, if available. + # * Otherwise contains all available bytes, if any available. + # * Otherwise is an empty string. # - # Also, the remote side can request SSL renegotiation which forces the local SSL - # engine to write some data. This means OpenSSL::SSL::SSLSocket#readpartial may - # invoke #write system call and it can block. In such a situation, - # OpenSSL::SSL::SSLSocket#read_nonblock raises IO::WaitWritable instead of - # blocking. So, the caller should wait for ready for writability as above - # example. # - # The combination of non-blocking methods and IO.select is also useful for - # streams such as tty, pipe socket socket when multiple processes read from a - # stream. + # With the single non-negative integer argument `maxlen` given, returns a new + # string: # - # Finally, Linux kernel developers don't guarantee that readability of select(2) - # means readability of following read(2) even for a single process; see - # [select(2)](https://linux.die.net/man/2/select) + # f = File.new('t.txt') + # f.readpartial(20) # => "First line\nSecond l" + # f.readpartial(20) # => "ine\n\nFourth line\n" + # f.readpartial(20) # => "Fifth line\n" + # f.readpartial(20) # Raises EOFError. + # f.close # - # Invoking IO.select before IO#readpartial works well as usual. However it is - # not the best way to use IO.select. + # With both argument `maxlen` and string argument `out_string` given, returns + # modified `out_string`: # - # The writability notified by select(2) doesn't show how many bytes are - # writable. IO#write method blocks until given whole string is written. So, - # `IO#write(two or more bytes)` can block after writability is notified by - # IO.select. IO#write_nonblock is required to avoid the blocking. + # f = File.new('t.txt') + # s = 'foo' + # f.readpartial(20, s) # => "First line\nSecond l" + # s = 'bar' + # f.readpartial(0, s) # => "" + # f.close # - # Blocking write (#write) can be emulated using #write_nonblock and IO.select as - # follows: IO::WaitReadable should also be rescued for SSL renegotiation in - # OpenSSL::SSL::SSLSocket. + # This method is useful for a stream such as a pipe, a socket, or a tty. It + # blocks only when no data is immediately available. This means that it blocks + # only when *all* of the following are true: # - # while 0 < string.bytesize - # begin - # written = io_like.write_nonblock(string) - # rescue IO::WaitReadable - # IO.select([io_like]) - # retry - # rescue IO::WaitWritable - # IO.select(nil, [io_like]) - # retry - # end - # string = string.byteslice(written..-1) - # end + # * The byte buffer in the stream is empty. + # * The content of the stream is empty. + # * The stream is not at EOF. # - # Example: # - # rp, wp = IO.pipe - # mesg = "ping " - # 100.times { - # # IO.select follows IO#read. Not the best way to use IO.select. - # rs, ws, = IO.select([rp], [wp]) - # if r = rs[0] - # ret = r.read(5) - # print ret - # case ret - # when /ping/ - # mesg = "pong\n" - # when /pong/ - # mesg = "ping " - # end - # end - # if w = ws[0] - # w.write(mesg) - # end - # } + # When blocked, the method waits for either more data or EOF on the stream: # - # Output: + # * If more data is read, the method returns the data. + # * If EOF is reached, the method raises EOFError. # - # ping pong - # ping pong - # ping pong - # (snipped) - # ping # - def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] - | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? - - # - # Opens the file at the given path with the given mode and permissions; returns - # the integer file descriptor. + # When not blocked, the method responds immediately: # - # If the file is to be readable, it must exist; if the file is to be writable - # and does not exist, it is created with the given permissions: + # * Returns data from the buffer if there is any. + # * Otherwise returns data from the stream if there is any. + # * Otherwise raises EOFError if the stream has reached EOF. # - # File.write('t.tmp', '') # => 0 - # IO.sysopen('t.tmp') # => 8 - # IO.sysopen('t.tmp', 'w') # => 9 # - def self.sysopen: (String path, ?String mode, ?String perm) -> Integer - - # - # Attempts to convert `object` into an IO object via method `to_io`; returns the - # new IO object if successful, or `nil` otherwise: + # Note that this method is similar to sysread. The differences are: # - # IO.try_convert(STDOUT) # => #> - # IO.try_convert(ARGF) # => #> - # IO.try_convert('STDOUT') # => nil + # * If the byte buffer is not empty, read from the byte buffer instead of + # "sysread for buffered IO (IOError)". + # * It doesn't cause Errno::EWOULDBLOCK and Errno::EINTR. When readpartial + # meets EWOULDBLOCK and EINTR by read system call, readpartial retries the + # system call. # - def self.try_convert: (_ToIO obj) -> IO - | (untyped obj) -> IO? - - # - # Opens the stream, writes the given `data` to it, and closes the stream; - # returns the number of bytes written. # - # When called from class IO (but not subclasses of IO), this method has - # potential security vulnerabilities if called with untrusted input; see - # [Command Injection](rdoc-ref:command_injection.rdoc). + # The latter means that readpartial is non-blocking-flag insensitive. It blocks + # on the situation IO#sysread causes Errno::EWOULDBLOCK as if the fd is blocking + # mode. # - # The first argument must be a string that is the path to a file. + # Examples: # - # With only argument `path` given, writes the given `data` to the file at that - # path: + # # # Returned Buffer Content Pipe Content + # r, w = IO.pipe # + # w << 'abc' # "" "abc". + # r.readpartial(4096) # => "abc" "" "" + # r.readpartial(4096) # (Blocks because buffer and pipe are empty.) # - # IO.write('t.tmp', 'abc') # => 3 - # File.read('t.tmp') # => "abc" + # # # Returned Buffer Content Pipe Content + # r, w = IO.pipe # + # w << 'abc' # "" "abc" + # w.close # "" "abc" EOF + # r.readpartial(4096) # => "abc" "" EOF + # r.readpartial(4096) # raises EOFError # - # If `offset` is zero (the default), the file is overwritten: + # # # Returned Buffer Content Pipe Content + # r, w = IO.pipe # + # w << "abc\ndef\n" # "" "abc\ndef\n" + # r.gets # => "abc\n" "def\n" "" + # w << "ghi\n" # "def\n" "ghi\n" + # r.readpartial(4096) # => "def\n" "" "ghi\n" + # r.readpartial(4096) # => "ghi\n" "" "" # - # IO.write('t.tmp', 'A') # => 1 - # File.read('t.tmp') # => "A" + def readpartial: (int maxlen, ?string? out_string) -> String + + # + # Reassociates the stream with another stream, which may be of a different + # class. This method may be used to redirect an existing stream to a new + # destination. # - # If `offset` in within the file content, the file is partly overwritten: + # With argument `other_io` given, reassociates with that stream: # - # IO.write('t.tmp', 'abcdef') # => 3 - # File.read('t.tmp') # => "abcdef" - # # Offset within content. - # IO.write('t.tmp', '012', 2) # => 3 - # File.read('t.tmp') # => "ab012f" + # # Redirect $stdin from a file. + # f = File.open('t.txt') + # $stdin.reopen(f) + # f.close # - # If `offset` is outside the file content, the file is padded with null - # characters `"\u0000"`: + # # Redirect $stdout to a file. + # f = File.open('t.tmp', 'w') + # $stdout.reopen(f) + # f.close # - # IO.write('t.tmp', 'xyz', 10) # => 3 - # File.read('t.tmp') # => "ab012f\u0000\u0000\u0000\u0000xyz" + # With argument `path` given, reassociates with a new stream to that file path: + # + # $stdin.reopen('t.txt') + # $stdout.reopen('t.tmp', 'w') # # Optional keyword arguments `opts` specify: # # * [Open Options](rdoc-ref:IO@Open+Options). # * [Encoding options](rdoc-ref:encodings.rdoc@Encoding+Options). # - def self.write: (String path, _ToS data, ?Integer offset, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> Integer + def reopen: (io other_io) -> self + | ( + path filepath, + ?open_mode mode, + ?mode: open_mode, + ?flags: int?, + ?textmode: boolish, + ?binmode: boolish, + ?encoding: encoding?, + ?external_encoding: encoding?, + ?internal_encoding: encoding?, + ?replace: string?, + ?fallback: Proc | Method | Encoding::_EncodeFallbackAref, + ?invalid: :replace | nil, + ?undef: :replace | nil, + ?xml: :text | :attr | nil, + ?newline: :universal | :crlf | :cr | :lf | nil, + ?universal_newline: boolish, + ?crlf_newline: boolish, + ?cr_newline: boolish, + ?lf_newline: boolish, + **untyped + ) -> self # - # Synonym for IO.new. + # Repositions the stream to its beginning, setting both the position and the + # line number to zero; see [Position](rdoc-ref:IO@Position) and [Line + # Number](rdoc-ref:IO@Line+Number): # - alias self.for_fd self.new + # f = File.open('t.txt') + # f.tell # => 0 + # f.lineno # => 0 + # f.gets # => "First line\n" + # f.tell # => 12 + # f.lineno # => 1 + # f.rewind # => 0 + # f.tell # => 0 + # f.lineno # => 0 + # f.close + # + # Note that this method cannot be used with streams such as pipes, ttys, and + # sockets. + # + def rewind: () -> 0 # - # Creates a new IO object, via IO.new with the given arguments. - # - # With no block given, returns the IO object. + # Seeks to the position given by integer `offset` (see + # [Position](rdoc-ref:IO@Position)) and constant `whence`, which is one of: # - # With a block given, calls the block with the IO object and returns the block's - # value. + # * `:CUR` or `IO::SEEK_CUR`: Repositions the stream to its current position + # plus the given `offset`: # - def self.open: (int fd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> instance - | [X] (int fd, ?string | int mode, ?path: string?, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) { (instance) -> X } -> X - - # - # Calls the block with each remaining line read from the stream; returns `self`. - # Does nothing if already at end-of-stream; See [Line IO](rdoc-ref:IO@Line+IO). + # f = File.open('t.txt') + # f.tell # => 0 + # f.seek(20, :CUR) # => 0 + # f.tell # => 20 + # f.seek(-10, :CUR) # => 0 + # f.tell # => 10 + # f.close # - # With no arguments given, reads lines as determined by line separator `$/`: + # * `:END` or `IO::SEEK_END`: Repositions the stream to its end plus the given + # `offset`: # - # f = File.new('t.txt') - # f.each_line {|line| p line } - # f.each_line {|line| fail 'Cannot happen' } - # f.close + # f = File.open('t.txt') + # f.tell # => 0 + # f.seek(0, :END) # => 0 # Repositions to stream end. + # f.tell # => 52 + # f.seek(-20, :END) # => 0 + # f.tell # => 32 + # f.seek(-40, :END) # => 0 + # f.tell # => 12 + # f.close # - # Output: + # * `:SET` or `IO:SEEK_SET`: Repositions the stream to the given `offset`: # - # "First line\n" - # "Second line\n" - # "\n" - # "Fourth line\n" - # "Fifth line\n" + # f = File.open('t.txt') + # f.tell # => 0 + # f.seek(20, :SET) # => 0 + # f.tell # => 20 + # f.seek(40, :SET) # => 0 + # f.tell # => 40 + # f.close # - # With only string argument `sep` given, reads lines as determined by line - # separator `sep`; see [Line Separator](rdoc-ref:IO@Line+Separator): # - # f = File.new('t.txt') - # f.each_line('li') {|line| p line } - # f.close + # Related: IO#pos=, IO#tell. # - # Output: + def seek: (int amount, ?whence) -> Integer + + # Where to seek from. Can be an integer (one of the `IO::SEEK_XXX` constants) or one of these + # symbols. + type whence = int | :SET | :CUR | :END | :DATA | :HOLE + + # + # See [Encodings](rdoc-ref:File@Encodings). # - # "First li" - # "ne\nSecond li" - # "ne\n\nFourth li" - # "ne\nFifth li" - # "ne\n" + # Argument `ext_enc`, if given, must be an Encoding object; it is assigned as + # the encoding for the stream. # - # The two special values for `sep` are honored: + # Argument `int_enc`, if given, must be an Encoding object; it is assigned as + # the encoding for the internal string. # - # f = File.new('t.txt') - # # Get all into one string. - # f.each_line(nil) {|line| p line } - # f.close + # Argument `'ext_enc:int_enc'`, if given, is a string containing two + # colon-separated encoding names; corresponding Encoding objects are assigned as + # the external and internal encodings for the stream. # - # Output: + # Optional keyword arguments `enc_opts` specify [Encoding + # options](rdoc-ref:encodings.rdoc@Encoding+Options). # - # "First line\nSecond line\n\nFourth line\nFifth line\n" + def set_encoding: (Encoding? ext_enc, ?nil, **untyped) -> self + | ( + encoding ext_enc, + encoding | '-' | nil int_enc, + ?replace: string?, + ?fallback: Proc | Method | Encoding::_EncodeFallbackAref, + ?invalid: :replace | nil, + ?undef: :replace | nil, + ?xml: :text | :attr | nil, + ?newline: :universal | :crlf | :cr | :lf | nil, + ?universal_newline: boolish, + ?crlf_newline: boolish, + ?cr_newline: boolish, + ?lf_newline: boolish, + **untyped + ) -> self + + # + # If the stream begins with a BOM ([byte order + # marker](https://en.wikipedia.org/wiki/Byte_order_mark)), consumes the BOM and + # sets the external encoding accordingly; returns the result encoding if found, + # or `nil` otherwise: # - # f.rewind - # # Get paragraphs (up to two line separators). - # f.each_line('') {|line| p line } + # File.write('t.tmp', "\u{FEFF}abc") + # io = File.open('t.tmp', 'rb') + # io.set_encoding_by_bom # => # + # io.close # - # Output: + # File.write('t.tmp', 'abc') + # io = File.open('t.tmp', 'rb') + # io.set_encoding_by_bom # => nil + # io.close # - # "First line\nSecond line\n\n" - # "Fourth line\nFifth line\n" + # Raises an exception if the stream is not binmode or its encoding has already + # been set. # - # With only integer argument `limit` given, limits the number of bytes in each - # line; see [Line Limit](rdoc-ref:IO@Line+Limit): + def set_encoding_by_bom: () -> Encoding? + + # + # Returns status information for *ios* as an object of type File::Stat. # - # f = File.new('t.txt') - # f.each_line(8) {|line| p line } - # f.close + # f = File.new("testfile") + # s = f.stat + # "%o" % s.mode #=> "100644" + # s.blksize #=> 4096 + # s.atime #=> Wed Apr 09 08:53:54 CDT 2003 # - # Output: + def stat: () -> File::Stat + + # + # Returns the current sync mode of the stream. When sync mode is true, all + # output is immediately flushed to the underlying operating system and is not + # buffered by Ruby internally. See also #fsync. # - # "First li" - # "ne\n" - # "Second l" - # "ine\n" - # "\n" - # "Fourth l" - # "ine\n" - # "Fifth li" - # "ne\n" + # f = File.open('t.tmp', 'w') + # f.sync # => false + # f.sync = true + # f.sync # => true + # f.close # - # With arguments `sep` and `limit` given, combines the two behaviors: + def sync: () -> bool + + # + # Sets the *sync* *mode* for the stream to the given value; returns the given + # value. # - # * Calls with the next line as determined by line separator `sep`. - # * But returns no more bytes than are allowed by the limit. + # Values for the sync mode: + # + # * `true`: All output is immediately flushed to the underlying operating + # system and is not buffered internally. + # * `false`: Output may be buffered internally. # # - # Optional keyword argument `chomp` specifies whether line separators are to be - # omitted: + # Example; # - # f = File.new('t.txt') - # f.each_line(chomp: true) {|line| p line } + # f = File.open('t.tmp', 'w') + # f.sync # => false + # f.sync = true + # f.sync # => true # f.close # - # Output: + # Related: IO#fsync. # - # "First line" - # "Second line" - # "" - # "Fourth line" - # "Fifth line" + def sync=: [T] (T boolean) -> T + + # + # Behaves like IO#readpartial, except that it uses low-level system functions. # - # Returns an Enumerator if no block is given. + # This method should not be used with other stream-reader methods. # - def each_line: (?String sep, ?Integer limit) { (String line) -> void } -> self - | (?String sep, ?Integer limit) -> ::Enumerator[String, self] + def sysread: (int maxlen, ?string? out_string) -> String # - # Calls the block with each remaining line read from the stream; returns `self`. - # Does nothing if already at end-of-stream; See [Line IO](rdoc-ref:IO@Line+IO). + # Behaves like IO#seek, except that it: # - # With no arguments given, reads lines as determined by line separator `$/`: + # * Uses low-level system functions. + # * Returns the new position. # - # f = File.new('t.txt') - # f.each_line {|line| p line } - # f.each_line {|line| fail 'Cannot happen' } - # f.close + def sysseek: (int offset, ?whence whence) -> Integer + + # + # Writes the given `object` to self, which must be opened for writing (see + # Modes); returns the number bytes written. If `object` is not a string is + # converted via method to_s: # - # Output: + # f = File.new('t.tmp', 'w') + # f.syswrite('foo') # => 3 + # f.syswrite(30) # => 2 + # f.syswrite(:foo) # => 3 + # f.close # - # "First line\n" - # "Second line\n" - # "\n" - # "Fourth line\n" - # "Fifth line\n" + # This methods should not be used with other stream-writer methods. # - # With only string argument `sep` given, reads lines as determined by line - # separator `sep`; see [Line Separator](rdoc-ref:IO@Line+Separator): + def syswrite: (_ToS object) -> Integer + + # + # Returns the current position (in bytes) in `self` (see + # [Position](rdoc-ref:IO@Position)): # - # f = File.new('t.txt') - # f.each_line('li') {|line| p line } + # f = File.open('t.txt') + # f.tell # => 0 + # f.gets # => "First line\n" + # f.tell # => 12 # f.close # - # Output: + # Related: IO#pos=, IO#seek. # - # "First li" - # "ne\nSecond li" - # "ne\n\nFourth li" - # "ne\nFifth li" - # "ne\n" + # IO#pos is an alias for IO#tell. # - # The two special values for `sep` are honored: + def tell: () -> Integer + + # + # Get the internal timeout duration or nil if it was not set. # - # f = File.new('t.txt') - # # Get all into one string. - # f.each_line(nil) {|line| p line } + %a{ruby:since:3.2.0} + def timeout: () -> io_timeout + + # The type used for timeouts in `IO`. + # + # Technically, this type should be `Time::_Timeout?`. However, in the vast majority of use-cases, + # people aren't going to pass their own `_Timeout` in, so `Numeric` is returned for ergonomics + # (eg `io.timeout += 10`). + type io_timeout = Numeric? + + # + # Set the internal timeout to the specified duration or nil. The timeout applies + # to all blocking operations where possible. + # + # This affects the following methods (but is not limited to): #gets, #puts, + # #read, #write, #wait_readable and #wait_writable. This also affects blocking + # socket operations like Socket#accept and Socket#connect. + # + # Some operations like File#open and IO#close are not affected by the timeout. A + # timeout during a write operation may leave the IO in an inconsistent state, + # e.g. data was partially written. Generally speaking, a timeout is a last ditch + # effort to prevent an application from hanging on slow I/O operations, such as + # those that occur during a slowloris attack. + # + %a{ruby:since:3.2.0} + def timeout=: (io_timeout duration) -> self + + # + # Returns the integer file descriptor for the stream: + # + # $stdin.fileno # => 0 + # $stdout.fileno # => 1 + # $stderr.fileno # => 2 + # File.open('t.txt').fileno # => 10 # f.close # - # Output: + alias to_i fileno + + # + # Returns `self`. # - # "First line\nSecond line\n\nFourth line\nFifth line\n" + %a{pure} + def to_io: () -> self + + %a{ruby:since:3.2.0} + alias to_path path + + # + # Returns `true` if the stream is associated with a terminal device (tty), + # `false` otherwise: # - # f.rewind - # # Get paragraphs (up to two line separators). - # f.each_line('') {|line| p line } + # f = File.new('t.txt').isatty #=> false + # f.close + # f = File.new('/dev/tty').isatty #=> true + # f.close # - # Output: + # IO#tty? is an alias for IO#isatty. # - # "First line\nSecond line\n\n" - # "Fourth line\nFifth line\n" + alias tty? isatty + + # + # Pushes back ("unshifts") the given data onto the stream's buffer, placing the + # data so that it is next to be read; returns `nil`. See [Byte + # IO](rdoc-ref:IO@Byte+IO). # - # With only integer argument `limit` given, limits the number of bytes in each - # line; see [Line Limit](rdoc-ref:IO@Line+Limit): + # Note that: # - # f = File.new('t.txt') - # f.each_line(8) {|line| p line } + # * Calling the method has no effect with unbuffered reads (such as + # IO#sysread). + # * Calling #rewind on the stream discards the pushed-back data. + # + # + # When argument `integer` is given, uses only its low-order byte: + # + # File.write('t.tmp', '012') + # f = File.open('t.tmp') + # f.ungetbyte(0x41) # => nil + # f.read # => "A012" + # f.rewind + # f.ungetbyte(0x4243) # => nil + # f.read # => "C012" # f.close # - # Output: + # When argument `string` is given, uses all bytes: # - # "First li" - # "ne\n" - # "Second l" - # "ine\n" - # "\n" - # "Fourth l" - # "ine\n" - # "Fifth li" - # "ne\n" + # File.write('t.tmp', '012') + # f = File.open('t.tmp') + # f.ungetbyte('A') # => nil + # f.read # => "A012" + # f.rewind + # f.ungetbyte('BCDE') # => nil + # f.read # => "BCDE012" + # f.close # - # With arguments `sep` and `limit` given, combines the two behaviors: + def ungetbyte: (Integer | string | nil byte) -> nil + + # + # Pushes back ("unshifts") the given data onto the stream's buffer, placing the + # data so that it is next to be read; returns `nil`. See [Character + # IO](rdoc-ref:IO@Character+IO). # - # * Calls with the next line as determined by line separator `sep`. - # * But returns no more bytes than are allowed by the limit. + # Note that: # + # * Calling the method has no effect with unbuffered reads (such as + # IO#sysread). + # * Calling #rewind on the stream discards the pushed-back data. # - # Optional keyword argument `chomp` specifies whether line separators are to be - # omitted: # - # f = File.new('t.txt') - # f.each_line(chomp: true) {|line| p line } + # When argument `integer` is given, interprets the integer as a character: + # + # File.write('t.tmp', '012') + # f = File.open('t.tmp') + # f.ungetc(0x41) # => nil + # f.read # => "A012" + # f.rewind + # f.ungetc(0x0442) # => nil + # f.getc.ord # => 1090 # f.close # + # When argument `string` is given, uses all characters: + # + # File.write('t.tmp', '012') + # f = File.open('t.tmp') + # f.ungetc('A') # => nil + # f.read # => "A012" + # f.rewind + # f.ungetc("\u0442\u0435\u0441\u0442") # => nil + # f.getc.ord # => 1090 + # f.getc.ord # => 1077 + # f.getc.ord # => 1089 + # f.getc.ord # => 1090 + # f.close + # + def ungetc: (Integer | string char) -> nil + + # + # Writes each of the given `objects` to `self`, which must be opened for writing + # (see [Access Modes](rdoc-ref:File@Access+Modes)); returns the total number + # bytes written; each of `objects` that is not a string is converted via method + # `to_s`: + # + # $stdout.write('Hello', ', ', 'World!', "\n") # => 14 + # $stdout.write('foo', :bar, 2, "\n") # => 8 + # # Output: # - # "First line" - # "Second line" - # "" - # "Fourth line" - # "Fifth line" + # Hello, World! + # foobar2 # - # Returns an Enumerator if no block is given. + # Related: IO#read. # - alias each each_line + def write: (*_ToS objects) -> Integer - # - # Returns `true` if the stream is positioned at its end, `false` otherwise; see - # [Position](rdoc-ref:IO@Position): + # + # Writes the given string to *ios* using the write(2) system call after + # O_NONBLOCK is set for the underlying file descriptor. # - # f = File.open('t.txt') - # f.eof # => false - # f.seek(0, :END) # => 0 - # f.eof # => true - # f.close + # It returns the number of bytes written. # - # Raises an exception unless the stream is opened for reading; see - # [Mode](rdoc-ref:File@Access+Modes). + # write_nonblock just calls the write(2) system call. It causes all errors the + # write(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The result + # may also be smaller than string.length (partial write). The caller should care + # such errors and partial write. # - # If `self` is a stream such as pipe or socket, this method blocks until the - # other end sends some data or closes it: + # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by + # IO::WaitWritable. So IO::WaitWritable can be used to rescue the exceptions for + # retrying write_nonblock. # + # # Creates a pipe. # r, w = IO.pipe - # Thread.new { sleep 1; w.close } - # r.eof? # => true # After 1-second wait. # - # r, w = IO.pipe - # Thread.new { sleep 1; w.puts "a" } - # r.eof? # => false # After 1-second wait. + # # write_nonblock writes only 65536 bytes and return 65536. + # # (The pipe size is 65536 bytes on this environment.) + # s = "a" * 100000 + # p w.write_nonblock(s) #=> 65536 # - # r, w = IO.pipe - # r.eof? # blocks forever + # # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN). + # p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN) # - # Note that this method reads data to the input byte buffer. So IO#sysread may - # not behave as you intend with IO#eof?, unless you call IO#rewind first (which - # is not available for some streams). + # If the write buffer is not empty, it is flushed at first. # - alias eof? eof - - # - # Returns the integer file descriptor for the stream: + # When write_nonblock raises an exception kind of IO::WaitWritable, + # write_nonblock should not be called until io is writable for avoiding busy + # loop. This can be done as follows. # - # $stdin.fileno # => 0 - # $stdout.fileno # => 1 - # $stderr.fileno # => 2 - # File.open('t.txt').fileno # => 10 - # f.close + # begin + # result = io.write_nonblock(string) + # rescue IO::WaitWritable, Errno::EINTR + # IO.select(nil, [io]) + # retry + # end # - alias to_i fileno -end - -IO::APPEND: Integer - -IO::BINARY: Integer - -IO::CREAT: Integer - -IO::DIRECT: Integer - -IO::DSYNC: Integer - -IO::EXCL: Integer - -IO::FNM_CASEFOLD: Integer - -IO::FNM_DOTMATCH: Integer - -IO::FNM_EXTGLOB: Integer - -IO::FNM_NOESCAPE: Integer - -IO::FNM_PATHNAME: Integer - -IO::FNM_SHORTNAME: Integer - -IO::FNM_SYSCASE: Integer - -IO::LOCK_EX: Integer - -IO::LOCK_NB: Integer - -IO::LOCK_SH: Integer - -IO::LOCK_UN: Integer - -IO::NOATIME: Integer - -IO::NOCTTY: Integer - -IO::NOFOLLOW: Integer - -IO::NONBLOCK: Integer - -IO::NULL: String - -IO::RDONLY: Integer - -IO::RDWR: Integer - -IO::RSYNC: Integer - -# -# Set I/O position from the current position -# -IO::SEEK_CUR: Integer - -# -# Set I/O position to the next location containing data -# -IO::SEEK_DATA: Integer - -# -# Set I/O position from the end -# -IO::SEEK_END: Integer - -# -# Set I/O position to the next hole -# -IO::SEEK_HOLE: Integer - -# -# Set I/O position from the beginning -# -IO::SEEK_SET: Integer - -IO::SHARE_DELETE: Integer - -IO::SYNC: Integer - -IO::TMPFILE: Integer - -IO::TRUNC: Integer - -IO::WRONLY: Integer - -# -# Readable event mask for IO#wait. -# -IO::READABLE: Integer - -# -# Writable event mask for IO#wait. -# -IO::WRITABLE: Integer - -# -# Priority event mask for IO#wait. -# -IO::PRIORITY: Integer - -# -# exception to wait for reading by EAGAIN. see IO.select. -# -class IO::EAGAINWaitReadable < Errno::EAGAIN - include IO::WaitReadable -end - -IO::EAGAINWaitReadable::Errno: Integer - -# -# exception to wait for writing by EAGAIN. see IO.select. -# -class IO::EAGAINWaitWritable < Errno::EAGAIN - include IO::WaitWritable -end - -IO::EAGAINWaitWritable::Errno: Integer - -# -# exception to wait for reading by EINPROGRESS. see IO.select. -# -class IO::EINPROGRESSWaitReadable < Errno::EINPROGRESS - include IO::WaitReadable -end + # Note that this doesn't guarantee to write all data in string. The length + # written is reported as result and it should be checked later. + # + # On some platforms such as Windows, write_nonblock is not supported according + # to the kind of the IO object. In such cases, write_nonblock raises + # `Errno::EBADF`. + # + # By specifying a keyword argument *exception* to `false`, you can indicate that + # write_nonblock should not raise an IO::WaitWritable exception, but return the + # symbol `:wait_writable` instead. + # + def write_nonblock: (_ToS string, ?exception: true) -> Integer + | (_ToS string, exception: false) -> (Integer | :wait_writable) -IO::EINPROGRESSWaitReadable::Errno: Integer + # + # Waits until the IO becomes ready for the specified events and returns the + # subset of events that become ready, or a falsy value when times out. + # + # The events can be a bit mask of `IO::READABLE`, `IO::WRITABLE` or + # `IO::PRIORITY`. + # + # Returns a truthy value immediately when buffered data is available. + # + # Optional parameter `mode` is one of `:read`, `:write`, or `:read_write`. + # + %a{ruby:since:3.2.0} + def wait: (int events, Time::_Timeout timeout) -> Integer + | (int events, Time::_Timeout? timeout) -> Integer? + | (*wait_mode | Time::_Timeout events_or_timeout) -> (self | bool)? -# -# exception to wait for writing by EINPROGRESS. see IO.select. -# -class IO::EINPROGRESSWaitWritable < Errno::EINPROGRESS - include IO::WaitWritable -end + # The mode to use in `IO#wait`. + type wait_mode = :r | :read | :readable | :w | :write | :writable | :rw | :read_write | :readable_writable -IO::EINPROGRESSWaitWritable::Errno: Integer + # + # Waits until IO is readable and returns a truthy value, or a falsy value when + # times out. Returns a truthy value immediately when buffered data is + # available. + # + %a{ruby:since:3.2.0} + def wait_readable: (?nil) -> (self | bool) + | (Time::_Timeout timeout) -> (self | bool)? -# -# exception to wait for reading. see IO.select. -# -module IO::WaitReadable -end + # + # Waits until IO is writable and returns a truthy value or a falsy value when + # times out. + # + %a{ruby:since:3.2.0} + def wait_writable: (?nil) -> (self | bool) + | (Time::_Timeout timeout) -> (self | bool)? -# -# exception to wait for writing. see IO.select. -# -module IO::WaitWritable + %a{ruby:since:3.2.0} + def wait_priority: (?nil) -> (self | bool) + | (Time::_Timeout timeout) -> (self | bool)? end diff --git a/core/io/wait.rbs b/core/io/wait.rbs index cdeb653d1..7e24be37a 100644 --- a/core/io/wait.rbs +++ b/core/io/wait.rbs @@ -20,51 +20,4 @@ class IO # You must require 'io/wait' to use this method. # def ready?: () -> boolish - - # - # Waits until the IO becomes ready for the specified events and returns the - # subset of events that become ready, or a falsy value when times out. - # - # The events can be a bit mask of `IO::READABLE`, `IO::WRITABLE` or - # `IO::PRIORITY`. - # - # Returns a truthy value immediately when buffered data is available. - # - # Optional parameter `mode` is one of `:read`, `:write`, or `:read_write`. - # - # You must require 'io/wait' to use this method. - # - def wait: (Integer events, ?Time::_Timeout timeout) -> (Integer | false | nil) - | (?Time::_Timeout? timeout, *wait_mode mode) -> (self | true | false) - - type wait_mode = :read | :r | :readable | :write | :w | :writable | :read_write | :rw | :readable_writable - - # - # Waits until IO is readable and returns a truthy value, or a falsy value when - # times out. Returns a truthy value immediately when buffered data is - # available. - # - # You must require 'io/wait' to use this method. - # - def wait_readable: (?Time::_Timeout? timeout) -> boolish - - # - # Waits until IO is writable and returns a truthy value or a falsy value when - # times out. - # - # You must require 'io/wait' to use this method. - # - def wait_writable: (?Time::_Timeout? timeout) -> boolish end diff --git a/core/string.rbs b/core/string.rbs index 52dfa02ef..b65f10914 100644 --- a/core/string.rbs +++ b/core/string.rbs @@ -1926,9 +1926,10 @@ class String ?cr_newline: boolish, ?crlf_newline: boolish, ?lf_newline: boolish, - ?fallback: ^(String) -> string? | Method | _EncodeFallbackAref + ?fallback: ^(String) -> string? | Method | Encoding::_EncodeFallbackAref ) -> instance + %a{steep:deprecated} # use `Encoding::_EncodeFallbackAref` interface _EncodeFallbackAref def []: (String) -> string? end diff --git a/lib/rbs/unit_test/spy.rb b/lib/rbs/unit_test/spy.rb index a0240fe68..9d85da267 100644 --- a/lib/rbs/unit_test/spy.rb +++ b/lib/rbs/unit_test/spy.rb @@ -128,7 +128,7 @@ def wrapped_object end end.ruby2_keywords ) - end.new() + end.allocate() end end end diff --git a/test/stdlib/IO_test.rb b/test/stdlib/IO_test.rb index bb670069c..250a67684 100644 --- a/test/stdlib/IO_test.rb +++ b/test/stdlib/IO_test.rb @@ -3,6 +3,50 @@ require "io/wait" +module IOTestHelper + def verify_encoding_options(arg_types:, return_type:, args:) + # ensure each of the options work + with_string.and_nil do |replace| + assert_send_type "(#{arg_types}, replace: string?) -> #{return_type}", + *args, replace: replace + end + + fallback_object = BlankSlate.new.__with_object_methods(:method) + def fallback_object.[](x) = nil # `[]` just need to exist for the test + with proc{}, fallback_object, fallback_object.method(:[]) do |fallback| + assert_send_type "(#{arg_types}, fallback: Proc | Method | Encoding::_EncodeFallbackAref) -> #{return_type}", + *args, fallback: fallback + end + + # econv_opts; checks for `replace` being nil/nonnil + with :replace, nil do |replace| + assert_send_type "(#{arg_types}, invalid: :replace | nil, undef: :replace | nil) -> #{return_type}", + *args, invalid: replace, undef: replace + end + + with :text, :attr, nil do |xml| + assert_send_type "(#{arg_types}, xml: :text | :attr | nil) -> #{return_type}", + *args, xml: xml + end + + with :universal, :crlf, :cr, :lf, nil do |newline| + assert_send_type "(#{arg_types}, newline: :universal | :crlf | :cr | :lf | nil) -> #{return_type}", + *args, newline: newline + end + + with_boolish do |boolish| + assert_send_type "(#{arg_types}, universal_newline: boolish) -> #{return_type}", + *args, universal_newline: boolish + assert_send_type "(#{arg_types}, crlf_newline: boolish) -> #{return_type}", + *args, crlf_newline: boolish + assert_send_type "(#{arg_types}, cr_newline: boolish) -> #{return_type}", + *args, cr_newline: boolish + assert_send_type "(#{arg_types}, lf_newline: boolish) -> #{return_type}", + *args, lf_newline: boolish + end + end +end + class IOSingletonTest < Test::Unit::TestCase include TestHelper @@ -183,335 +227,1137 @@ def test_select class IOInstanceTest < Test::Unit::TestCase include TestHelper + include IOTestHelper - testing "::IO" + testing '::IO' - def test_append_symbol - Dir.mktmpdir do |dir| - File.open(File.join(dir, "some_file"), "w") do |io| - assert_send_type "(String) -> File", - io, :<<, "foo" - assert_send_type "(Object) -> File", - io, :<<, Object.new + def open_io(path, mode:, **kw) + io = IO.new(IO.sysopen(path, mode), mode, **kw) + yield io + ensure + io.close + end + + SMALL_TEXT_FILE = File.join(__dir__, 'util', 'small-file.txt') + + def open_read(path: SMALL_TEXT_FILE, **k, &b) = open_io(path, mode: 'r', **k, &b) + def open_write(**k, &b) = open_io(File::NULL, mode: 'w', **k, &b) + def open_either(**k, &b) = open_io(File::NULL, mode: 'r', **k, &b) + + def with_open_mode(mode = :read, &block) + case mode + when :read + with_string('r', &block) + with_int(File::Constants::RDONLY, &block) + when :write + with_string('w', &block) + with_int(File::Constants::WRONLY, &block) + else + raise ArgumentError, "unknown mode `#{mode.inspect}`" + end + + block.call(nil) + end + + def test_initialize + stdinfd = STDIN.fileno + with_int stdinfd do |fd| + assert_send_type '(int) -> IO', + IO.allocate, :initialize, fd + assert_send_type '(int) -> IO', + IO.allocate, :initialize, fd + + with_open_mode do |mode| + assert_send_type '(int, IO::open_mode) -> IO', + IO.allocate, :initialize, fd, mode + assert_send_type '(int, IO::open_mode) -> IO', + IO.allocate, :initialize, fd, mode + end + end + + # just test each option individually for efficiency. The comments are which C functions + # the flags come from, to keep things organized + + # rb_io_initialize: + with_boolish do |boolish| + assert_send_type '(int, autoclose: boolish) -> IO', + IO.allocate, :initialize, stdinfd, autoclose: boolish + end + + if RUBY_VERSION > '3.2' + with_string.and_nil do |path| + assert_send_type '(int, path: string?) -> IO', + IO.allocate, :initialize, stdinfd, path: path + end + end + + # rb_io_extract_modeenc: + with_open_mode do |mode| + assert_send_type '(int, mode: IO::open_mode) -> IO', + IO.allocate, :initialize, stdinfd, mode: mode + end + + with_int(File::Constants::RDONLY).and_nil do |flags| + assert_send_type '(int, flags: int?) -> IO', + IO.allocate, :initialize, stdinfd, flags: flags + end + + # extract_binmode: + with_boolish do |boolish| + assert_send_type '(int, textmode: boolish) -> IO', + IO.allocate, :initialize, stdinfd, textmode: boolish + assert_send_type '(int, binmode: boolish) -> IO', + IO.allocate, :initialize, stdinfd, binmode: boolish + end + + # rb_io_extract_encoding_option: + with_encoding.and_nil do |enc| + assert_send_type '(int, encoding: encoding?) -> IO', + IO.allocate, :initialize, stdinfd, encoding: enc + assert_send_type '(int, external_encoding: encoding?, internal_encoding: encoding?) -> IO', + IO.allocate, :initialize, stdinfd, external_encoding: enc, internal_encoding: enc + end + + verify_encoding_options( + arg_types: 'int', + return_type: 'IO', + args: [IO.allocate, :initialize, stdinfd] + ) + + assert_send_type '(int, **untyped) -> IO', + IO.allocate, :initialize, stdinfd, THESE_ARE: 'NOT VALID', OPTIONS: true + end + + def test_initialize_copy + with_io STDOUT do |io| + assert_send_type '(io) -> IO', + IO.allocate, :initialize_copy, io + end + end + + def test_lsh + open_write do |io| + with_to_s do |obj| + assert_send_type '(_ToS) -> IO', + io, :<<, obj end end end def test_advise - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type "(Symbol) -> nil", - io, :advise, :normal - assert_send_type "(Symbol) -> nil", - io, :advise, :sequential - assert_send_type "(Symbol) -> nil", - io, :advise, :random - assert_send_type "(Symbol) -> nil", - io, :advise, :willneed - assert_send_type "(Symbol) -> nil", - io, :advise, :dontneed - assert_send_type "(Symbol) -> nil", - io, :advise, :noreuse - assert_send_type "(Symbol, Integer) -> nil", - io, :advise, :normal, 1 - assert_send_type "(Symbol, Integer, Integer) -> nil", - io, :advise, :normal, 1, 2 + open_either do |io| + %i[normal sequential random noreuse willneed dontneed].each do |advice| + assert_send_type '(IO::advice) -> nil', + io, :advise, advice + with_int.and_nil do |offset| + assert_send_type '(IO::advice, int?) -> nil', + io, :advise, advice, offset + + with_int.and_nil do |len| + assert_send_type '(IO::advice, int?, int?) -> nil', + io, :advise, advice, offset, len + end + end + end end end def test_autoclose= - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type "(bool) -> bool", - io, :autoclose=, true - assert_send_type "(bool) -> bool", - io, :autoclose=, false - assert_send_type "(::Integer) -> ::Integer", - io, :autoclose=, 42 - assert_send_type "(nil) -> nil", - io, :autoclose=, nil + open_either do |io| + with_boolish do |bool| + assert_send_type '[T] (T) -> T', + io, :autoclose=, bool + end end end def test_autoclose? - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type "() -> bool", - io, :autoclose? + open_either do |io| + assert_send_type '() -> bool', + io, :autoclose? end end - def test_path - IO.open(IO.sysopen(File.expand_path(__FILE__)), path: "foo") do |io| - assert_send_type( - "() -> String", - io, :path - ) + def test_binmode + open_either do |io| + assert_send_type '() -> IO', + io, :binmode end + end - IO.open(IO.sysopen(File.expand_path(__FILE__)), path: nil) do |io| - assert_send_type( - "() -> nil", - io, :path - ) + def test_binmode? + open_either do |io| + assert_send_type '() -> bool', + io, :binmode? end end - def test_read - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type "() -> String", - io, :read - assert_send_type "(Integer) -> String", - io, :read, 0 - assert_send_type "(Integer) -> nil", - io, :read, 1 - assert_send_type "(nil) -> String", - io, :read, nil - assert_send_type "(Integer, String) -> String", - io, :read, 0, "buffer" - assert_send_type "(Integer, String) -> nil", - io, :read, 1, "buffer" - assert_send_type "(nil, String) -> String", - io, :read, nil, "buffer" + def test_close + open_either do |io| + assert_send_type '() -> nil', + io, :close end end - def test_readpartial - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type "(Integer) -> String", - io, :readpartial, 10 - assert_send_type "(Integer, String) -> String", - io, :readpartial, 10, "buffer" + def test_close_on_exec= + open_either do |io| + with_boolish do |boolish| + assert_send_type '(boolish) -> nil', + io, :close_on_exec=, boolish + end end end - def test_timeout - io, _ = IO.pipe() + def test_close_on_exec? + open_either do |io| + assert_send_type '() -> bool', + io, :close_on_exec? + end + end - assert_send_type( - "(Integer) -> IO", - io, :timeout=, 1 - ) - assert_send_type( - "() -> Integer", - io, :timeout - ) + def test_close_read + open_read do |io| + assert_send_type '() -> nil', + io, :close_read + end + end - assert_send_type( - "(Float) -> IO", - io, :timeout=, 1.2 - ) - assert_send_type( - "() -> Float", - io, :timeout - ) + def test_close_write + open_write do |io| + assert_send_type '() -> nil', + io, :close_write + end + end - assert_send_type( - "(Rational) -> IO", - io, :timeout=, 3r - ) - assert_send_type( - "() -> Rational", - io, :timeout - ) + def test_closed? + open_either do |io| + assert_send_type '() -> bool', + io, :closed? + end + end - assert_send_type( - "(nil) -> IO", - io, :timeout=, nil - ) - assert_send_type( - "() -> nil", - io, :timeout - ) + + def assert_limit_delim(method:, return_type:, path: SMALL_TEXT_FILE) + open_read path: path do |io| + assert_send_type "() -> #{return_type}", + io, method + io.rewind + + with_string("a").and_nil do |delim| + assert_send_type "(string?) -> #{return_type}", + io, method, delim + io.rewind + + with_int(10).and_nil do |limit| + assert_send_type "(string?, int?) -> #{return_type}", + io, method, delim, limit + io.rewind + + with_boolish do |chomp| + assert_send_type "(string?, int?, chomp: boolish) -> #{return_type}", + io, method, delim, limit, chomp: chomp + io.rewind + end + end + + with_boolish do |chomp| + assert_send_type "(string?, chomp: boolish) -> #{return_type}", + io, method, delim, chomp: chomp + io.rewind + end + end + + with_int(10).and_nil do |limit| + assert_send_type "(int?) -> #{return_type}", + io, method, limit + io.rewind + + with_boolish do |chomp| + assert_send_type "(int?, chomp: boolish) -> #{return_type}", + io, method, limit, chomp: chomp + io.rewind + end + end + + with_boolish do |chomp| + assert_send_type "(chomp: boolish) -> #{return_type}", + io, method, chomp: chomp + io.rewind + end + end end - def test_write - Dir.mktmpdir do |dir| - File.open(File.join(dir, "some_file"), "w") do |io| - assert_send_type "() -> Integer", - io, :write - assert_send_type "(String) -> Integer", - io, :write, "foo" - assert_send_type "(String, Float) -> Integer", - io, :write, "foo", 1.5 + def test_each(method: :each) + open_read do |io| + assert_send_type '() -> Enumerator[String, IO]', + io, method + io.rewind + assert_send_type '() { (String) -> void } -> IO', + io, method do end + io.rewind + + with_string("a").and_nil do |delim| + assert_send_type '(string?) -> Enumerator[String, IO]', + io, method, delim + io.rewind + assert_send_type '(string?) { (String) -> void } -> IO', + io, method, delim do end + io.rewind + + with_int(10).and_nil do |limit| + assert_send_type '(string?, int?) -> Enumerator[String, IO]', + io, method, delim, limit + io.rewind + assert_send_type '(string?, int?) { (String) -> void } -> IO', + io, method, delim, limit do end + io.rewind + + with_boolish do |chomp| + assert_send_type '(string?, int?, chomp: boolish) -> Enumerator[String, IO]', + io, method, delim, limit, chomp: chomp + io.rewind + assert_send_type '(string?, int?, chomp: boolish) { (String) -> void } -> IO', + io, method, delim, limit, chomp: chomp do end + io.rewind + end + end + + with_boolish do |chomp| + assert_send_type '(string?, chomp: boolish) -> Enumerator[String, IO]', + io, method, delim, chomp: chomp + io.rewind + assert_send_type '(string?, chomp: boolish) { (String) -> void } -> IO', + io, method, delim, chomp: chomp do end + io.rewind + end + end + + with_int(10).and_nil do |limit| + assert_send_type '(int?) -> Enumerator[String, IO]', + io, method, limit + io.rewind + assert_send_type '(int?) { (String) -> void } -> IO', + io, method, limit do end + io.rewind + + with_boolish do |chomp| + assert_send_type '(int?, chomp: boolish) -> Enumerator[String, IO]', + io, method, limit, chomp: chomp + io.rewind + assert_send_type '(int?, chomp: boolish) { (String) -> void } -> IO', + io, method, limit, chomp: chomp do end + io.rewind + end + end + + with_boolish do |chomp| + assert_send_type '(chomp: boolish) -> Enumerator[String, IO]', + io, method, chomp: chomp + io.rewind + assert_send_type '(chomp: boolish) { (String) -> void } -> IO', + io, method, chomp: chomp do end + io.rewind end end end - def test_close_on_exec - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type '() -> bool', - io, :close_on_exec? - assert_send_type '(::Integer) -> untyped', - io, :close_on_exec=, 42 - assert_send_type '() -> bool', - io, :close_on_exec? - assert_send_type '(nil) -> nil', - io, :close_on_exec=, nil - assert_send_type '() -> bool', - io, :close_on_exec? + def test_each_line + test_each(method: :each_line) + end + + def test_each_byte + open_read do |io| + assert_send_type '() -> Enumerator[Integer, IO]', + io, :each_byte + io.rewind + assert_send_type '() { (Integer) -> void } -> IO', + io, :each_byte do end end end - def test_sync - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type '() -> bool', - io, :sync - assert_send_type '(::Integer) -> ::Integer', - io, :sync=, 42 - assert_send_type '() -> bool', - io, :sync - assert_send_type '(nil) -> nil', - io, :sync=, nil - assert_send_type '() -> bool', - io, :sync + def test_each_char + open_read do |io| + assert_send_type '() -> Enumerator[String, IO]', + io, :each_char + io.rewind + assert_send_type '() { (String) -> void } -> IO', + io, :each_char do end + end + end + + def test_each_codepoint + open_read do |io| + assert_send_type '() -> Enumerator[Integer, IO]', + io, :each_codepoint + io.rewind + assert_send_type '() { (Integer) -> void } -> IO', + io, :each_codepoint do end + end + end + + def test_eof(method: :eof) + open_read do |io| + assert_send_type '() -> bool', + io, method + end + end + + def test_eof? + test_eof(method: :eof?) + end + + def test_external_encoding + open_write external_encoding: nil do |io| + assert_send_type '() -> nil', + io, :external_encoding + end + + open_either external_encoding: 'UTF-8' do |io| + assert_send_type '() -> Encoding', + io, :external_encoding + end + end + + def test_fcntl + # There's no way to test `fcntl` safely and consistently (as the first argument is + # system-dependent, and Ruby doesn't expose it), so we'll just leave it untested. + end + + def test_fdatasync + open_either do |io| + assert_send_type '() -> 0', + io, :fdatasync + end + end + + def test_fileno(method: :fileno) + open_either do |io| + assert_send_type '() -> Integer', + io, method + end + end + + def test_flush + open_either do |io| + assert_send_type '() -> IO', + io, :flush + end + end + + def test_fsync + open_either do |io| + assert_send_type '() -> 0', + io, :fsync + end + end + + def test_getbyte + open_read path: File::NULL do |io| + assert_send_type '() -> nil', + io, :getbyte + end + + open_read do |io| + assert_send_type '() -> Integer', + io, :getbyte + end + end + + def test_getc + open_read path: File::NULL do |io| + assert_send_type '() -> nil', + io, :getc + end + + open_read do |io| + assert_send_type '() -> String', + io, :getc end end def test_gets - IO.open(IO.sysopen(File.expand_path(__FILE__))) do |io| - assert_send_type '() -> String', - io, :gets - assert_send_type '(String) -> String', - io, :gets, "" - assert_send_type '(Integer) -> String', - io, :gets, 10 - assert_send_type '(chomp: bool) -> String', - io, :gets, chomp: true - - assert_send_type '(Integer, chomp: bool) -> String', - io, :gets, 42, chomp: true - assert_send_type '(String, Integer, chomp: bool) -> String', - io, :gets, "", 42, chomp: true - assert_send_type '(String, chomp: bool) -> String', - io, :gets, "", chomp: true - - assert_send_type '(nil) -> String', - io, :gets, nil - assert_send_type '(nil, chomp: bool) -> nil', - io, :gets, nil, chomp: true + assert_limit_delim(method: :gets, return_type: 'String') + assert_limit_delim(method: :gets, return_type: 'nil', path: File::NULL) + end + + def test_inspect + open_either do |io| + assert_send_type '() -> String', + io, :inspect end end -end -class IOWaitTest < Test::Unit::TestCase - include TestHelper + def test_internal_encoding + open_write internal_encoding: nil do |io| + assert_send_type '() -> nil', + io, :internal_encoding + end - testing "::IO" + open_either internal_encoding: 'UTF-8' do |io| + assert_send_type '() -> Encoding', + io, :internal_encoding + end + end - def test_readyp - if_ruby31 do - # This method returns true|false in Ruby 2.7, nil|IO in 3.0, and true|false in 3.1. + def test_ioctl + # There's no way to test `ioctl` safely and consistently (as the first argument is + # system-dependent, and Ruby doesn't expose it), so we'll just leave it untested. + end - IO.pipe.tap do |r, w| - assert_send_type( - "() -> untyped", - r, :ready? - ) + def test_isatty(method: :isatty) + open_either do |io| + assert_send_type '() -> bool', + io, method + end + end + + def test_lineno + open_either do |io| + assert_send_type '() -> Integer', + io, :lineno + end + end + + def test_lineno= + open_either do |io| + with_int 0 do |lineno| + assert_send_type '[T < _ToInt] (T) -> T', + io, :lineno=, lineno end + end + end - IO.pipe.tap do |r, w| - w.write("hello") + def test_path(method: :path) + omit_if RUBY_VERSION < '3.2.0' - assert_send_type( - "() -> untyped", - r, :ready? - ) + assert_send_type '() -> nil', + IO.new(STDIN.fileno), method # FD 0 doesn't have a path, but STDIN does. + open_read do |io| + assert_send_type '() -> String', + io, method + end + end + + def test_pid + open_either do |io| + assert_send_type '() -> nil', + io, :pid + end + + io = IO.popen [RUBY_EXE, '-e', '1'] + begin + assert_send_type '() -> Integer', + io, :pid + ensure + io.close + end + end + + def test_pos + test_tell(method: :pos) + end + + def test_pos= + open_either do |io| + with_int 0 do |new_position| + assert_send_type '(int) -> Integer', + io, :pos=, new_position end end end - def test_wait_readable - if_ruby "3.0.0"..."3.2.0" do - IO.pipe.tap do |r, w| - w.write("hello") + def test_pread + open_read do |io| + with_int 3 do |length| + with_int 10 do |offset| + assert_send_type '(int, int) -> String', + io, :pread, length, offset + + with_string(+"").and_nil do |out_string| + assert_send_type '(int, int, string?) -> String', + io, :pread, length, offset, out_string + end + end + end + end + end - assert_send_type( - "() -> IO", - r, :wait_readable - ) + def test_print + open_write do |io| + assert_send_type '() -> nil', + io, :print + + with_to_s do |object| + assert_send_type '(*_ToS) -> nil', + io, :print, object, object end + end + end - IO.pipe.tap do |r, w| - assert_send_type( - "(Integer) -> nil", - r, :wait_readable, 1 - ) + def test_printf + open_write do |io| + with_string '%s, %s!' do |fmt| + assert_send_type '(string, *untyped) -> nil', + io, :printf, fmt, 'hello', 'world' end end end - def test_wait_writable - if_ruby "3.0.0"..."3.2.0" do - IO.pipe.tap do |r, w| - assert_send_type( - "() -> IO", - w, :wait_writable - ) + def test_putc + open_write do |io| + # NOTE: doesn't take a `_ToStr`, but just `String`. + assert_send_type '(String) -> String', + io, :putc, '&' + begin + io.putc ToStr.new('&') + rescue TypeError + pass 'io.putc does not accept _ToStr' + else + flunk '`io.putc` does not accept _ToStr' end - IO.pipe.tap do |r, w| - assert_send_type( - "(Integer) -> IO", - w, :wait_writable, 1 - ) + with_int 38 do |chr| + assert_send_type '[T < _ToInt] (T) -> T', + io, :putc, chr end end end - def test_nread - IO.pipe.tap do |r, w| - assert_send_type( - "() -> Integer", - r, :nread - ) + def test_puts + open_write do |io| + assert_send_type '() -> nil', + io, :puts + + with_to_s do |object| + assert_send_type '(*_ToS) -> nil', + io, :puts, object, object + end end end - def test_wait - if_ruby "3.0.0"..."3.2.0" do - IO.pipe.tap do |r, w| - w.write("hello") + def test_pwrite + open_write do |io| + with_to_s do |object| + with_int 0 do |offset| + assert_send_type '(_ToS, int) -> Integer', + io, :pwrite, object, offset + end + end + end + end - assert_send_type( - "(Integer) -> IO", - r, :wait, IO::READABLE - ) + def test_read + open_read do |io| + assert_send_type '() -> String', + io, :read + io.rewind + + with_int 3 do |length| + assert_send_type '(int) -> String', + io, :read, length + io.seek(0, IO::SEEK_END) + assert_send_type '(int) -> nil', + io, :read, length + io.rewind + + with_string(+"").and_nil do |outbuf| + assert_send_type '(int, string?) -> String', + io, :read, length, outbuf + io.seek(0, IO::SEEK_END) + assert_send_type '(int, string?) -> nil', + io, :read, length, outbuf + io.rewind + end end - IO.pipe.tap do |r, w| - w.write("hello") + io.seek(0, IO::SEEK_END) # reading from end of string with `nil` arg returns `""`. + assert_send_type '(nil) -> String', + io, :read, nil - assert_send_type( - "(Integer, Float) -> IO", - w, :wait, IO::WRITABLE, 0.1 - ) + with_string(+"").and_nil do |outbuf| + assert_send_type '(nil, string?) -> String', + io, :read, nil, outbuf end end + end - IO.pipe.tap do |r, w| - w.write("hello") + def test_read_nonblock + IO.pipe do |read, write| + with_int 1 do |len| + write.write_nonblock('AAA') + write.flush + + assert_send_type '(int) -> String', + read, :read_nonblock, len + assert_send_type '(int, exception: true) -> String', + read, :read_nonblock, len, exception: true + assert_send_type '(int, exception: false) -> String', + read, :read_nonblock, len, exception: false + assert_send_type '(int, exception: false) -> :wait_readable', # b/c only 3 chars were written + read, :read_nonblock, len, exception: false + + # ensure it actually throws an exception with `exception: true` and doesnt return + begin + read.read_nonblock(len) + rescue IO::WaitReadable + pass '.read_nonblock without anything left doesnt return nil/:wait_readable' + else + flunk '.read_nonblock returned something in the exception: true/not given case?' + end - assert_send_type( - "(Float, :read, :w, :readable_writable) -> IO", - r, :wait, 0.1, :read, :w, :readable_writable + with_string(+"").and_nil do |outbuf| + write.write_nonblock('AAA') + write.flush + + assert_send_type '(int, string?) -> String', + read, :read_nonblock, len, outbuf + assert_send_type '(int, string?, exception: true) -> String', + read, :read_nonblock, len, outbuf, exception: true + assert_send_type '(int, string?, exception: false) -> String', + read, :read_nonblock, len, outbuf, exception: false + assert_send_type '(int, string?, exception: false) -> :wait_readable', # b/c only 3 chars were written + read, :read_nonblock, len, outbuf, exception: false + + # ensure it actually throws an exception with `exception: true` and doesnt return + begin + read.read_nonblock(len, outbuf) + rescue IO::WaitReadable + pass '.read_nonblock without anything left doesnt return nil/:wait_readable' + else + flunk '.read_nonblock returned something in the exception: true/not given case?' + end + end + end + + write.close + with_int 1 do |len| + assert_send_type '(int, exception: false) -> nil', + read, :read_nonblock, len, exception: false + + # ensure it actually throws an exception with `exception: true` and doesnt return + begin + read.read_nonblock(len) + rescue EOFError + pass '.read_nonblock without anything left doesnt return nil/:wait_readable' + else + flunk '.read_nonblock returned something in the exception: true/not given case?' + end + + with_string.and_nil do |outbuf| + assert_send_type '(int, string?, exception: false) -> nil', + read, :read_nonblock, len, outbuf, exception: false + + # ensure it actually throws an exception with `exception: true` and doesnt return + begin + read.read_nonblock(len, outbuf) + rescue EOFError + pass '.read_nonblock without anything left doesnt return nil/:wait_readable' + else + flunk '.read_nonblock returned something in the exception: true/not given case?' + end + end + end + end + end + + def test_readbyte + open_read do |io| + assert_send_type '() -> Integer', + io, :readbyte + end + end + + def test_readchar + open_read do |io| + assert_send_type '() -> String', + io, :readchar + end + end + + def test_readline + assert_limit_delim(method: :readline, return_type: 'String') + end + + def test_readlines + assert_limit_delim(method: :readlines, return_type: 'Array[String]') + end + + def test_readpartial + open_read do |io| + with_int 3 do |maxlen| + assert_send_type '(int) -> String', + io, :readpartial, maxlen + io.rewind + + with_string(+"").and_nil do |outbuf| + assert_send_type '(int, string?) -> String', + io, :readpartial, maxlen, outbuf + io.rewind + end + end + end + end + + def test_reopen + # We use `open_read` so `with_open_mode` can use the same defaults; reopen doesnt need to just be read. + open_read do |io| + with_io STDIN do |newio| # we use STDIN so we can read, so `with_open_mode` can use read. + assert_send_type '(io) -> IO', + io, :reopen, newio + end + + with_path File::NULL do |path| + assert_send_type '(path) -> IO', + io, :reopen, path + + with_open_mode :read do |mode| + assert_send_type '(path, IO::open_mode) -> IO', + io, :reopen, path, mode + end + end + + # rb_io_extract_modeenc: + with_open_mode do |mode| + assert_send_type '(path, mode: IO::open_mode) -> IO', + io, :reopen, File::NULL, mode: mode + end + + with_int(File::Constants::RDONLY).and_nil do |flags| + assert_send_type '(path, flags: int?) -> IO', + io, :reopen, File::NULL, flags: flags + end + + # extract_binmode: + with_boolish do |boolish| + assert_send_type '(path, textmode: boolish) -> IO', + io, :reopen, File::NULL, textmode: boolish + assert_send_type '(path, binmode: boolish) -> IO', + io, :reopen, File::NULL, binmode: boolish + end + + # rb_io_extract_encoding_option: + with_encoding.and_nil do |enc| + assert_send_type '(path, encoding: encoding?) -> IO', + io, :reopen, File::NULL, encoding: enc + assert_send_type '(path, external_encoding: encoding?, internal_encoding: encoding?) -> IO', + io, :reopen, File::NULL, external_encoding: enc, internal_encoding: enc + end + + verify_encoding_options( + arg_types: 'path', + return_type: 'IO', + args: [io, :reopen, File::NULL] ) + + assert_send_type '(path, **untyped) -> IO', + io, :reopen, File::NULL, THESE_ARE: 'NOT VALID', OPTIONS: true + end + end + + def test_rewind + open_either do |io| + assert_send_type '() -> 0', + io, :rewind + end + end + + def test_seek + open_read do |io| + with_int 3 do |amount| + assert_send_type '(int) -> Integer', + io, :seek, amount + + whences = %i[SET CUR END] + whences << :DATA if defined? IO::SEEK_DATA + whences << :HOLE if defined? IO::SEEK_HOLE + + with_int(3).and(*whences) do |whence| + assert_send_type '(int, IO::whence) -> Integer', + io, :seek, amount, whence + end + end + end + end + + def test_set_encoding + open_either do |io| + with Encoding::UTF_8, nil do |ext_enc| + assert_send_type '(Encoding?, **untyped) -> IO', + io, :set_encoding, ext_enc, THESE_ARE: 'NOT VALID', OPTIONS: true + assert_send_type '(Encoding?, nil, **untyped) -> IO', + io, :set_encoding, ext_enc, nil, THESE_ARE: 'NOT VALID', OPTIONS: true + end + + # Make sure we use two different encodings; otherwise they'll shortcircuit + with_encoding Encoding::UTF_8 do |ext_enc| + with_encoding(Encoding::US_ASCII).and(with_string('-'), nil) do |int_enc| + assert_send_type '(encoding, encoding | "-" | nil) -> IO', + io, :set_encoding, ext_enc, int_enc + + verify_encoding_options( + arg_types: 'encoding, encoding | "-" | nil', + return_type: 'IO', + args: [io, :set_encoding, ext_enc, int_enc] + ) + + assert_send_type '(encoding, encoding | "-" | nil, **untyped) -> IO', + io, :set_encoding, ext_enc, int_enc, THESE_ARE: 'NOT VALID', OPTIONS: true + end + end end end def test_set_encoding_by_bom - open(IO::NULL, 'rb') do |f| - assert_send_type( - "() -> nil", - f, :set_encoding_by_bom - ) + Tempfile.create do |tmp| + tmp.write "\uFEFFabc" + + open_io tmp, mode: 'r', binmode: true do |io| + assert_send_type '() -> Encoding', + io, :set_encoding_by_bom + end end - file = Tempfile.new('test_set_encoding_by_bom') - file.write("\u{FEFF}abc") - file.close - open(file.path, 'rb') do |f| - assert_send_type( - "() -> Encoding", - f, :set_encoding_by_bom - ) + Tempfile.create do |tmp| + tmp.write 'abc!' + + open_io tmp, mode: 'r', binmode: true do |io| + assert_send_type '() -> nil', + io, :set_encoding_by_bom + end end end + + def test_stat + open_either do |io| + assert_send_type '() -> File::Stat', + io, :stat + end + end + + def test_sync + open_either do |io| + assert_send_type '() -> bool', + io, :sync + end + end + + def test_sync= + open_either do |io| + with_boolish do |boolish| + assert_send_type '[T] (T) -> T', + io, :sync=, boolish + end + end + end + + def test_sysread + open_read do |io| + with_int 3 do |maxlen| + assert_send_type '(int) -> String', + io, :sysread, maxlen + io.rewind + + with_string(+"").and_nil do |outbuf| + assert_send_type '(int, string?) -> String', + io, :sysread, maxlen, outbuf + io.rewind + end + end + end + end + + def test_sysseek + open_read do |io| + with_int 3 do |amount| + assert_send_type '(int) -> Integer', + io, :sysseek, amount + + whences = %i[SET CUR END] + whences << :DATA if defined? IO::SEEK_DATA + whences << :HOLE if defined? IO::SEEK_HOLE + + with_int(3).and(*whences) do |whence| + assert_send_type '(int, IO::whence) -> Integer', + io, :sysseek, amount, whence + end + end + end + end + + def test_syswrite + open_write do |io| + with_to_s do |object| + assert_send_type '(_ToS) -> Integer', + io, :syswrite, object + end + end + end + + def test_tell(method: :tell) + open_either do |io| + assert_send_type '() -> Integer', + io, method + end + end + + def test_timeout + omit_if RUBY_VERSION < '3.2.0' + + open_either do |io| + io.timeout = nil + assert_send_type '() -> nil', + io, :timeout + + io.timeout = 1r + assert_send_type '() -> IO::io_timeout', + io, :timeout + end + end + + def test_timeout= + omit_if RUBY_VERSION < '3.2.0' + + open_either do |io| + assert_send_type '(nil) -> IO', + io, :timeout=, nil + + with 1, 1.1, 1r do |timeout| + assert_send_type '(IO::io_timeout) -> IO', + io, :timeout=, timeout + end + end + end + + def test_to_i + test_fileno(method: :to_i) + end + + def test_to_io + open_either do |io| + assert_send_type '() -> IO', + io, :to_io + end + end + + def test_to_path + test_path(method: :to_path) + end + + def test_tty? + test_isatty(method: :tty?) + end + + def test_ungetbyte + open_read do |io| + with_string('hello world').and 38, nil do |byte| + assert_send_type '(Integer | string | nil) -> nil', + io, :ungetbyte, byte + + end + end + end + + def test_ungetc + open_read do |io| + with_string('hello world').and 38 do |chr| + assert_send_type '(Integer | string) -> nil', + io, :ungetc, chr + + end + end + end + + def test_write + open_write do |io| + assert_send_type '() -> Integer', + io, :write + + with_to_s do |object| + assert_send_type '(*_ToS) -> Integer', + io, :write, object, object + end + end + end + + def test_write_nonblock + IO.pipe do |read, write| + # don't use `with_to_s` because we want to be certain the object's small enough + # to be written in one go + with ToS.new("XYZ") do |object| + assert_send_type '(_ToS) -> Integer', + write, :write_nonblock, object + assert_send_type '(_ToS, exception: true) -> Integer', + write, :write_nonblock, object, exception: true + assert_send_type '(_ToS, exception: false) -> Integer', + write, :write_nonblock, object, exception: false + end + + with ToS.new("A"*100_000) do |object| + 100.times do + result = write.write_nonblock(object, exception: false) + if :wait_writable.equal?(result) + pass '`.write_nonblock(object, exception: false)` returned `:wait_readable`' + break + end + end and flunk "never got `:wait_readable` even after 100 executions" + + 100.times do + write.write_nonblock(object) + rescue IO::WaitWritable + pass '`.write_nonblock(object)` did not return anything when it couldnt write' + break + end and flunk "`.write_nonblock(object) returned something when it could write" + end + end + end + + def test_wait + IO.pipe do |read, write| + with_int IO::READABLE do |events| + with_timeout(seconds: 0, nanoseconds: 500).and_nil do |timeout| + write.puts 'Hello' + assert_send_type '(int, Time::_Timeout?) -> Integer', + read, :wait, events, timeout + read.gets + + next if nil.equal?(timeout) # `nil` timeout will never return + assert_send_type '(int, Time::_Timeout) -> nil', + read, :wait, events, timeout + end + end + + # For some reason, `IO.pipe`'s `read` can always be written to, so we need to make sure + # for the timeout part of the test, we use readable only. + %i[r read readable].each do |event| + with_timeout(seconds: 0, nanoseconds: 500) do |timeout| + assert_send_type '(*IO::wait_mode | Time::_Timeout) -> nil', + read, :wait, event, timeout, event + end + end + + %i[r read readable w write writable rw read_write readable_writable].each do |event| + with_timeout(seconds: 0, nanoseconds: 500) do |timeout| + write.puts 'hello' + assert_send_type '(*IO::wait_mode | Time::_Timeout) -> IO', + read, :wait, event, timeout, event + read.gets + end + end + + # NB (@sampersand): I can't figure out how to get `IO#wait` to return `false`, but I can get + # it to return `true`. Since I can't prove to myself it'll never return `false`, I've left + # the signature as `bool`, not `true`. + read.ungetc '&' + assert_send_type '(*IO::wait_mode | Time::_Timeout) -> true', + read, :wait, :read, 0, :read + end + end + + def test_wait_writable + omit 'todo: test wait_writable' + end + + def test_wait_priority + omit 'todo: test wait_priority' + end end diff --git a/test/stdlib/test_helper.rb b/test/stdlib/test_helper.rb index 1f23d3478..0e383c071 100644 --- a/test/stdlib/test_helper.rb +++ b/test/stdlib/test_helper.rb @@ -79,6 +79,12 @@ def with_timeout(seconds: 1, nanoseconds: 0) end end end + + def with_to_s(&block) + return WithEnum.new to_enum(__method__ || raise) unless block + with_bool(&block) + [nil, 1, Object.new, ToS.new, "hello, world!"].each(&block) + end end class Writer @@ -137,12 +143,6 @@ def each(&block) end end -class ArefFromStringToString < RBS::UnitTest::Convertibles::BlankSlate - def [](str) - "!" - end -end - include RBS::UnitTest::Convertibles module TestHelper @@ -152,6 +152,10 @@ module TestHelper include VersionHelper include WithStdlibAliases + # The current ruby executable path; used in some functions which spawn shells, as the only + # executable we can be guaranteed to exist is ruby itself. + RUBY_EXE = ENV['RUBY'] || RbConfig.ruby + def self.included(base) base.extend RBS::UnitTest::TypeAssertions::ClassMethods end diff --git a/test/stdlib/util/small-file.txt b/test/stdlib/util/small-file.txt new file mode 100644 index 000000000..40467c1b4 --- /dev/null +++ b/test/stdlib/util/small-file.txt @@ -0,0 +1,4 @@ +hello +world! what +is up? +:-)