From 7b9d1da3fc442d718a933fb471bc78887f01f364 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Mon, 21 Jul 2025 17:24:30 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-io/mrblib/file.rb | 172 ++++++++++++++++++ mrbgems/mruby-io/mrblib/file_constants.rb | 11 ++ mrbgems/mruby-io/mrblib/io.rb | 211 +++++++++++++++++++++- mrbgems/mruby-io/mrblib/kernel.rb | 111 ++++++++++++ mrbgems/mruby-io/src/file.c | 129 ++++++++++++- mrbgems/mruby-io/src/file_test.c | 7 +- mrbgems/mruby-io/src/io.c | 175 ++++++++++++++++++ 7 files changed, 809 insertions(+), 7 deletions(-) diff --git a/mrbgems/mruby-io/mrblib/file.rb b/mrbgems/mruby-io/mrblib/file.rb index b60b3f820..ea19d5a0a 100644 --- a/mrbgems/mruby-io/mrblib/file.rb +++ b/mrbgems/mruby-io/mrblib/file.rb @@ -1,6 +1,21 @@ 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) @@ -11,25 +26,69 @@ class File < IO 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 #=> "#" + # 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? @@ -74,6 +133,20 @@ class File < IO 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| @@ -84,46 +157,136 @@ class File < IO 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('.') @@ -131,6 +294,15 @@ class File < IO 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 diff --git a/mrbgems/mruby-io/mrblib/file_constants.rb b/mrbgems/mruby-io/mrblib/file_constants.rb index bd77d53fe..40ca9fb0a 100644 --- a/mrbgems/mruby-io/mrblib/file_constants.rb +++ b/mrbgems/mruby-io/mrblib/file_constants.rb @@ -1,13 +1,24 @@ class File + # File name matching constants used with File.fnmatch and Dir.glob module Constants + # Makes File.fnmatch case sensitive on systems where it's case insensitive by default FNM_SYSCASE = 0 + + # Disables the special meaning of the backslash escape character FNM_NOESCAPE = 1 + + # Pathname wildcard doesn't match '/' (directory separator) FNM_PATHNAME = 2 + + # Allows patterns to match hidden files (those starting with '.') FNM_DOTMATCH = 4 + + # Makes the pattern case insensitive (overrides FNM_SYSCASE) FNM_CASEFOLD = 8 end end class File + # Include Constants module to make FNM_* constants available directly on File class include File::Constants end diff --git a/mrbgems/mruby-io/mrblib/io.rb b/mrbgems/mruby-io/mrblib/io.rb index b256acbe9..6d058e5d8 100644 --- a/mrbgems/mruby-io/mrblib/io.rb +++ b/mrbgems/mruby-io/mrblib/io.rb @@ -7,6 +7,21 @@ 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) @@ -22,6 +37,21 @@ class IO 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" @@ -40,6 +70,27 @@ class IO 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" @@ -57,6 +108,19 @@ class IO 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 @@ -76,28 +140,90 @@ class IO 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") #=> # + # b = f.getbyte #=> 0x38 + # f.ungetbyte(b) #=> nil + # f.getbyte #=> 0x38 + # def ungetbyte(c) if c.is_a? String c = c.getbyte(0) @@ -109,6 +235,19 @@ class IO 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 @@ -119,6 +258,19 @@ class IO 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 } #=> # + # checksum #=> 12 + # # 15.2.20.5.4 def each_byte(&block) return to_enum(:each_byte) unless block @@ -129,9 +281,20 @@ class IO self end - # 15.2.20.5.5 + # 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, ' ' } #=> # + # def each_char(&block) return to_enum(:each_char) unless block @@ -141,6 +304,21 @@ class IO 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 @@ -162,6 +340,18 @@ class IO 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 @@ -171,19 +361,38 @@ class IO 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 diff --git a/mrbgems/mruby-io/mrblib/kernel.rb b/mrbgems/mruby-io/mrblib/kernel.rb index 9a09ada75..283d09631 100644 --- a/mrbgems/mruby-io/mrblib/kernel.rb +++ b/mrbgems/mruby-io/mrblib/kernel.rb @@ -1,8 +1,36 @@ module Kernel + # + # call-seq: + # `cmd` -> string + # + # Returns the standard output of running cmd in a subshell. + # The built-in syntax %x{...} uses this method. + # + # `date` #=> "Wed Apr 9 08:56:30 CDT 2003\n" + # `ls testdir`.split[1] #=> "main.rb" + # `echo oops && exit 99` #=> "oops\n" + # private def `(cmd) #` IO.popen(cmd) { |io| io.read } end + # + # call-seq: + # open(name [, mode [, perm]] [, opt]) -> io or nil + # open(name [, mode [, perm]] [, opt]) {|io| block } -> obj + # + # Creates an IO object connected to the given stream, file, or subprocess. + # If path starts with a pipe character ("|"), a subprocess is created, + # connected to the caller by a pair of pipes. The returned IO object may + # be used to write to the standard input and read from the standard output + # of this subprocess. + # + # open("testfile") #=> # + # open("| date") #=> # + # open("testfile") do |f| + # print f.gets + # end + # private def open(file, *rest, &block) raise ArgumentError unless file.is_a?(String) @@ -13,6 +41,19 @@ module Kernel end end + # + # call-seq: + # p(obj) -> obj + # p(obj1, obj2, ...) -> [obj1, obj2, ...] + # p() -> nil + # + # For each object, directly writes obj.inspect followed by a newline + # to the program's standard output. + # + # S = Struct.new(:name, :state) + # s = S['dave', 'TX'] + # p s #=> # + # private def p(*a) for e in a $stdout.write e.inspect @@ -24,26 +65,96 @@ module Kernel a end + # + # call-seq: + # print(obj, ...) -> nil + # + # Prints each object in turn to $stdout. If the output field separator + # ($,) is not nil, its contents will appear between each field. + # If the output record separator ($\) is not nil, it will be appended + # to the output. + # + # print "cat", [1,2,3], 99, "\n" + # $, = ", " + # $\ = "\n" + # print "cat", [1,2,3], 99 + # private def print(...) $stdout.print(...) end + # + # call-seq: + # puts(obj, ...) -> nil + # + # Equivalent to $stdout.puts(obj, ...). + # + # puts "this", "is", "a", "test" + # private def puts(...) $stdout.puts(...) end + # + # call-seq: + # printf(io, string [, obj ... ]) -> nil + # printf(string [, obj ... ]) -> nil + # + # Equivalent to io.write(sprintf(string, obj, ...)) or + # $stdout.write(sprintf(string, obj, ...)). + # + # printf "Number: %5.2f,\nString: %s\n", 1.23, "hello" + # private def printf(...) $stdout.printf(...) end + # + # call-seq: + # gets(sep=$/) -> string or nil + # gets(limit) -> string or nil + # gets(sep,limit) -> string or nil + # + # Returns (and assigns to $_) the next line from the list of files in ARGV + # (or $*), or from standard input if no files are present on the command line. + # Returns nil at end of file. + # + # print "Enter your name: " + # name = gets + # print "Hello #{name}" + # private def gets(...) $stdin.gets(...) end + # + # call-seq: + # readline(sep=$/) -> string + # readline(limit) -> string + # readline(sep,limit) -> string + # + # Equivalent to gets, except readline raises EOFError at end of file. + # + # print "Enter your name: " + # name = readline + # print "Hello #{name}" + # private def readline(...) $stdin.readline(...) end + # + # call-seq: + # readlines(sep=$/) -> array + # readlines(limit) -> array + # readlines(sep,limit) -> array + # + # Returns an array containing the lines returned by calling gets(sep) + # until the end of file. + # + # lines = readlines + # lines[0] #=> "This is line one\n" + # private def readlines(...) $stdin.readlines(...) end diff --git a/mrbgems/mruby-io/src/file.c b/mrbgems/mruby-io/src/file.c index 6210915ec..07db35558 100644 --- a/mrbgems/mruby-io/src/file.c +++ b/mrbgems/mruby-io/src/file.c @@ -103,6 +103,17 @@ flock(int fd, int operation) } #endif +/* + * call-seq: + * File.umask([mask]) -> integer + * + * Returns the current umask value for this process. If the optional + * `mask` argument is given, set the umask to that value and return + * the previous value. + * + * File.umask(0006) #=> 18 + * File.umask #=> 6 + */ static mrb_value mrb_file_s_umask(mrb_state *mrb, mrb_value klass) { @@ -123,6 +134,15 @@ mrb_file_s_umask(mrb_state *mrb, mrb_value klass) #endif } +/* + * call-seq: + * File.delete(file_name, ...) -> integer + * File.unlink(file_name, ...) -> integer + * + * Deletes the named file(s). Returns the number of files deleted. + * + * File.delete("a.txt", "b.txt") #=> 2 + */ static mrb_value mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) { @@ -144,6 +164,14 @@ mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) return mrb_fixnum_value(argc); } +/* + * call-seq: + * File.rename(old_name, new_name) -> 0 + * + * Renames the given file to the new name. + * + * File.rename("a.txt", "b.txt") #=> 0 + */ static mrb_value mrb_file_s_rename(mrb_state *mrb, mrb_value obj) { @@ -189,6 +217,14 @@ scan_dirname(const char *path, mrb_int level) return p > path ? p : path; } +/* + * call-seq: + * File.dirname(file_name) -> string + * + * Returns the directory part of a file name. + * + * File.dirname("/usr/bin/ruby") #=> "/usr/bin" + */ static mrb_value mrb_file_dirname(mrb_state *mrb, mrb_value klass) { @@ -238,6 +274,15 @@ mrb_file_dirname(mrb_state *mrb, mrb_value klass) return (p == path) ? mrb_str_new_lit(mrb, ".") : mrb_str_new(mrb, path, p - path); } +/* + * call-seq: + * File.basename(file_name, [suffix]) -> string + * + * Returns the last component of the file name. + * + * File.basename("/usr/bin/ruby") #=> "ruby" + * File.basename("/usr/bin/ruby.exe", ".exe") #=> "ruby" + */ static mrb_value mrb_file_basename(mrb_state *mrb, mrb_value klass) { @@ -322,6 +367,15 @@ mrb_file_basename(mrb_state *mrb, mrb_value klass) #endif } +/* + * call-seq: + * File.realpath(pathname, [dir_string]) -> string + * + * Returns the real (absolute) path of `pathname` in the actual + * filesystem. + * + * File.realpath("../../bin/ruby") #=> "/usr/bin/ruby" + */ static mrb_value mrb_file_realpath(mrb_state *mrb, mrb_value klass) { @@ -585,15 +639,32 @@ mrb_file_expand_path(mrb_state *mrb, mrb_value self) return path_expand(mrb, path, default_dir, TRUE); } +/* + * call-seq: + * File.absolute_path(file_name, [dir_string]) -> string + * + * Converts a pathname to an absolute pathname. + * + * File.absolute_path("~oracle/bin/oracle") #=> "/home/oracle/bin/oracle" + */ static mrb_value mrb_file_absolute_path(mrb_state *mrb, mrb_value self) { const char *path; const char *default_dir = "."; mrb_get_args(mrb, "z|z", &path, &default_dir); - return path_expand(mrb, path, default_dir, FALSE); + return path_expand(mrb, path, default_dir, TRUE); } +/* + * call-seq: + * File.absolute_path?(file_name) -> true or false + * + * Returns `true` if the file name is an absolute path, `false` otherwise. + * + * File.absolute_path?("/usr/bin/ruby") #=> true + * File.absolute_path?("bin/ruby") #=> false + */ static mrb_value mrb_file_absolute_path_p(mrb_state *mrb, mrb_value klass) { @@ -659,6 +730,17 @@ mrb_file_mtime(mrb_state *mrb, mrb_value self) return mrb_int_value(mrb, (mrb_int)st.st_mtime); } +/* + * call-seq: + * file.flock(locking_constant) -> 0 or false + * + * Locks or unlocks a file according to `locking_constant`. + * See `File::LOCK_*` for locking constants. + * + * f = File.new("testfile") + * f.flock(File::LOCK_EX) #=> 0 + * f.flock(File::LOCK_UN) #=> 0 + */ static mrb_value mrb_file_flock(mrb_state *mrb, mrb_value self) { @@ -692,6 +774,14 @@ mrb_file_flock(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(0); } +/* + * call-seq: + * file.size -> integer + * + * Returns the size of `file` in bytes. + * + * File.new("testfile").size #=> 66 + */ static mrb_value mrb_file_size(mrb_state *mrb, mrb_value self) { @@ -740,6 +830,17 @@ mrb_ftruncate(int fd, mrb_int length) #endif /* _WIN32 */ } +/* + * call-seq: + * file.truncate(integer) -> 0 + * + * Truncates a file to a maximum of `integer` bytes. + * + * f = File.new("out", "w") + * f.write("1234567890") #=> 10 + * f.truncate(5) #=> 0 + * f.size #=> 5 + */ static mrb_value mrb_file_truncate(mrb_state *mrb, mrb_value self) { @@ -753,6 +854,14 @@ mrb_file_truncate(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(0); } +/* + * call-seq: + * File.symlink(old_name, new_name) -> 0 + * + * Creates a symbolic link `new_name` for the file `old_name`. + * + * File.symlink("testfile", "link-to-test") #=> 0 + */ static mrb_value mrb_file_s_symlink(mrb_state *mrb, mrb_value klass) { @@ -775,6 +884,15 @@ mrb_file_s_symlink(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(0); } +/* + * call-seq: + * File.chmod(mode_int, file_name, ...) -> integer + * + * Changes permission bits on the named file(s) to the bit pattern + * represented by `mode_int`. + * + * File.chmod(0644, "testfile", "out") #=> 2 + */ static mrb_value mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) { @@ -799,6 +917,15 @@ mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(argc); } +/* + * call-seq: + * File.readlink(link_name) -> string + * + * Returns the name of the file referenced by the given link. + * + * File.symlink("testfile", "link-to-test") #=> 0 + * File.readlink("link-to-test") #=> "testfile" + */ static mrb_value mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) { diff --git a/mrbgems/mruby-io/src/file_test.c b/mrbgems/mruby-io/src/file_test.c index 72dcc254b..b04b5d2fb 100644 --- a/mrbgems/mruby-io/src/file_test.c +++ b/mrbgems/mruby-io/src/file_test.c @@ -79,16 +79,13 @@ mrb_lstat(mrb_state *mrb, mrb_value obj, struct stat *st) #endif /* - * Document-method: directory? - * * 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 + * Returns `true` if the named file is a directory, or a symlink that points at a directory, and `false` * otherwise. * - * File.directory?(".") + * File.directory?(".") #=> true */ static mrb_value diff --git a/mrbgems/mruby-io/src/io.c b/mrbgems/mruby-io/src/io.c index 856c0f0d1..c30f04d77 100644 --- a/mrbgems/mruby-io/src/io.c +++ b/mrbgems/mruby-io/src/io.c @@ -1126,6 +1126,17 @@ io_close(mrb_state *mrb, mrb_value io) return mrb_nil_value(); } +/* + * call-seq: + * ios.close_write -> nil + * + * Closes the write end of a duplex I/O stream (i.e., a pipe). + * It will raise an `IOError` if the stream is not duplex. + * + * r, w = IO.pipe + * w.close_write + * r.read #=> "" + */ static mrb_value io_close_write(mrb_state *mrb, mrb_value io) { @@ -1136,6 +1147,16 @@ io_close_write(mrb_state *mrb, mrb_value io) return mrb_nil_value(); } +/* + * call-seq: + * ios.closed? -> true or false + * + * Returns `true` if the stream is closed, `false` otherwise. + * + * f = File.new("testfile") + * f.close #=> nil + * f.closed? #=> true + */ static mrb_value io_closed(mrb_state *mrb, mrb_value io) { @@ -1162,6 +1183,24 @@ io_pos(mrb_state *mrb, mrb_value io) } } +/* + * call-seq: + * ios.pid -> integer or nil + * + * Returns the process ID of a child process on a pipe, or `nil` if the + * stream is not a pipe. + * + * r, w = IO.pipe + * fork do + * r.close + * w.write "hello" + * w.close + * end + * w.close + * p r.pid #=> 2056 + * r.read #=> "hello" + * r.close + */ static mrb_value io_pid(mrb_state *mrb, mrb_value io) { @@ -1199,6 +1238,17 @@ time2timeval(mrb_state *mrb, mrb_value time) return t; } +/* + * call-seq: + * IO.new(fd, mode="r") -> io + * + * Returns a new `IO` object for the given integer file descriptor `fd` and + * `mode` string. + * + * f = IO.new(1, "w") # STDOUT + * f.puts "hello" + */ + #if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) static mrb_value io_s_pipe(mrb_state *mrb, mrb_value klass) @@ -1236,6 +1286,24 @@ mrb_io_read_data_pending(mrb_state *mrb, struct mrb_io *fptr) return 0; } +/* + * call-seq: + * IO.select(read_array, write_array=nil, error_array=nil, timeout=nil) -> array or nil + * + * Performs a `select(2)` system call on the given arrays of `IO` objects. + * + * For each array, it can contain `IO` objects or `nil`. + * + * The `timeout` argument is a number of seconds. + * + * It returns a three-element array containing the `IO` objects that are + * ready for reading, writing, or have an error, respectively. + * + * If the `timeout` is reached, it returns `nil`. + * + * r, w = IO.pipe + * IO.select([r], [w]) #=> [[#], [#], []] + */ static mrb_value io_s_select(mrb_state *mrb, mrb_value klass) { @@ -1421,6 +1489,15 @@ mrb_io_fileno(mrb_state *mrb, mrb_value io) return fptr->fd; } +/* + * call-seq: + * ios.fileno -> integer + * + * Returns the integer file descriptor number for the `IO` object. + * + * $stdin.fileno #=> 0 + * $stdout.fileno #=> 1 + */ static mrb_value io_fileno(mrb_state *mrb, mrb_value io) { @@ -1429,6 +1506,16 @@ io_fileno(mrb_state *mrb, mrb_value io) } #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) +/* + * call-seq: + * ios.close_on_exec? -> true or false + * + * Returns `true` if the `FD_CLOEXEC` flag is set for the `IO` object, `false` + * otherwise. + * + * f = IO.new(1, "w") + * f.close_on_exec? #=> true + */ static mrb_value io_close_on_exec_p(mrb_state *mrb, mrb_value io) { @@ -1449,6 +1536,16 @@ io_close_on_exec_p(mrb_state *mrb, mrb_value io) #endif #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) +/* + * call-seq: + * ios.close_on_exec = bool -> bool + * + * Sets the `FD_CLOEXEC` flag on the `IO` object. + * + * f = IO.new(1, "w") + * f.close_on_exec = false + * f.close_on_exec? #=> false + */ static mrb_value io_set_close_on_exec(mrb_state *mrb, mrb_value io) { @@ -1484,6 +1581,18 @@ io_set_close_on_exec(mrb_state *mrb, mrb_value io) # define io_set_close_on_exec mrb_notimplement_m #endif +/* + * call-seq: + * ios.sync = bool -> bool + * + * Sets the sync mode for the `IO` object. + * + * If `true`, all output is immediately flushed to the underlying operating + * system and is not buffered internally. + * + * f = File.new("testfile", "w") + * f.sync = true + */ static mrb_value io_set_sync(mrb_state *mrb, mrb_value io) { @@ -1495,6 +1604,15 @@ io_set_sync(mrb_state *mrb, mrb_value io) return mrb_bool_value(b); } +/* + * call-seq: + * ios.sync -> true or false + * + * Returns the sync mode for the `IO` object. + * + * f = File.new("testfile", "w") + * f.sync #=> false + */ static mrb_value io_sync(mrb_state *mrb, mrb_value io) { @@ -1543,6 +1661,19 @@ io_pwrite(mrb_state *mrb, mrb_value io) } #endif /* MRB_USE_IO_PREAD_PWRITE */ +/* + * call-seq: + * ios.ungetc(string) -> nil + * + * Pushes back characters (passed as a parameter) onto ios, such that a + * subsequent buffered character read will return it. Has no effect with + * unbuffered reads (such as IO#sysread). + * + * f = File.new("testfile") #=> # + * c = f.getc #=> "H" + * f.ungetc(c) #=> nil + * f.getc #=> "H" + */ static mrb_value io_ungetc(mrb_state *mrb, mrb_value io) { @@ -1662,6 +1793,18 @@ io_reset_outbuf(mrb_state *mrb, mrb_value outbuf, mrb_int len) return outbuf; } +/* + * call-seq: + * ios.read(length = nil, outbuf = "") -> string, outbuf, or nil + * + * Reads `length` bytes from the I/O stream. + * + * If `length` is `nil`, it reads until end of file. + * If `outbuf` is given, it will be used as the buffer. + * + * f = File.new("testfile") + * f.read(16) #=> "This is line one" + */ static mrb_value io_read(mrb_state *mrb, mrb_value io) { @@ -1875,6 +2018,17 @@ io_readchar(mrb_state *mrb, mrb_value io) return result; } +/* + * call-seq: + * ios.getbyte -> integer or nil + * + * Reads a byte from the `IO` stream. + * + * Returns the byte as an integer, or `nil` at end of file. + * + * f = File.new("testfile") + * f.getbyte #=> 72 + */ static mrb_value io_getbyte(mrb_state *mrb, mrb_value io) { @@ -1889,6 +2043,17 @@ io_getbyte(mrb_state *mrb, mrb_value io) return mrb_int_value(mrb, (mrb_int)c); } +/* + * call-seq: + * ios.readbyte -> integer + * + * Reads a byte from the `IO` stream. + * + * Returns the byte as an integer. Raises `EOFError` at end of file. + * + * f = File.new("testfile") + * f.readbyte #=> 72 + */ static mrb_value io_readbyte(mrb_state *mrb, mrb_value io) { @@ -1899,6 +2064,16 @@ io_readbyte(mrb_state *mrb, mrb_value io) return result; } +/* + * call-seq: + * ios.flush -> ios + * + * Flushes any buffered data within the `IO` object to the underlying + * operating system. + * + * $stdout.print "no newline" + * $stdout.flush + */ static mrb_value io_flush(mrb_state *mrb, mrb_value io) {