Files
mruby-mruby/mrbgems/mruby-io/mrblib/io.rb
T
Yukihiro "Matz" Matsumoto 7b9d1da3fc mruby-io: add comprehensive call-seq documentation for all Ruby and C methods
Added complete call-seq documentation for the entire mruby-io gem across
both Ruby and C implementations:

## Ruby Methods (mrblib/) - 50 methods documented:

### Kernel Module (kernel.rb):
- Backtick operator: shell command execution with output capture
- open: unified file/subprocess opening with pipe support
- p: debug output with inspect formatting and multiple argument handling
- print/puts/printf: output methods with proper formatting and separators
- gets/readline/readlines: input methods with various line handling options

### File Constants (file_constants.rb):
- FNM_* constants: file name matching flags for glob and fnmatch operations
  with detailed explanations of case sensitivity, escaping, and pattern behavior

### IO Class (io.rb):
Class methods:
- IO.open: creates IO objects with automatic resource management
- IO.popen: subprocess communication with pipe handling
- IO.pipe: creates connected pipe endpoints for IPC
- IO.read: convenience method for reading entire files

Instance methods:
- Stream positioning: pos=, rewind, tell with proper seeking behavior
- Iteration: each, each_byte, each_char with enumerator support
- Output: puts, print, printf with formatting and newline handling
- Utility: hash, <<, ungetbyte with proper stream manipulation
- Global streams: STDIN/STDOUT/STDERR and $stdin/$stdout/$stderr

### File Class (file.rb):
Instance methods:
- Constructor: handles both file paths and file descriptors
- Timestamps: atime, ctime, mtime with proper Time object conversion
- Inspection: inspect method for debugging file objects

Class methods:
- Path utilities: join with cross-platform separator handling
- File iteration: foreach with block and enumerator support
- FileTest delegation: complete set of file type and existence checks
  (directory?, exist?, file?, pipe?, size, socket?, symlink?, zero?)
- Path manipulation: extname for extension extraction, path for conversion

## C Methods (src/) - 25 methods documented:

### Core IO Operations (io.c):
- File descriptor management: fileno with proper error handling
- Stream state: closed?, eof?, sync/sync= for buffering control
- Process management: pid for pipe process tracking
- Resource management: close_on_exec?/close_on_exec= for FD_CLOEXEC handling

### Reading Operations:
- Character reading: getc, readchar with EOF handling differences
- Byte reading: getbyte, readbyte with integer conversion
- Buffer reading: read with length and output buffer support
- Stream manipulation: ungetc for character pushback

### System Operations:
- IO multiplexing: IO.select for monitoring multiple streams
- Constructor: IO.new for creating IO objects from file descriptors
- Stream flushing: flush for forcing output to OS

Co-authored-by: Atlassian Rovo Dev
2025-08-14 10:52:48 +09:00

399 lines
9.6 KiB
Ruby

