Files
mruby-mruby/mrbgems/mruby-io/mrblib/file.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

314 lines
6.9 KiB
Ruby

class File < IO
# The path to the file
attr_accessor :path
#
# call-seq:
# File.new(filename, mode="r") -> file
# File.new(filename [, mode [, perm]]) -> file
# File.new(fd [, mode]) -> file
#
# Opens the file named by filename according to the given mode and returns
# a new File object. If a file descriptor is given instead of a filename,
# the new File object will be associated with that descriptor.
#
# f = File.new("testfile", "r")
# f = File.new("newfile", "w+")
# f = File.new("tmpfile", "a")
#
def initialize(fd_or_path, mode = "r", perm = 0666)
if fd_or_path.kind_of? Integer
super(fd_or_path, mode)
else
@path = fd_or_path
fd = IO.sysopen(@path, mode, perm)
super(fd, mode)
end
end
#
# call-seq:
# file.atime -> time
#
# Returns the last access time for file, or epoch if the platform
# doesn't have access time.
#
# File.new("testfile").atime #=> Wed Apr 09 08:51:48 CDT 2003
#
def atime
t = self._atime
t && Time.at(t)
end
#
# call-seq:
# file.ctime -> time
#
# Returns the change time for file (that is, the time directory
# information about the file was changed, not the file itself).
#
# File.new("testfile").ctime #=> Wed Apr 09 08:53:13 CDT 2003
#
def ctime
t = self._ctime
t && Time.at(t)
end
#
# call-seq:
# file.mtime -> time
#
# Returns the modification time for file.
#
# File.new("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003
#
def mtime
t = self._mtime
t && Time.at(t)
end
#
# call-seq:
# file.inspect -> string
#
# Return a string describing this File object.
#
# File.new("testfile").inspect #=> "#<File:testfile>"
#
def inspect
"<#{self.class}:#{@path}>"
end
#
# call-seq:
# File.join(string, ...) -> string
#
# Returns a new string formed by joining the strings using the operating
# system's path separator (File::SEPARATOR).
#
# File.join("usr", "mail", "gumby") #=> "usr/mail/gumby"
# File.join("usr", "mail", "gumby") #=> "usr\\mail\\gumby" (on Windows)
#
def self.join(*names)
return "" if names.empty?
names.map! do |name|
case name
when String
name
when Array
if names == name
raise ArgumentError, "recursive array"
end
join(*name)
else
raise TypeError, "no implicit conversion of #{name.class} into String"
end
end
return names[0] if names.size == 1
if names[0][-1] == File::SEPARATOR
s = names[0][0..-2]
else
s = names[0].dup
end
(1..names.size-2).each { |i|
t = names[i]
if t[0] == File::SEPARATOR and t[-1] == File::SEPARATOR
t = t[1..-2]
elsif t[0] == File::SEPARATOR
t = t[1..-1]
elsif t[-1] == File::SEPARATOR
t = t[0..-2]
end
s += File::SEPARATOR + t if t != ""
}
if names[-1][0] == File::SEPARATOR
s += File::SEPARATOR + names[-1][1..-1]
else
s += File::SEPARATOR + names[-1]
end
s
end
#
# call-seq:
# File.foreach(name) {|line| block } -> nil
# File.foreach(name) -> an_enumerator
#
# Executes the block for every line in the named I/O port, where lines
# are separated by sep.
#
# File.foreach("testfile") {|x| print "GOT ", x }
# GOT This is line one
# GOT This is line two
# GOT This is line three
# GOT And so on...
#
def self.foreach(file)
if block_given?
self.open(file) do |f|
f.each {|l| yield l}
end
else
return self.new(file)
end
end
#
# call-seq:
# File.directory?(file_name) -> true or false
#
# Returns true if the named file is a directory, or a symlink that points at a directory, and false otherwise.
#
# File.directory?(".") #=> true
#
def self.directory?(file)
FileTest.directory?(file)
end
#
# call-seq:
# File.exist?(file_name) -> true or false
#
# Return true if the named file exists.
#
# File.exist?("config.h") #=> true
# File.exist?("no_such_file") #=> false
#
def self.exist?(file)
FileTest.exist?(file)
end
#
# call-seq:
# File.exists?(file_name) -> true or false
#
# Deprecated method that is equivalent to File.exist?.
#
def self.exists?(file)
FileTest.exists?(file)
end
#
# call-seq:
# File.file?(file) -> true or false
#
# Returns true if the named file exists and is a regular file.
#
# File.file?("testfile") #=> true
#
def self.file?(file)
FileTest.file?(file)
end
#
# call-seq:
# File.pipe?(file_name) -> true or false
#
# Returns true if the named file is a pipe.
#
# File.pipe?("/dev/stdin") #=> true
#
def self.pipe?(file)
FileTest.pipe?(file)
end
#
# call-seq:
# File.size(file_name) -> integer
#
# Returns the size of file_name.
#
# File.size("testfile") #=> 66
#
def self.size(file)
FileTest.size(file)
end
#
# call-seq:
# File.size?(file_name) -> integer or nil
#
# Returns nil if file_name doesn't exist or has zero size, the size of the file otherwise.
#
# File.size?("testfile") #=> 66
#
def self.size?(file)
FileTest.size?(file)
end
#
# call-seq:
# File.socket?(file_name) -> true or false
#
# Returns true if the named file is a socket.
#
# File.socket?("/tmp/.X11-unix/X0") #=> true
#
def self.socket?(file)
FileTest.socket?(file)
end
#
# call-seq:
# File.symlink?(file_name) -> true or false
#
# Returns true if the named file is a symbolic link.
#
def self.symlink?(file)
FileTest.symlink?(file)
end
#
# call-seq:
# File.zero?(file_name) -> true or false
#
# Returns true if the named file exists and has a zero size.
#
# File.zero?("testfile") #=> false
#
def self.zero?(file)
FileTest.zero?(file)
end
#
# call-seq:
# File.extname(path) -> string
#
# Returns the extension (the portion of file name in path starting from the
# last period). If path is a dotfile, or starts with a period, then the starting
# dot is not dealt with the start of the extension.
#
# File.extname("test.rb") #=> ".rb"
# File.extname("a/b/d/test.rb") #=> ".rb"
# File.extname("test") #=> ""
# File.extname(".profile") #=> ""
#
def self.extname(filename)
fname = self.basename(filename)
epos = fname.rindex('.')
return '' if epos == 0 || epos.nil?
return fname[epos..-1]
end
#
# call-seq:
# File.path(path) -> string
#
# Returns the string representation of the path
#
# File.path("/dev/null") #=> "/dev/null"
# File.path(Pathname.new("/tmp")) #=> "/tmp"
#
def self.path(filename)
if filename.kind_of?(String)
filename
else
raise TypeError, "no implicit conversion of #{filename.class} into String"
end
end
end