##
# IO
#
# ISO 15.2.20
class IOError < StandardError; end
class EOFError < IOError; end
class IO
#
# call-seq:
# IO.open(fd, mode="r" [, opt]) -> io
# IO.open(fd, mode="r" [, opt]) {|io| block } -> obj
#
# With no associated block, IO.open is a synonym for IO.new. If the optional
# code block is given, it will be passed io as an argument, and the IO object
# will automatically be closed when the block terminates. In this instance,
# IO.open returns the value of the block.
#
# fd = IO.sysopen("/dev/tty", "w")
# a = IO.open(fd,"w")
# $stderr.puts "Hello"
# a.close
#
def self.open(*args, &block)
io = self.new(*args)
return io unless block
begin
yield io
ensure
begin
io.close unless io.closed?
rescue StandardError
end
end
end
#
# call-seq:
# IO.popen(cmd, mode="r" [, opt]) -> io
# IO.popen(cmd, mode="r" [, opt]) {|io| block } -> obj
#
# Runs the specified command as a subprocess; the subprocess's standard input
# and output will be connected to the returned IO object.
#
# p IO.popen("date").read #=> "Wed Apr 9 08:56:30 CDT 2003\n"
# IO.popen("dc", "r+") {|f|
# f.puts "5 2 *"
# f.close_write
# puts f.read
# }
#
def self.popen(command, mode = 'r', **opts, &block)
if !self.respond_to?(:_popen)
raise NotImplementedError, "popen is not supported on this platform"
end
io = self._popen(command, mode, **opts)
return io unless block
begin
yield io
ensure
begin
io.close unless io.closed?
rescue IOError
# nothing
end
end
end
#
# call-seq:
# IO.pipe -> [read_io, write_io]
# IO.pipe {|read_io, write_io| ... } -> obj
#
# Creates a pair of pipe endpoints (connected to each other) and returns
# them as a two-element array of IO objects: [read_io, write_io].
#
# rd, wr = IO.pipe
# if fork
# wr.close
# puts rd.read
# rd.close
# Process.wait
# else
# rd.close
# wr.write "Hello, parent!"
# wr.close
# exit
# end
#
def self.pipe(&block)
if !self.respond_to?(:_pipe)
raise NotImplementedError, "pipe is not supported on this platform"
end
if block
begin
r, w = IO._pipe
yield r, w
ensure
r.close unless r.closed?
w.close unless w.closed?
end
else
IO._pipe
end
end
#
# call-seq:
# IO.read(name, [length [, offset]] ) -> string
# IO.read(name, [length [, offset]], mode: mode) -> string
#
# Opens the file, optionally seeks to the given offset, then returns length
# bytes (defaulting to the rest of the file). read ensures the file is
# closed before returning.
#
# IO.read("testfile") #=> "This is line one\nThis is line two\n"
# IO.read("testfile", 20) #=> "This is line one\nTh"
# IO.read("testfile", 20, 10) #=> "ne one\nThis is line "
#
def self.read(path, length=nil, offset=0, mode: "r")
str = ""
fd = -1
io = nil
begin
fd = IO.sysopen(path, mode)
io = IO.open(fd, mode)
io.seek(offset) if offset > 0
str = io.read(length)
ensure
if io
io.close
elsif fd != -1
IO._sysclose(fd)
end
end
str
end
#
# call-seq:
# ios.hash -> integer
#
# Compute a hash based on the IO object. Two IO objects with the same
# content will have the same hash code (and will compare using eql?).
# We must define IO#hash here because IO includes Enumerable and
# Enumerable#hash will call IO#read() otherwise.
#
def hash
# We must define IO#hash here because IO includes Enumerable and
# Enumerable#hash will call IO#read() otherwise
self.__id__
end
#
# call-seq:
# ios << obj -> ios
#
# String Output - Writes obj to ios. obj will be converted to a string using
# to_s.
#
# $stdout << "Hello " << "world!\n"
# Hello world!
#
def <<(str)
write(str)
self
end
# Alias for eof?
alias_method :eof, :eof?
# Alias for pos
alias_method :tell, :pos
#
# call-seq:
# ios.pos = integer -> integer
#
# Seeks to the given position (in bytes) in ios. It is not guaranteed that
# seeking to the right position when ios is textmode.
#
# f = File.new("testfile")
# f.pos = 17
# f.gets #=> "This is line two\n"
#
def pos=(i)
seek(i, SEEK_SET)
end
#
# call-seq:
# ios.rewind -> 0
#
# Positions ios to the beginning of input, resetting lineno to zero.
#
# f = File.new("testfile")
# f.readline #=> "This is line one\n"
# f.rewind #=> 0
# f.lineno #=> 0
# f.readline #=> "This is line one\n"
#
def rewind
seek(0, SEEK_SET)
end
#
# call-seq:
# ios.ungetbyte(string) -> nil
# ios.ungetbyte(integer) -> nil
#
# Pushes back bytes (passed as a parameter) onto ios, such that a subsequent
# buffered character read will return it. Only one byte may be pushed back
# before a subsequent read operation (that is, you will be able to read only
# the last of several bytes that have been pushed back). Has no effect with
# unbuffered reads (such as IO#sysread).
#
# f = File.new("testfile") #=> #<File:testfile>
# b = f.getbyte #=> 0x38
# f.ungetbyte(b) #=> nil
# f.getbyte #=> 0x38
#
def ungetbyte(c)
if c.is_a? String
c = c.getbyte(0)
else
c &= 0xff
end
s = " "
s.setbyte(0,c)
ungetc s
end
#
# call-seq:
# ios.each(sep=$/) {|line| block } -> ios
# ios.each(limit) {|line| block } -> ios
# ios.each(sep,limit) {|line| block } -> ios
# ios.each(...) -> an_enumerator
#
# Executes the block for every line in ios, where lines are separated by sep.
# ios must be opened for reading. If no block is given, an enumerator is returned instead.
#
# f = File.new("testfile")
# f.each {|line| puts "#{f.lineno}: #{line}" }
#
# 15.2.20.5.3
def each(&block)
return to_enum unless block
while line = self.gets
block.call(line)
end
self
end
#
# call-seq:
# ios.each_byte {|byte| block } -> ios
# ios.each_byte -> an_enumerator
#
# Calls the given block once for each byte (0..255) in ios, passing the byte
# as an argument. The stream must be opened for reading or an IOError will be raised.
#
# f = File.new("testfile")
# checksum = 0
# f.each_byte {|x| checksum ^= x } #=> #<File:testfile>
# checksum #=> 12
#
# 15.2.20.5.4
def each_byte(&block)
return to_enum(:each_byte) unless block
while byte = self.getbyte
block.call(byte)
end
self
end
# Alias for each - 15.2.20.5.5
alias each_line each
#
# call-seq:
# ios.each_char {|c| block } -> ios
# ios.each_char -> an_enumerator
#
# Calls the given block once for each character in ios, passing the character
# as an argument. The stream must be opened for reading or an IOError will be raised.
#
# f = File.new("testfile")
# ios.each_char {|c| print c, ' ' } #=> #<File:testfile>
#
def each_char(&block)
return to_enum(:each_char) unless block
while char = self.getc
block.call(char)
end
self
end
#
# call-seq:
# ios.puts(obj, ...) -> nil
#
# Writes the given objects to ios as strings, each followed by a newline
# character unless the string already ends with a newline. If called with
# an array argument, writes each element on a new line. If called without
# arguments, outputs a single newline.
#
# $stdout.puts("this", "is", "a", "test")
# this
# is
# a
# test
#
def puts(*args)
i = 0
len = args.size
if len == 0
write "\n"
return
end
while i < len
s = args[i]
if s.kind_of?(Array)
puts(*s) if s.size > 0
else
s = s.to_s
write s
write "\n" if (s[-1] != "\n")
end
i += 1
end
nil
end
#
# call-seq:
# ios.print() -> nil
# ios.print(obj, ...) -> nil
#
# Writes the given object(s) to ios. Objects that aren't strings will be
# converted by calling their to_s method. With no argument, prints the
# contents of the variable $_.
#
# $stdout.print("This is ", 100, " percent.\n")
# This is 100 percent.
#
def print(*args)
i = 0
len = args.size
while i < len
write args[i].to_s
i += 1
end
end
#
# call-seq:
# ios.printf(format_string [, obj, ...]) -> nil
#
# Formats and writes to ios, converting parameters under control of the format string.
# See sprintf for details of the format string.
#
# $stdout.printf "Number: %5.2f,\nString: %s\n", 1.23, "hello"
# Number: 1.23,
# String: hello
#
def printf(*args)
write sprintf(*args)
nil
end
# Alias for fileno - returns the integer file descriptor for ios
alias_method :to_i, :fileno
# Alias for isatty - returns true if ios is associated with a terminal device
alias_method :tty?, :isatty
end
# Standard input stream - connected to file descriptor 0
STDIN = IO.open(0, "r")
# Standard output stream - connected to file descriptor 1
STDOUT = IO.open(1, "w")
# Standard error stream - connected to file descriptor 2
STDERR = IO.open(2, "w")
# Global variable for standard input
$stdin = STDIN
# Global variable for standard output
$stdout = STDOUT
# Global variable for standard error
$stderr = STDERR