first commit
CI / Build (clang, clang++, ubuntu-22.04, linux, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Build (clang, clang++, windows-2022, windows, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Release (push) Canceled after 0s

This commit is contained in:
2026-09-13 22:18:11 +01:00
commit 08c03ac533
6701 changed files with 1577314 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
mruby-io
========
`IO` and `File` classes for mruby
## Installation
Add the line below to your `build_config.rb`:
```
conf.gem core: 'mruby-io'
```
## Implemented methods
### IO
- http://doc.ruby-lang.org/ja/1.9.3/class/IO.html
| method | mruby-io | memo |
| ------------------------- | -------- | ---- |
| IO.binread | | |
| IO.binwrite | | |
| IO.copy_stream | | |
| IO.new, IO.for_fd, IO.open | o | |
| IO.foreach | | |
| IO.pipe | o | |
| IO.popen | o | |
| IO.read | o | |
| IO.readlines | | |
| IO.select | o | |
| IO.sysopen | o | |
| IO.try_convert | | |
| IO.write | | |
| IO#<< | | |
| IO#advise | | |
| IO#autoclose= | | |
| IO#autoclose? | | |
| IO#binmode | | |
| IO#binmode? | | |
| IO#bytes | | obsolete |
| IO#chars | | obsolete |
| IO#clone, IO#dup | o | |
| IO#close | o | |
| IO#close_on_exec= | o | |
| IO#close_on_exec? | o | |
| IO#close_read | | |
| IO#close_write | | |
| IO#closed? | o | |
| IO#codepoints | | obsolete |
| IO#each_byte | o | |
| IO#each_char | o | |
| IO#each_codepoint | | |
| IO#each_line | o | |
| IO#eof, IO#eof? | o | |
| IO#external_encoding | | |
| IO#fcntl | | |
| IO#fdatasync | | |
| IO#fileno, IO#to_i | o | |
| IO#flush | o | |
| IO#fsync | | |
| IO#getbyte | | |
| IO#getc | o | |
| IO#gets | o | |
| IO#internal_encoding | | |
| IO#ioctl | | |
| IO#isatty, IO#tty? | o | |
| IO#lineno | | |
| IO#lineno= | | |
| IO#lines | | obsolete |
| IO#pid | o | |
| IO#pos, IO#tell | o | |
| IO#pos= | o | |
| IO#print | o | |
| IO#printf | o | |
| IO#putc | | |
| IO#puts | o | |
| IO#read | o | |
| IO#read_nonblock | | |
| IO#readbyte | | |
| IO#readchar | o | |
| IO#readline | o | |
| IO#readlines | o | |
| IO#readpartial | | |
| IO#reopen | | |
| IO#rewind | | |
| IO#seek | o | |
| IO#set_encoding | | |
| IO#stat | | |
| IO#sync | o | |
| IO#sync= | o | |
| IO#sysread | o | |
| IO#sysseek | o | |
| IO#syswrite | o | |
| IO#to_io | | |
| IO#ungetbyte | | |
| IO#ungetc | o | |
| IO#write | o | |
| IO#write_nonblock | | |
### File
- http://doc.ruby-lang.org/ja/1.9.3/class/File.html
| method | mruby-io | memo |
| --------------------------- | -------- | ---- |
| File.absolute_path | | |
| File.atime | | |
| File.basename | o | |
| File.blockdev? | | FileTest |
| File.chardev? | | FileTest |
| File.chmod | o | |
| File.chown | | |
| File.ctime | | |
| File.delete, File.unlink | o | |
| File.directory? | o | FileTest |
| File.dirname | o | |
| File.executable? | | FileTest |
| File.executable_real? | | FileTest |
| File.exist?, exists? | o | FileTest |
| File.expand_path | o | |
| File.extname | o | |
| File.file? | o | FileTest |
| File.fnmatch, File.fnmatch? | | |
| File.ftype | | |
| File.grpowned? | | FileTest |
| File.identical? | | FileTest |
| File.join | o | |
| File.lchmod | | |
| File.lchown | | |
| File.link | | |
| File.lstat | | |
| File.mtime | | |
| File.new, File.open | o | |
| File.owned? | | FileTest |
| File.path | | |
| File.pipe? | o | FileTest |
| File.readable? | | FileTest |
| File.readable_real? | | FileTest |
| File.readlink | o | |
| File.realdirpath | | |
| File.realpath | o | |
| File.rename | o | |
| File.setgid? | | FileTest |
| File.setuid? | | FileTest |
| File.size | o | |
| File.size? | o | FileTest |
| File.socket? | o | FileTest |
| File.split | | |
| File.stat | | |
| File.sticky? | | FileTest |
| File.symlink | | |
| File.symlink? | o | FileTest |
| File.truncate | | |
| File.umask | o | |
| File.utime | | |
| File.world_readable? | | |
| File.world_writable? | | |
| File.writable? | | FileTest |
| File.writable_real? | | FileTest |
| File.zero? | o | FileTest |
| File#atime | | |
| File#chmod | | |
| File#chown | | |
| File#ctime | | |
| File#flock | o | |
| File#lstat | | |
| File#mtime | | |
| File#path, File#to_path | o | |
| File#size | | |
| File#truncate | | |
## License
Copyright (c) 2013 Internet Initiative Japan Inc.
Copyright (c) 2017 mruby developers
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,70 @@
/*
** io.h - IO class
*/
#ifndef MRUBY_IO_H
#define MRUBY_IO_H
#include <mruby.h>
#ifdef MRB_DISABLE_STDIO
# error IO and File conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(MRB_WITHOUT_IO_PREAD_PWRITE)
# undef MRB_WITH_IO_PREAD_PWRITE
#elif !defined(MRB_WITH_IO_PREAD_PWRITE)
# if defined(__unix__) || defined(__MACH__)
# define MRB_WITH_IO_PREAD_PWRITE
# endif
#endif
struct mrb_io {
int fd; /* file descriptor, or -1 */
int fd2; /* file descriptor to write if it's different from fd, or -1 */
int pid; /* child's pid (for pipes) */
unsigned int readable:1,
writable:1,
sync:1,
is_socket:1;
};
#define MRB_O_RDONLY 0x0000
#define MRB_O_WRONLY 0x0001
#define MRB_O_RDWR 0x0002
#define MRB_O_ACCMODE (MRB_O_RDONLY | MRB_O_WRONLY | MRB_O_RDWR)
#define MRB_O_NONBLOCK 0x0004
#define MRB_O_APPEND 0x0008
#define MRB_O_SYNC 0x0010
#define MRB_O_NOFOLLOW 0x0020
#define MRB_O_CREAT 0x0040
#define MRB_O_TRUNC 0x0080
#define MRB_O_EXCL 0x0100
#define MRB_O_NOCTTY 0x0200
#define MRB_O_DIRECT 0x0400
#define MRB_O_BINARY 0x0800
#define MRB_O_SHARE_DELETE 0x1000
#define MRB_O_TMPFILE 0x2000
#define MRB_O_NOATIME 0x4000
#define MRB_O_DSYNC 0x00008000
#define MRB_O_RSYNC 0x00010000
#define MRB_O_RDONLY_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_RDONLY))
#define MRB_O_WRONLY_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_WRONLY))
#define MRB_O_RDWR_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_RDWR))
#define MRB_O_READABLE_P(f) ((mrb_bool)((((f) & MRB_O_ACCMODE) | 2) == 2))
#define MRB_O_WRITABLE_P(f) ((mrb_bool)(((((f) & MRB_O_ACCMODE) + 1) & 2) == 2))
#define E_IO_ERROR (mrb_class_get(mrb, "IOError"))
#define E_EOF_ERROR (mrb_class_get(mrb, "EOFError"))
int mrb_io_fileno(mrb_state *mrb, mrb_value io);
#if defined(__cplusplus)
} /* extern "C" { */
#endif
#endif /* MRUBY_IO_H */
+12
View File
@@ -0,0 +1,12 @@
MRuby::Gem::Specification.new('mruby-io') do |spec|
spec.license = 'MIT'
spec.authors = ['Internet Initiative Japan Inc.', 'mruby developers']
spec.summary = 'IO and File class'
spec.cc.include_paths << "#{build.root}/src"
if for_windows?
spec.linker.libraries << "ws2_32"
end
spec.add_test_dependency 'mruby-time', core: 'mruby-time'
end
@@ -0,0 +1,203 @@
class File < IO
attr_accessor :path
def initialize(fd_or_path, mode = "r", perm = 0666)
if fd_or_path.kind_of? Fixnum
super(fd_or_path, mode)
else
@path = fd_or_path
fd = IO.sysopen(@path, mode, perm)
super(fd, mode)
end
end
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
def self._concat_path(path, base_path)
if path[0] == "/" || path[1] == ':' # Windows root!
expanded_path = path
elsif path[0] == "~"
if (path[1] == "/" || path[1] == nil)
dir = path[1, path.size]
home_dir = _gethome
unless home_dir
raise ArgumentError, "couldn't find HOME environment -- expanding '~'"
end
expanded_path = home_dir
expanded_path += dir if dir
expanded_path += "/"
else
splitted_path = path.split("/")
user = splitted_path[0][1, splitted_path[0].size]
dir = "/" + splitted_path[1, splitted_path.size].join("/")
home_dir = _gethome(user)
unless home_dir
raise ArgumentError, "user #{user} doesn't exist"
end
expanded_path = home_dir
expanded_path += dir if dir
expanded_path += "/"
end
else
expanded_path = _concat_path(base_path, _getwd)
expanded_path += "/" + path
end
expanded_path
end
def self.expand_path(path, default_dir = '.')
expanded_path = _concat_path(path, default_dir)
drive_prefix = ""
if File::ALT_SEPARATOR && expanded_path.size > 2 &&
("A".."Z").include?(expanded_path[0].upcase) && expanded_path[1] == ":"
drive_prefix = expanded_path[0, 2]
expanded_path = expanded_path[2, expanded_path.size]
end
expand_path_array = []
if File::ALT_SEPARATOR && expanded_path.include?(File::ALT_SEPARATOR)
expanded_path.gsub!(File::ALT_SEPARATOR, '/')
end
while expanded_path.include?('//')
expanded_path = expanded_path.gsub('//', '/')
end
if expanded_path != "/"
expanded_path.split('/').each do |path_token|
if path_token == '..'
if expand_path_array.size > 1
expand_path_array.pop
end
elsif path_token == '.'
# nothing to do.
else
expand_path_array << path_token
end
end
expanded_path = expand_path_array.join("/")
if expanded_path.empty?
expanded_path = '/'
end
end
if drive_prefix.empty?
expanded_path
else
drive_prefix + expanded_path.gsub("/", File::ALT_SEPARATOR)
end
end
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
def self.directory?(file)
FileTest.directory?(file)
end
def self.exist?(file)
FileTest.exist?(file)
end
def self.exists?(file)
FileTest.exists?(file)
end
def self.file?(file)
FileTest.file?(file)
end
def self.pipe?(file)
FileTest.pipe?(file)
end
def self.size(file)
FileTest.size(file)
end
def self.size?(file)
FileTest.size?(file)
end
def self.socket?(file)
FileTest.socket?(file)
end
def self.symlink?(file)
FileTest.symlink?(file)
end
def self.zero?(file)
FileTest.zero?(file)
end
def self.extname(filename)
fname = self.basename(filename)
return '' if fname[0] == '.' || fname.index('.').nil?
ext = fname.split('.').last
ext.empty? ? '' : ".#{ext}"
end
def self.path(filename)
if filename.kind_of?(String)
filename
elsif filename.respond_to?(:to_path)
filename.to_path
else
raise TypeError, "no implicit conversion of #{filename.class} into String"
end
end
end
@@ -0,0 +1,13 @@
class File
module Constants
FNM_SYSCASE = 0
FNM_NOESCAPE = 1
FNM_PATHNAME = 2
FNM_DOTMATCH = 4
FNM_CASEFOLD = 8
end
end
class File
include File::Constants
end
+369
View File
@@ -0,0 +1,369 @@
##
# IO
class IOError < StandardError; end
class EOFError < IOError; end
class IO
SEEK_SET = 0
SEEK_CUR = 1
SEEK_END = 2
BUF_SIZE = 4096
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
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
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
def self.read(path, length=nil, offset=nil, opt=nil)
if not opt.nil? # 4 arguments
offset ||= 0
elsif not offset.nil? # 3 arguments
if offset.is_a? Hash
opt = offset
offset = 0
else
opt = {}
end
elsif not length.nil? # 2 arguments
if length.is_a? Hash
opt = length
offset = 0
length = nil
else
offset = 0
opt = {}
end
else # only 1 argument
opt = {}
offset = 0
length = nil
end
str = ""
fd = -1
io = nil
begin
if path[0] == "|"
io = IO.popen(path[1..-1], (opt[:mode] || "r"))
else
mode = opt[:mode] || "r"
fd = IO.sysopen(path, mode)
io = IO.open(fd, mode)
end
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
def flush
# mruby-io always writes immediately (no output buffer).
raise IOError, "closed stream" if self.closed?
self
end
def hash
# We must define IO#hash here because IO includes Enumerable and
# Enumerable#hash will call IO#read...
self.__id__
end
def write(string)
str = string.is_a?(String) ? string : string.to_s
return 0 if str.empty?
unless @buf.empty?
# reset real pos ignore buf
seek(pos, SEEK_SET)
end
len = syswrite(str)
len
end
def <<(str)
write(str)
self
end
def eof?
_check_readable
begin
_read_buf
return @buf.empty?
rescue EOFError
return true
end
end
alias_method :eof, :eof?
def pos
raise IOError if closed?
sysseek(0, SEEK_CUR) - @buf.bytesize
end
alias_method :tell, :pos
def pos=(i)
seek(i, SEEK_SET)
end
def rewind
seek(0, SEEK_SET)
end
def seek(i, whence = SEEK_SET)
raise IOError if closed?
sysseek(i, whence)
@buf = ''
0
end
def _read_buf
return @buf if @buf && @buf.bytesize > 0
sysread(BUF_SIZE, @buf)
end
def ungetc(substr)
raise TypeError.new "expect String, got #{substr.class}" unless substr.is_a?(String)
if @buf.empty?
@buf.replace(substr)
else
@buf[0,0] = substr
end
nil
end
def read(length = nil, outbuf = "")
unless length.nil?
unless length.is_a? Fixnum
raise TypeError.new "can't convert #{length.class} into Integer"
end
if length < 0
raise ArgumentError.new "negative length: #{length} given"
end
if length == 0
return "" # easy case
end
end
array = []
while 1
begin
_read_buf
rescue EOFError
array = nil if array.empty? and (not length.nil?) and length != 0
break
end
if length
consume = (length <= @buf.bytesize) ? length : @buf.bytesize
array.push IO._bufread(@buf, consume)
length -= consume
break if length == 0
else
array.push @buf
@buf = ''
end
end
if array.nil?
outbuf.replace("")
nil
else
outbuf.replace(array.join)
end
end
def readline(arg = "\n", limit = nil)
case arg
when String
rs = arg
when Fixnum
rs = "\n"
limit = arg
else
raise ArgumentError
end
if rs.nil?
return read
end
if rs == ""
rs = "\n\n"
end
array = []
while 1
begin
_read_buf
rescue EOFError
array = nil if array.empty?
break
end
if limit && limit <= @buf.size
array.push @buf[0, limit]
@buf[0, limit] = ""
break
elsif idx = @buf.index(rs)
len = idx + rs.size
array.push @buf[0, len]
@buf[0, len] = ""
break
else
array.push @buf
@buf = ''
end
end
raise EOFError.new "end of file reached" if array.nil?
array.join
end
def gets(*args)
begin
readline(*args)
rescue EOFError
nil
end
end
def readchar
_read_buf
_readchar(@buf)
end
def getc
begin
readchar
rescue EOFError
c = @buf[0]
@buf[0,1]="" if c
nil
end
end
# 15.2.20.5.3
def each(&block)
return to_enum unless block
while line = self.gets
block.call(line)
end
self
end
# 15.2.20.5.4
def each_byte(&block)
return to_enum(:each_byte) unless block
while char = self.getc
block.call(char)
end
self
end
# 15.2.20.5.5
alias each_line each
alias each_char each_byte
def readlines
ary = []
while (line = gets)
ary << line
end
ary
end
def puts(*args)
i = 0
len = args.size
while i < len
s = args[i].to_s
write s
write "\n" if (s[-1] != "\n")
i += 1
end
write "\n" if len == 0
nil
end
def print(*args)
i = 0
len = args.size
while i < len
write args[i].to_s
i += 1
end
end
def printf(*args)
write sprintf(*args)
nil
end
alias_method :to_i, :fileno
alias_method :tty?, :isatty
end
STDIN = IO.open(0, "r")
STDOUT = IO.open(1, "w")
STDERR = IO.open(2, "w")
$stdin = STDIN
$stdout = STDOUT
$stderr = STDERR
@@ -0,0 +1,31 @@
module Kernel
def `(cmd)
IO.popen(cmd) { |io| io.read }
end
def open(file, *rest, &block)
raise ArgumentError unless file.is_a?(String)
if file[0] == "|"
IO.popen(file[1..-1], *rest, &block)
else
File.open(file, *rest, &block)
end
end
def print(*args)
$stdout.print(*args)
end
def puts(*args)
$stdout.puts(*args)
end
def printf(*args)
$stdout.printf(*args)
end
def gets(*args)
$stdin.gets(*args)
end
end
+647
View File
@@ -0,0 +1,647 @@
/*
** file.c - File class
*/
#include "mruby.h"
#include "mruby/class.h"
#include "mruby/data.h"
#include "mruby/string.h"
#include "mruby/ext/io.h"
#include "mruby/error.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <io.h>
#define NULL_FILE "NUL"
#define UNLINK _unlink
#define GETCWD _getcwd
#define CHMOD(a, b) 0
#define MAXPATHLEN 1024
#if !defined(PATH_MAX)
#define PATH_MAX _MAX_PATH
#endif
#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
#include <direct.h>
#else
#define NULL_FILE "/dev/null"
#include <unistd.h>
#define UNLINK unlink
#define GETCWD getcwd
#define CHMOD(a, b) chmod(a,b)
#include <sys/file.h>
#include <libgen.h>
#include <sys/param.h>
#include <pwd.h>
#endif
#define FILE_SEPARATOR "/"
#if defined(_WIN32) || defined(_WIN64)
#define PATH_SEPARATOR ";"
#define FILE_ALT_SEPARATOR "\\"
#define VOLUME_SEPARATOR ":"
#else
#define PATH_SEPARATOR ":"
#endif
#ifndef LOCK_SH
#define LOCK_SH 1
#endif
#ifndef LOCK_EX
#define LOCK_EX 2
#endif
#ifndef LOCK_NB
#define LOCK_NB 4
#endif
#ifndef LOCK_UN
#define LOCK_UN 8
#endif
#ifndef _WIN32
typedef struct stat mrb_stat;
# define mrb_stat(path, sb) stat(path, sb)
# define mrb_fstat(fd, sb) fstat(fd, sb)
#else
typedef struct __stat64 mrb_stat;
# define mrb_stat(path, sb) _stat64(path, sb)
# define mrb_fstat(fd, sb) _fstat64(fd, sb)
#endif
#ifdef _WIN32
static int
flock(int fd, int operation) {
OVERLAPPED ov;
HANDLE h = (HANDLE)_get_osfhandle(fd);
DWORD flags;
flags = ((operation & LOCK_NB) ? LOCKFILE_FAIL_IMMEDIATELY : 0)
| ((operation & LOCK_SH) ? LOCKFILE_EXCLUSIVE_LOCK : 0);
memset(&ov, 0, sizeof(ov));
return LockFileEx(h, flags, 0, 0xffffffff, 0xffffffff, &ov) ? 0 : -1;
}
#endif
static mrb_value
mrb_file_s_umask(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
/* nothing to do on windows */
return mrb_fixnum_value(0);
#else
mrb_int mask, omask;
if (mrb_get_args(mrb, "|i", &mask) == 0) {
omask = umask(0);
umask(omask);
} else {
omask = umask(mask);
}
return mrb_fixnum_value(omask);
#endif
}
static mrb_value
mrb_file_s_unlink(mrb_state *mrb, mrb_value obj)
{
mrb_value *argv;
mrb_value pathv;
mrb_int argc, i;
char *path;
mrb_get_args(mrb, "*", &argv, &argc);
for (i = 0; i < argc; i++) {
const char *utf8_path;
pathv = mrb_ensure_string_type(mrb, argv[i]);
utf8_path = RSTRING_CSTR(mrb, pathv);
path = mrb_locale_from_utf8(utf8_path, -1);
if (UNLINK(path) < 0) {
mrb_locale_free(path);
mrb_sys_fail(mrb, utf8_path);
}
mrb_locale_free(path);
}
return mrb_fixnum_value(argc);
}
static mrb_value
mrb_file_s_rename(mrb_state *mrb, mrb_value obj)
{
mrb_value from, to;
char *src, *dst;
mrb_get_args(mrb, "SS", &from, &to);
src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1);
dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1);
if (rename(src, dst) < 0) {
#if defined(_WIN32) || defined(_WIN64)
if (CHMOD(dst, 0666) == 0 && UNLINK(dst) == 0 && rename(src, dst) == 0) {
mrb_locale_free(src);
mrb_locale_free(dst);
return mrb_fixnum_value(0);
}
#endif
mrb_locale_free(src);
mrb_locale_free(dst);
mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "(%v, %v)", from, to)));
return mrb_fixnum_value(-1); /* not reached */
}
mrb_locale_free(src);
mrb_locale_free(dst);
return mrb_fixnum_value(0);
}
static mrb_value
mrb_file_dirname(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
char dname[_MAX_DIR], vname[_MAX_DRIVE];
char buffer[_MAX_DRIVE + _MAX_DIR];
const char *utf8_path;
char *path;
size_t ridx;
mrb_get_args(mrb, "z", &utf8_path);
path = mrb_locale_from_utf8(utf8_path, -1);
_splitpath(path, vname, dname, NULL, NULL);
snprintf(buffer, _MAX_DRIVE + _MAX_DIR, "%s%s", vname, dname);
mrb_locale_free(path);
ridx = strlen(buffer);
if (ridx == 0) {
strncpy(buffer, ".", 2); /* null terminated */
} else if (ridx > 1) {
ridx--;
while (ridx > 0 && (buffer[ridx] == '/' || buffer[ridx] == '\\')) {
buffer[ridx] = '\0'; /* remove last char */
ridx--;
}
}
return mrb_str_new_cstr(mrb, buffer);
#else
char *dname, *path;
mrb_value s;
mrb_get_args(mrb, "S", &s);
path = mrb_locale_from_utf8(mrb_str_to_cstr(mrb, s), -1);
if ((dname = dirname(path)) == NULL) {
mrb_locale_free(path);
mrb_sys_fail(mrb, "dirname");
}
mrb_locale_free(path);
return mrb_str_new_cstr(mrb, dname);
#endif
}
static mrb_value
mrb_file_basename(mrb_state *mrb, mrb_value klass)
{
// NOTE: Do not use mrb_locale_from_utf8 here
#if defined(_WIN32) || defined(_WIN64)
char bname[_MAX_DIR];
char extname[_MAX_EXT];
char *path;
size_t ridx;
char buffer[_MAX_DIR + _MAX_EXT];
mrb_value s;
mrb_get_args(mrb, "S", &s);
path = mrb_str_to_cstr(mrb, s);
ridx = strlen(path);
if (ridx > 0) {
ridx--;
while (ridx > 0 && (path[ridx] == '/' || path[ridx] == '\\')) {
path[ridx] = '\0';
ridx--;
}
if (strncmp(path, "/", 2) == 0) {
return mrb_str_new_cstr(mrb, path);
}
}
_splitpath((const char*)path, NULL, NULL, bname, extname);
snprintf(buffer, _MAX_DIR + _MAX_EXT, "%s%s", bname, extname);
return mrb_str_new_cstr(mrb, buffer);
#else
char *bname, *path;
mrb_value s;
mrb_get_args(mrb, "S", &s);
path = mrb_str_to_cstr(mrb, s);
if ((bname = basename(path)) == NULL) {
mrb_sys_fail(mrb, "basename");
}
if (strncmp(bname, "//", 3) == 0) bname[1] = '\0'; /* patch for Cygwin */
return mrb_str_new_cstr(mrb, bname);
#endif
}
static mrb_value
mrb_file_realpath(mrb_state *mrb, mrb_value klass)
{
mrb_value pathname, dir_string, s, result;
mrb_int argc;
char *cpath;
argc = mrb_get_args(mrb, "S|S", &pathname, &dir_string);
if (argc == 2) {
s = mrb_str_dup(mrb, dir_string);
s = mrb_str_append(mrb, s, mrb_str_new_cstr(mrb, FILE_SEPARATOR));
s = mrb_str_append(mrb, s, pathname);
pathname = s;
}
cpath = mrb_locale_from_utf8(RSTRING_CSTR(mrb, pathname), -1);
result = mrb_str_buf_new(mrb, PATH_MAX);
if (realpath(cpath, RSTRING_PTR(result)) == NULL) {
mrb_locale_free(cpath);
mrb_sys_fail(mrb, cpath);
return result; /* not reached */
}
mrb_locale_free(cpath);
mrb_str_resize(mrb, result, strlen(RSTRING_PTR(result)));
return result;
}
static mrb_value
mrb_file__getwd(mrb_state *mrb, mrb_value klass)
{
mrb_value path;
char buf[MAXPATHLEN], *utf8;
if (GETCWD(buf, MAXPATHLEN) == NULL) {
mrb_sys_fail(mrb, "getcwd(2)");
}
utf8 = mrb_utf8_from_locale(buf, -1);
path = mrb_str_new_cstr(mrb, utf8);
mrb_utf8_free(utf8);
return path;
}
#ifdef _WIN32
#define IS_FILESEP(x) (x == (*(char*)(FILE_SEPARATOR)) || x == (*(char*)(FILE_ALT_SEPARATOR)))
#define IS_VOLSEP(x) (x == (*(char*)(VOLUME_SEPARATOR)))
#define IS_DEVICEID(x) (x == '.' || x == '?')
#define CHECK_UNCDEV_PATH (IS_FILESEP(path[0]) && IS_FILESEP(path[1]))
static int
is_absolute_traditional_path(const char *path, size_t len)
{
if (len < 3) return 0;
return (ISALPHA(path[0]) && IS_VOLSEP(path[1]) && IS_FILESEP(path[2]));
}
static int
is_aboslute_unc_path(const char *path, size_t len) {
if (len < 2) return 0;
return (CHECK_UNCDEV_PATH && !IS_DEVICEID(path[2]));
}
static int
is_absolute_device_path(const char *path, size_t len) {
if (len < 4) return 0;
return (CHECK_UNCDEV_PATH && IS_DEVICEID(path[2]) && IS_FILESEP(path[3]));
}
static int
mrb_file_is_absolute_path(const char *path)
{
size_t len = strlen(path);
if (IS_FILESEP(path[0])) return 1;
if (len > 0)
return (
is_absolute_traditional_path(path, len) ||
is_aboslute_unc_path(path, len) ||
is_absolute_device_path(path, len)
);
else
return 0;
}
#undef IS_FILESEP
#undef IS_VOLSEP
#undef IS_DEVICEID
#undef CHECK_UNCDEV_PATH
#else
static int
mrb_file_is_absolute_path(const char *path)
{
return (path[0] == *(char*)(FILE_SEPARATOR));
}
#endif
static mrb_value
mrb_file__gethome(mrb_state *mrb, mrb_value klass)
{
mrb_int argc;
char *home;
mrb_value path;
#ifndef _WIN32
mrb_value username;
argc = mrb_get_args(mrb, "|S", &username);
if (argc == 0) {
home = getenv("HOME");
if (home == NULL) {
return mrb_nil_value();
}
if (!mrb_file_is_absolute_path(home)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "non-absolute home");
}
} else {
const char *cuser = RSTRING_CSTR(mrb, username);
struct passwd *pwd = getpwnam(cuser);
if (pwd == NULL) {
return mrb_nil_value();
}
home = pwd->pw_dir;
if (!mrb_file_is_absolute_path(home)) {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "non-absolute home of ~%v", username);
}
}
home = mrb_locale_from_utf8(home, -1);
path = mrb_str_new_cstr(mrb, home);
mrb_locale_free(home);
return path;
#else /* _WIN32 */
argc = mrb_get_argc(mrb);
if (argc == 0) {
home = getenv("USERPROFILE");
if (home == NULL) {
return mrb_nil_value();
}
if (!mrb_file_is_absolute_path(home)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "non-absolute home");
}
} else {
return mrb_nil_value();
}
home = mrb_locale_from_utf8(home, -1);
path = mrb_str_new_cstr(mrb, home);
mrb_locale_free(home);
return path;
#endif
}
static mrb_value
mrb_file_mtime(mrb_state *mrb, mrb_value self)
{
mrb_value obj;
struct stat st;
int fd;
obj = mrb_obj_value(mrb_class_get(mrb, "Time"));
fd = mrb_io_fileno(mrb, self);
if (fstat(fd, &st) == -1)
return mrb_false_value();
return mrb_funcall(mrb, obj, "at", 1, mrb_fixnum_value(st.st_mtime));
}
static mrb_value
mrb_file_flock(mrb_state *mrb, mrb_value self)
{
#if defined(sun)
mrb_raise(mrb, E_NOTIMP_ERROR, "flock is not supported on Illumos/Solaris/Windows");
#else
mrb_int operation;
int fd;
mrb_get_args(mrb, "i", &operation);
fd = mrb_io_fileno(mrb, self);
while (flock(fd, (int)operation) == -1) {
switch (errno) {
case EINTR:
/* retry */
break;
case EAGAIN: /* NetBSD */
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK: /* FreeBSD OpenBSD Linux */
#endif
if (operation & LOCK_NB) {
return mrb_false_value();
}
/* FALLTHRU - should not happen */
default:
mrb_sys_fail(mrb, "flock failed");
break;
}
}
#endif
return mrb_fixnum_value(0);
}
static mrb_value
mrb_file_size(mrb_state *mrb, mrb_value self)
{
mrb_stat st;
int fd;
fd = mrb_io_fileno(mrb, self);
if (mrb_fstat(fd, &st) == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "fstat failed");
}
if (st.st_size > MRB_INT_MAX) {
#ifdef MRB_WITHOUT_FLOAT
mrb_raise(mrb, E_RUNTIME_ERROR, "File#size too large for MRB_WITHOUT_FLOAT");
#else
return mrb_float_value(mrb, (mrb_float)st.st_size);
#endif
}
return mrb_fixnum_value((mrb_int)st.st_size);
}
static int
mrb_ftruncate(int fd, mrb_int length)
{
#ifndef _WIN32
return ftruncate(fd, (off_t)length);
#else
HANDLE file;
__int64 cur;
file = (HANDLE)_get_osfhandle(fd);
if (file == INVALID_HANDLE_VALUE) {
return -1;
}
cur = _lseeki64(fd, 0, SEEK_CUR);
if (cur == -1) return -1;
if (_lseeki64(fd, (__int64)length, SEEK_SET) == -1) return -1;
if (!SetEndOfFile(file)) {
errno = EINVAL; /* TODO: GetLastError to errno */
return -1;
}
if (_lseeki64(fd, cur, SEEK_SET) == -1) return -1;
return 0;
#endif /* _WIN32 */
}
static mrb_value
mrb_file_truncate(mrb_state *mrb, mrb_value self)
{
int fd;
mrb_int length;
mrb_value lenv = mrb_get_arg1(mrb);
fd = mrb_io_fileno(mrb, self);
length = mrb_int(mrb, lenv);
if (mrb_ftruncate(fd, length) != 0) {
mrb_raise(mrb, E_IO_ERROR, "ftruncate failed");
}
return mrb_fixnum_value(0);
}
static mrb_value
mrb_file_s_symlink(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
#else
mrb_value from, to;
const char *src, *dst;
int ai = mrb_gc_arena_save(mrb);
mrb_get_args(mrb, "SS", &from, &to);
src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1);
dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1);
if (symlink(src, dst) == -1) {
mrb_locale_free(src);
mrb_locale_free(dst);
mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "(%v, %v)", from, to)));
}
mrb_locale_free(src);
mrb_locale_free(dst);
mrb_gc_arena_restore(mrb, ai);
#endif
return mrb_fixnum_value(0);
}
static mrb_value
mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) {
mrb_int mode;
mrb_int argc, i;
mrb_value *filenames;
int ai = mrb_gc_arena_save(mrb);
mrb_get_args(mrb, "i*", &mode, &filenames, &argc);
for (i = 0; i < argc; i++) {
const char *utf8_path = RSTRING_CSTR(mrb, filenames[i]);
char *path = mrb_locale_from_utf8(utf8_path, -1);
if (CHMOD(path, mode) == -1) {
mrb_locale_free(path);
mrb_sys_fail(mrb, utf8_path);
}
mrb_locale_free(path);
mrb_gc_arena_restore(mrb, ai);
}
return mrb_fixnum_value(argc);
}
static mrb_value
mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) {
#if defined(_WIN32) || defined(_WIN64)
mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on this platform");
return mrb_nil_value(); // unreachable
#else
char *path, *buf, *tmp;
size_t bufsize = 100;
ssize_t rc;
mrb_value ret;
int ai = mrb_gc_arena_save(mrb);
mrb_get_args(mrb, "z", &path);
tmp = mrb_locale_from_utf8(path, -1);
buf = (char *)mrb_malloc(mrb, bufsize);
while ((rc = readlink(tmp, buf, bufsize)) == (ssize_t)bufsize && rc != -1) {
bufsize *= 2;
buf = (char *)mrb_realloc(mrb, buf, bufsize);
}
mrb_locale_free(tmp);
if (rc == -1) {
mrb_free(mrb, buf);
mrb_sys_fail(mrb, path);
}
tmp = mrb_utf8_from_locale(buf, -1);
ret = mrb_str_new(mrb, tmp, rc);
mrb_locale_free(tmp);
mrb_free(mrb, buf);
mrb_gc_arena_restore(mrb, ai);
return ret;
#endif
}
void
mrb_init_file(mrb_state *mrb)
{
struct RClass *io, *file, *cnst;
io = mrb_class_get(mrb, "IO");
file = mrb_define_class(mrb, "File", io);
MRB_SET_INSTANCE_TT(file, MRB_TT_DATA);
mrb_define_class_method(mrb, file, "umask", mrb_file_s_umask, MRB_ARGS_OPT(1));
mrb_define_class_method(mrb, file, "delete", mrb_file_s_unlink, MRB_ARGS_ANY());
mrb_define_class_method(mrb, file, "unlink", mrb_file_s_unlink, MRB_ARGS_ANY());
mrb_define_class_method(mrb, file, "rename", mrb_file_s_rename, MRB_ARGS_REQ(2));
mrb_define_class_method(mrb, file, "symlink", mrb_file_s_symlink, MRB_ARGS_REQ(2));
mrb_define_class_method(mrb, file, "chmod", mrb_file_s_chmod, MRB_ARGS_REQ(1) | MRB_ARGS_REST());
mrb_define_class_method(mrb, file, "readlink", mrb_file_s_readlink, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, file, "dirname", mrb_file_dirname, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, file, "basename", mrb_file_basename, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, file, "realpath", mrb_file_realpath, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
mrb_define_class_method(mrb, file, "_getwd", mrb_file__getwd, MRB_ARGS_NONE());
mrb_define_class_method(mrb, file, "_gethome", mrb_file__gethome, MRB_ARGS_OPT(1));
mrb_define_method(mrb, file, "flock", mrb_file_flock, MRB_ARGS_REQ(1));
mrb_define_method(mrb, file, "mtime", mrb_file_mtime, MRB_ARGS_NONE());
mrb_define_method(mrb, file, "size", mrb_file_size, MRB_ARGS_NONE());
mrb_define_method(mrb, file, "truncate", mrb_file_truncate, MRB_ARGS_REQ(1));
cnst = mrb_define_module_under(mrb, file, "Constants");
mrb_define_const(mrb, cnst, "LOCK_SH", mrb_fixnum_value(LOCK_SH));
mrb_define_const(mrb, cnst, "LOCK_EX", mrb_fixnum_value(LOCK_EX));
mrb_define_const(mrb, cnst, "LOCK_UN", mrb_fixnum_value(LOCK_UN));
mrb_define_const(mrb, cnst, "LOCK_NB", mrb_fixnum_value(LOCK_NB));
mrb_define_const(mrb, cnst, "SEPARATOR", mrb_str_new_cstr(mrb, FILE_SEPARATOR));
mrb_define_const(mrb, cnst, "PATH_SEPARATOR", mrb_str_new_cstr(mrb, PATH_SEPARATOR));
#if defined(_WIN32) || defined(_WIN64)
mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_str_new_cstr(mrb, FILE_ALT_SEPARATOR));
#else
mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_nil_value());
#endif
mrb_define_const(mrb, cnst, "NULL", mrb_str_new_cstr(mrb, NULL_FILE));
mrb_define_const(mrb, cnst, "RDONLY", mrb_fixnum_value(MRB_O_RDONLY));
mrb_define_const(mrb, cnst, "WRONLY", mrb_fixnum_value(MRB_O_WRONLY));
mrb_define_const(mrb, cnst, "RDWR", mrb_fixnum_value(MRB_O_RDWR));
mrb_define_const(mrb, cnst, "APPEND", mrb_fixnum_value(MRB_O_APPEND));
mrb_define_const(mrb, cnst, "CREAT", mrb_fixnum_value(MRB_O_CREAT));
mrb_define_const(mrb, cnst, "EXCL", mrb_fixnum_value(MRB_O_EXCL));
mrb_define_const(mrb, cnst, "TRUNC", mrb_fixnum_value(MRB_O_TRUNC));
mrb_define_const(mrb, cnst, "NONBLOCK", mrb_fixnum_value(MRB_O_NONBLOCK));
mrb_define_const(mrb, cnst, "NOCTTY", mrb_fixnum_value(MRB_O_NOCTTY));
mrb_define_const(mrb, cnst, "BINARY", mrb_fixnum_value(MRB_O_BINARY));
mrb_define_const(mrb, cnst, "SHARE_DELETE", mrb_fixnum_value(MRB_O_SHARE_DELETE));
mrb_define_const(mrb, cnst, "SYNC", mrb_fixnum_value(MRB_O_SYNC));
mrb_define_const(mrb, cnst, "DSYNC", mrb_fixnum_value(MRB_O_DSYNC));
mrb_define_const(mrb, cnst, "RSYNC", mrb_fixnum_value(MRB_O_RSYNC));
mrb_define_const(mrb, cnst, "NOFOLLOW", mrb_fixnum_value(MRB_O_NOFOLLOW));
mrb_define_const(mrb, cnst, "NOATIME", mrb_fixnum_value(MRB_O_NOATIME));
mrb_define_const(mrb, cnst, "DIRECT", mrb_fixnum_value(MRB_O_DIRECT));
mrb_define_const(mrb, cnst, "TMPFILE", mrb_fixnum_value(MRB_O_TMPFILE));
}
@@ -0,0 +1,347 @@
/*
** file_test.c - FileTest class
*/
#include "mruby.h"
#include "mruby/class.h"
#include "mruby/data.h"
#include "mruby/string.h"
#include "mruby/ext/io.h"
#include "mruby/error.h"
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(_WIN64)
#define LSTAT stat
#include <winsock.h>
#else
#define LSTAT lstat
#include <sys/file.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <libgen.h>
#include <pwd.h>
#include <unistd.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
extern struct mrb_data_type mrb_io_type;
static int
mrb_stat0(mrb_state *mrb, mrb_value obj, struct stat *st, int do_lstat)
{
if (mrb_obj_is_kind_of(mrb, obj, mrb_class_get(mrb, "IO"))) {
struct mrb_io *fptr;
fptr = (struct mrb_io *)mrb_data_get_ptr(mrb, obj, &mrb_io_type);
if (fptr && fptr->fd >= 0) {
return fstat(fptr->fd, st);
}
mrb_raise(mrb, E_IO_ERROR, "closed stream");
return -1;
}
else {
char *path = mrb_locale_from_utf8(RSTRING_CSTR(mrb, obj), -1);
int ret;
if (do_lstat) {
ret = LSTAT(path, st);
} else {
ret = stat(path, st);
}
mrb_locale_free(path);
return ret;
}
}
static int
mrb_stat(mrb_state *mrb, mrb_value obj, struct stat *st)
{
return mrb_stat0(mrb, obj, st, 0);
}
static int
mrb_lstat(mrb_state *mrb, mrb_value obj, struct stat *st)
{
return mrb_stat0(mrb, obj, st, 1);
}
/*
* Document-method: directory?
*
* call-seq:
* File.directory?(file_name) -> true or false
*
* Returns <code>true</code> if the named file is a directory,
* or a symlink that points at a directory, and <code>false</code>
* otherwise.
*
* File.directory?(".")
*/
static mrb_value
mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass)
{
#ifndef S_ISDIR
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
if (S_ISDIR(st.st_mode))
return mrb_true_value();
return mrb_false_value();
}
/*
* call-seq:
* File.pipe?(file_name) -> true or false
*
* Returns <code>true</code> if the named file is a pipe.
*/
static mrb_value
mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
mrb_raise(mrb, E_NOTIMP_ERROR, "pipe is not supported on this platform");
#else
#ifdef S_IFIFO
# ifndef S_ISFIFO
# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
# endif
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
if (S_ISFIFO(st.st_mode))
return mrb_true_value();
#endif
return mrb_false_value();
#endif
}
/*
* call-seq:
* File.symlink?(file_name) -> true or false
*
* Returns <code>true</code> if the named file is a symbolic link.
*/
static mrb_value
mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
#else
#ifndef S_ISLNK
# ifdef _S_ISLNK
# define S_ISLNK(m) _S_ISLNK(m)
# else
# ifdef _S_IFLNK
# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK)
# else
# ifdef S_IFLNK
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
# endif
# endif
# endif
#endif
#ifdef S_ISLNK
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_lstat(mrb, obj, &st) == -1)
return mrb_false_value();
if (S_ISLNK(st.st_mode))
return mrb_true_value();
#endif
return mrb_false_value();
#endif
}
/*
* call-seq:
* File.socket?(file_name) -> true or false
*
* Returns <code>true</code> if the named file is a socket.
*/
static mrb_value
mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
mrb_raise(mrb, E_NOTIMP_ERROR, "socket is not supported on this platform");
#else
#ifndef S_ISSOCK
# ifdef _S_ISSOCK
# define S_ISSOCK(m) _S_ISSOCK(m)
# else
# ifdef _S_IFSOCK
# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK)
# else
# ifdef S_IFSOCK
# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
# endif
# endif
# endif
#endif
#ifdef S_ISSOCK
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
if (S_ISSOCK(st.st_mode))
return mrb_true_value();
#endif
return mrb_false_value();
#endif
}
/*
* call-seq:
* File.exist?(file_name) -> true or false
* File.exists?(file_name) -> true or false
*
* Return <code>true</code> if the named file exists.
*/
static mrb_value
mrb_filetest_s_exist_p(mrb_state *mrb, mrb_value klass)
{
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
return mrb_true_value();
}
/*
* call-seq:
* File.file?(file_name) -> true or false
*
* Returns <code>true</code> if the named file exists and is a
* regular file.
*/
static mrb_value
mrb_filetest_s_file_p(mrb_state *mrb, mrb_value klass)
{
#ifndef S_ISREG
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
if (S_ISREG(st.st_mode))
return mrb_true_value();
return mrb_false_value();
}
/*
* call-seq:
* File.zero?(file_name) -> true or false
*
* Returns <code>true</code> if the named file exists and has
* a zero size.
*/
static mrb_value
mrb_filetest_s_zero_p(mrb_state *mrb, mrb_value klass)
{
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_false_value();
if (st.st_size == 0)
return mrb_true_value();
return mrb_false_value();
}
/*
* call-seq:
* File.size(file_name) -> integer
*
* Returns the size of <code>file_name</code>.
*
* _file_name_ can be an IO object.
*/
static mrb_value
mrb_filetest_s_size(mrb_state *mrb, mrb_value klass)
{
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
mrb_sys_fail(mrb, "mrb_stat");
return mrb_fixnum_value(st.st_size);
}
/*
* 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.
*/
static mrb_value
mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass)
{
struct stat st;
mrb_value obj = mrb_get_arg1(mrb);
if (mrb_stat(mrb, obj, &st) < 0)
return mrb_nil_value();
if (st.st_size == 0)
return mrb_nil_value();
return mrb_fixnum_value(st.st_size);
}
void
mrb_init_file_test(mrb_state *mrb)
{
struct RClass *f;
f = mrb_define_class(mrb, "FileTest", mrb->object_class);
mrb_define_class_method(mrb, f, "directory?", mrb_filetest_s_directory_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "exist?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "exists?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "file?", mrb_filetest_s_file_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "pipe?", mrb_filetest_s_pipe_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "size", mrb_filetest_s_size, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "size?", mrb_filetest_s_size_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "socket?", mrb_filetest_s_socket_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "symlink?", mrb_filetest_s_symlink_p, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, f, "zero?", mrb_filetest_s_zero_p, MRB_ARGS_REQ(1));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
#include "mruby.h"
void mrb_init_io(mrb_state *mrb);
void mrb_init_file(mrb_state *mrb);
void mrb_init_file_test(mrb_state *mrb);
#define DONE mrb_gc_arena_restore(mrb, 0)
void
mrb_mruby_io_gem_init(mrb_state* mrb)
{
mrb_init_io(mrb); DONE;
mrb_init_file(mrb); DONE;
mrb_init_file_test(mrb); DONE;
}
void
mrb_mruby_io_gem_final(mrb_state* mrb)
{
}
+235
View File
@@ -0,0 +1,235 @@
##
# File Test
MRubyIOTestUtil.io_test_setup
assert('File.class', '15.2.21') do
assert_equal Class, File.class
end
assert('File.superclass', '15.2.21.2') do
assert_equal IO, File.superclass
end
assert('File#initialize', '15.2.21.4.1') do
io = File.open($mrbtest_io_rfname, "r")
assert_nil io.close
assert_raise IOError do
io.close
end
end
assert('File#path', '15.2.21.4.2') do
io = File.open($mrbtest_io_rfname, "r")
assert_equal $mrbtest_io_msg, io.read
assert_equal $mrbtest_io_rfname, io.path
io.close
assert_equal $mrbtest_io_rfname, io.path
assert_true io.closed?
end
assert('File.basename') do
assert_equal '/', File.basename('//')
assert_equal 'a', File.basename('/a/')
assert_equal 'b', File.basename('/a/b')
assert_equal 'b', File.basename('../a/b')
assert_raise(ArgumentError) { File.basename("/a/b\0") }
end
assert('File.dirname') do
assert_equal '.', File.dirname('')
assert_equal '.', File.dirname('a')
assert_equal '/', File.dirname('/a')
assert_equal 'a', File.dirname('a/b')
assert_equal '/a', File.dirname('/a/b')
end
assert('File.extname') do
assert_equal '.txt', File.extname('foo/foo.txt')
assert_equal '.gz', File.extname('foo/foo.tar.gz')
assert_equal '', File.extname('foo/bar')
assert_equal '', File.extname('foo/.bar')
assert_equal '', File.extname('foo.txt/bar')
assert_equal '', File.extname('.foo')
end
assert('File#flock') do
f = File.open $mrbtest_io_rfname
begin
assert_equal(f.flock(File::LOCK_SH), 0)
assert_equal(f.flock(File::LOCK_UN), 0)
assert_equal(f.flock(File::LOCK_EX | File::LOCK_NB), 0)
assert_equal(f.flock(File::LOCK_UN), 0)
rescue NotImplementedError => e
skip e.message
ensure
f.close
end
end
assert('File#mtime') do
begin
File.open("#{$mrbtest_io_wfname}.mtime", 'w') do |f|
assert_equal Time, f.mtime.class
File.open("#{$mrbtest_io_wfname}.mtime", 'r') do |f2|
assert_equal true, f.mtime == f2.mtime
end
end
ensure
File.delete("#{$mrbtest_io_wfname}.mtime")
end
end
assert('File#size and File#truncate') do
fname = "#{$mrbtest_io_wfname}.resize"
begin
File.open(fname, 'w') do |f|
assert_equal 0, f.size
assert_equal 0, f.truncate(100)
assert_equal 100, f.size
assert_equal 0, f.pos
assert_equal 0, f.truncate(5)
assert_equal 5, f.size
end
ensure
File.delete(fname)
end
end
assert('File.join') do
assert_equal "", File.join()
assert_equal "a", File.join("a")
assert_equal "/a", File.join("/a")
assert_equal "a/", File.join("a/")
assert_equal "a/b/c", File.join("a", "b", "c")
assert_equal "/a/b/c", File.join("/a", "b", "c")
assert_equal "a/b/c/", File.join("a", "b", "c/")
assert_equal "a/b/c", File.join("a/", "/b/", "/c")
assert_equal "a/b/c", File.join(["a", "b", "c"])
assert_equal "a/b/c", File.join("a", ["b", ["c"]])
end
assert('File.realpath') do
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
begin
sep = File::ALT_SEPARATOR || File::SEPARATOR
relative_path = "#{File.basename(dir)}#{sep}realpath_test"
path = "#{File._getwd}#{sep}#{relative_path}"
File.open(path, "w"){}
assert_equal path, File.realpath(relative_path)
unless MRubyIOTestUtil.win?
path1 = File.realpath($mrbtest_io_rfname)
path2 = File.realpath($mrbtest_io_symlinkname)
assert_equal path1, path2
end
ensure
File.delete path rescue nil
MRubyIOTestUtil.rmdir dir
end
assert_raise(ArgumentError) { File.realpath("TO\0DO") }
end
assert("File.readlink") do
begin
exp = File.basename($mrbtest_io_rfname)
act = File.readlink($mrbtest_io_symlinkname)
assert_equal exp, act
rescue NotImplementedError => e
skip e.message
end
end
assert("File.readlink fails with non-symlink") do
skip "readlink is not supported on this platform" if MRubyIOTestUtil.win?
begin
e2 = nil
assert_raise(RuntimeError) {
begin
File.readlink($mrbtest_io_rfname)
rescue => e
if Object.const_defined?(:SystemCallError) and e.kind_of?(SystemCallError)
raise RuntimeError, "SystemCallError converted to RuntimeError"
end
raise e
rescue NotImplementedError => e
e2 = e
end
}
raise e2 if e2
rescue NotImplementedError => e
skip e.message
end
end
assert('File.expand_path') do
assert_equal "/", File.expand_path("..", "/tmp"), "parent path with base_dir (1)"
assert_equal "/tmp", File.expand_path("..", "/tmp/mruby"), "parent path with base_dir (2)"
assert_equal "/home", File.expand_path("/home"), "absolute"
assert_equal "/home", File.expand_path("/home", "."), "absolute with base_dir"
assert_equal "/hoge", File.expand_path("/tmp/..//hoge")
assert_equal "/hoge", File.expand_path("////tmp/..///////hoge")
assert_equal "/", File.expand_path("../../../..", "/")
if File._getwd[1] == ":"
drive_letter = File._getwd[0]
assert_equal drive_letter + ":\\", File.expand_path(([".."] * 100).join("/"))
else
assert_equal "/", File.expand_path(([".."] * 100).join("/"))
end
end
assert('File.expand_path (with ENV)') do
skip unless Object.const_defined?(:ENV) && ENV['HOME']
assert_equal ENV['HOME'], File.expand_path("~/"), "home"
assert_equal ENV['HOME'], File.expand_path("~/", "/"), "home with base_dir"
assert_equal "#{ENV['HOME']}/user", File.expand_path("user", ENV['HOME']), "relative with base_dir"
end
assert('File.path') do
assert_equal "", File.path("")
assert_equal "a/b/c", File.path("a/b/c")
assert_equal "a/../b/./c", File.path("a/../b/./c")
assert_raise(TypeError) { File.path(nil) }
assert_raise(TypeError) { File.path(123) }
end
assert('File.symlink') do
target_name = "/usr/bin"
if !File.exist?(target_name)
skip("target directory of File.symlink is not found")
end
begin
tmpdir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
rescue => e
skip e.message
end
symlink_name = "#{tmpdir}/test-bin-dummy"
begin
assert_equal 0, File.symlink(target_name, symlink_name)
assert_equal true, File.symlink?(symlink_name)
rescue NotImplementedError => e
skip e.message
ensure
File.delete symlink_name rescue nil
MRubyIOTestUtil.rmdir tmpdir rescue nil
end
end
assert('File.chmod') do
File.open("#{$mrbtest_io_wfname}.chmod-test", 'w') {}
begin
assert_equal 1, File.chmod(0400, "#{$mrbtest_io_wfname}.chmod-test")
ensure
File.delete("#{$mrbtest_io_wfname}.chmod-test")
end
end
MRubyIOTestUtil.io_test_cleanup
@@ -0,0 +1,112 @@
##
# FileTest
MRubyIOTestUtil.io_test_setup
assert("FileTest.directory?") do
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
begin
assert_true FileTest.directory?(dir)
assert_false FileTest.directory?($mrbtest_io_rfname)
ensure
MRubyIOTestUtil.rmdir dir
end
end
assert("FileTest.exist?") do
assert_equal true, FileTest.exist?($mrbtest_io_rfname), "filename - exist"
assert_equal false, FileTest.exist?($mrbtest_io_rfname + "-"), "filename - not exist"
io = IO.new(IO.sysopen($mrbtest_io_rfname))
assert_equal true, FileTest.exist?(io), "io obj - exist"
io.close
assert_equal true, io.closed?
assert_raise(IOError) { FileTest.exist?(io) }
assert_raise(TypeError) { File.exist?($mrbtest_io_rfname.to_sym) }
end
assert("FileTest.file?") do
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
begin
assert_true FileTest.file?($mrbtest_io_rfname)
assert_false FileTest.file?(dir)
ensure
MRubyIOTestUtil.rmdir dir
end
end
assert("FileTest.pipe?") do
begin
assert_equal false, FileTest.pipe?("/tmp")
io = IO.popen("ls")
assert_equal true, FileTest.pipe?(io)
rescue NotImplementedError => e
skip e.message
end
end
assert('FileTest.size') do
assert_equal FileTest.size($mrbtest_io_rfname), $mrbtest_io_msg.size
assert_equal FileTest.size($mrbtest_io_wfname), 0
end
assert("FileTest.size?") do
assert_equal $mrbtest_io_msg.size, FileTest.size?($mrbtest_io_rfname)
assert_equal nil, FileTest.size?($mrbtest_io_wfname)
assert_equal nil, FileTest.size?("not-exist-test-target-file")
fp1 = File.open($mrbtest_io_rfname)
fp2 = File.open($mrbtest_io_wfname)
assert_equal $mrbtest_io_msg.size, FileTest.size?(fp1)
assert_equal nil, FileTest.size?(fp2)
fp1.close
fp2.close
assert_raise IOError do
FileTest.size?(fp1)
end
assert_true fp1.closed?
assert_raise IOError do
FileTest.size?(fp2)
end
assert_true fp2.closed?
end
assert("FileTest.socket?") do
begin
assert_true FileTest.socket?($mrbtest_io_socketname)
rescue NotImplementedError => e
skip e.message
end
end
assert("FileTest.symlink?") do
begin
assert_true FileTest.symlink?($mrbtest_io_symlinkname)
rescue NotImplementedError => e
skip e.message
end
end
assert("FileTest.zero?") do
assert_equal false, FileTest.zero?($mrbtest_io_rfname)
assert_equal true, FileTest.zero?($mrbtest_io_wfname)
assert_equal false, FileTest.zero?("not-exist-test-target-file")
fp1 = File.open($mrbtest_io_rfname)
fp2 = File.open($mrbtest_io_wfname)
assert_equal false, FileTest.zero?(fp1)
assert_equal true, FileTest.zero?(fp2)
fp1.close
fp2.close
assert_raise IOError do
FileTest.zero?(fp1)
end
assert_true fp1.closed?
assert_raise IOError do
FileTest.zero?(fp2)
end
assert_true fp2.closed?
end
MRubyIOTestUtil.io_test_cleanup
+647
View File
@@ -0,0 +1,647 @@
##
# IO Test
MRubyIOTestUtil.io_test_setup
$cr, $crlf, $cmd = MRubyIOTestUtil.win? ? [1, "\r\n", "cmd /c "] : [0, "\n", ""]
def assert_io_open(meth)
assert "assert_io_open" do
fd = IO.sysopen($mrbtest_io_rfname)
assert_equal Fixnum, fd.class
io1 = IO.__send__(meth, fd)
begin
assert_equal IO, io1.class
assert_equal $mrbtest_io_msg, io1.read
ensure
io1.close
end
io2 = IO.__send__(meth, IO.sysopen($mrbtest_io_rfname))do |io|
if meth == :open
assert_equal $mrbtest_io_msg, io.read
else
flunk "IO.#{meth} does not take block"
end
end
io2.close unless meth == :open
assert_raise(RuntimeError) { IO.__send__(meth, 1023) } # For Windows
assert_raise(RuntimeError) { IO.__send__(meth, 1 << 26) }
assert_raise(RuntimeError) { IO.__send__(meth, 1 << 32) } if (1 << 32).kind_of?(Integer)
end
end
assert('IO.class', '15.2.20') do
assert_equal(Class, IO.class)
end
assert('IO.superclass', '15.2.20.2') do
assert_equal(Object, IO.superclass)
end
assert('IO.ancestors', '15.2.20.3') do
assert_include(IO.ancestors, Enumerable)
end
assert('IO.open', '15.2.20.4.1') do
assert_io_open(:open)
end
assert('IO#close', '15.2.20.5.1') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
assert_nil io.close
end
assert('IO#closed?', '15.2.20.5.2') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
assert_false io.closed?
io.close
assert_true io.closed?
end
#assert('IO#each', '15.2.20.5.3') do
#assert('IO#each_byte', '15.2.20.5.4') do
#assert('IO#each_line', '15.2.20.5.5') do
assert('IO#eof?', '15.2.20.5.6') do
io = IO.new(IO.sysopen($mrbtest_io_wfname, 'w'), 'w')
assert_raise(IOError) do
io.eof?
end
io.close
# empty file
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'w'), 'w')
io.close
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'r'), 'r')
assert_true io.eof?
io.close
# nonempty file
io = IO.new(IO.sysopen($mrbtest_io_rfname))
assert_false io.eof?
io.readchar
assert_false io.eof?
io.read
assert_true io.eof?
io.close
end
assert('IO#flush', '15.2.20.5.7') do
# Note: mruby-io does not have any buffer to be flushed now.
io = IO.new(IO.sysopen($mrbtest_io_wfname))
assert_equal io, io.flush
io.close
assert_raise(IOError) do
io.flush
end
end
assert('IO#getc', '15.2.20.5.8') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
$mrbtest_io_msg.split("").each { |ch|
assert_equal ch, io.getc
}
assert_equal nil, io.getc
io.close
end
#assert('IO#gets', '15.2.20.5.9') do
#assert('IO#initialize_copy', '15.2.20.5.10') do
#assert('IO#print', '15.2.20.5.11') do
#assert('IO#putc', '15.2.20.5.12') do
#assert('IO#puts', '15.2.20.5.13') do
assert('IO#read', '15.2.20.5.14') do
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
assert_raise(ArgumentError) { io.read(-5) }
assert_raise(TypeError) { io.read("str") }
len = $mrbtest_io_msg.length
assert_equal '', io.read(0)
assert_equal 'mruby', io.read(5)
assert_equal $mrbtest_io_msg[5,len], io.read(len)
assert_equal "", io.read
assert_nil io.read(1)
end
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
assert_equal $mrbtest_io_msg, io.read
end
end
assert "IO#read(n) with n > IO::BUF_SIZE" do
skip "pipe is not supported on this platform" if MRubyIOTestUtil.win?
IO.pipe do |r,w|
n = IO::BUF_SIZE+1
w.write 'a'*n
assert_equal 'a'*n, r.read(n)
end
end
assert('IO#readchar', '15.2.20.5.15') do
# almost same as IO#getc
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
$mrbtest_io_msg.split("").each { |ch|
assert_equal ch, io.readchar
}
assert_raise(EOFError) do
io.readchar
end
end
end
#assert('IO#readline', '15.2.20.5.16') do
#assert('IO#readlines', '15.2.20.5.17') do
assert('IO#sync', '15.2.20.5.18') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
s = io.sync
assert_true(s == true || s == false)
io.close
assert_raise(IOError) do
io.sync
end
end
assert('IO#sync=', '15.2.20.5.19') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
io.sync = true
assert_true io.sync
io.sync = false
assert_false io.sync
io.close
assert_raise(IOError) do
io.sync = true
end
end
assert('IO#write', '15.2.20.5.20') do
io = IO.open(IO.sysopen($mrbtest_io_wfname))
assert_equal 0, io.write("")
io.close
io = IO.open(IO.sysopen($mrbtest_io_wfname, "r+"), "r+")
assert_equal 7, io.write("abcdefg")
io.rewind
assert_equal "ab", io.read(2)
assert_equal 3, io.write("123")
io.rewind
assert_equal "ab123fg", io.read
io.close
end
assert('IO#<<') do
io = IO.open(IO.sysopen($mrbtest_io_wfname))
io << "" << ""
assert_equal 0, io.pos
io.close
end
assert('IO#dup for readable') do
io = IO.new(IO.sysopen($mrbtest_io_rfname))
dup = io.dup
assert_true io != dup
assert_true io.fileno != dup.fileno
begin
assert_true dup.close_on_exec?
rescue NotImplementedError
end
assert_equal 'm', dup.sysread(1)
assert_equal 'r', io.sysread(1)
assert_equal 'u', dup.sysread(1)
assert_equal 'b', io.sysread(1)
assert_equal 'y', dup.sysread(1)
dup.close
assert_false io.closed?
io.close
end
assert('IO#dup for writable') do
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'w+'), 'w+')
dup = io.dup
io.syswrite "mruby"
assert_equal 5, dup.sysseek(0, IO::SEEK_CUR)
io.sysseek 0, IO::SEEK_SET
assert_equal 0, dup.sysseek(0, IO::SEEK_CUR)
assert_equal "mruby", dup.sysread(5)
dup.close
io.close
end
assert('IO.for_fd') do
assert_io_open(:for_fd)
end
assert('IO.new') do
assert_io_open(:new)
end
assert('IO gc check') do
assert_nothing_raised { 100.times { IO.new(0) } }
end
assert('IO.sysopen("./nonexistent")') do
if Object.const_defined? :Errno
eclass = Errno::ENOENT
else
eclass = RuntimeError
end
assert_raise eclass do
fd = IO.sysopen "./nonexistent"
IO._sysclose fd
end
end
assert('IO.sysopen, IO#sysread') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
str1 = " "
str2 = io.sysread(5, str1)
assert_equal $mrbtest_io_msg[0,5], str1
assert_equal $mrbtest_io_msg[0,5], str2
assert_raise EOFError do
io.sysread(10000)
io.sysread(10000)
end
assert_raise RuntimeError do
io.sysread(5, "abcde".freeze)
end
io.close
assert_equal "", io.sysread(0)
assert_raise(IOError) { io.sysread(1) }
assert_raise(ArgumentError) { io.sysread(-1) }
io.closed?
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
assert_raise(IOError) { io.sysread(1) }
io.close
end
assert('IO.sysopen, IO#syswrite') do
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
str = "abcdefg"
len = io.syswrite(str)
assert_equal str.size, len
io.close
io = IO.new(IO.sysopen($mrbtest_io_rfname), "r")
assert_raise(IOError) { io.syswrite("a") }
io.close
end
assert('IO#_read_buf') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
def io._buf
@buf
end
msg_len = $mrbtest_io_msg.size
assert_equal '', io._buf
assert_equal $mrbtest_io_msg, io._read_buf
assert_equal $mrbtest_io_msg, io._buf
assert_equal 'mruby', io.read(5)
assert_equal 5, io.pos
assert_equal msg_len - 5, io._buf.size
assert_equal $mrbtest_io_msg[5,100], io.read
assert_equal 0, io._buf.size
assert_raise EOFError do
io._read_buf
end
assert_equal true, io.eof
assert_equal true, io.eof?
io.close
end
assert('IO#isatty') do
skip "isatty is not supported on this platform" if MRubyIOTestUtil.win?
begin
f = File.open("/dev/tty")
rescue RuntimeError => e
skip e.message
else
assert_true f.isatty
ensure
f&.close
end
begin
f = File.open($mrbtest_io_rfname)
assert_false f.isatty
ensure
f&.close
end
end
assert('IO#pos=, IO#seek') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
def io._buf
@buf
end
assert_equal 'm', io.getc
assert_equal 1, io.pos
assert_equal 0, io.seek(0)
assert_equal 0, io.pos
io.close
end
assert('IO#rewind') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
assert_equal 'm', io.getc
assert_equal 1, io.pos
assert_equal 0, io.rewind
assert_equal 0, io.pos
io.close
end
assert('IO#gets') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
# gets without arguments
assert_equal $mrbtest_io_msg, io.gets, "gets without arguments"
assert_equal nil, io.gets, "gets returns nil, when EOF"
# gets with limit
io.pos = 0
assert_equal $mrbtest_io_msg[0, 5], io.gets(5), "gets with limit"
# gets with rs
io.pos = 0
assert_equal $mrbtest_io_msg[0, 6], io.gets(' '), "gets with rs"
# gets with rs, limit
io.pos = 0
assert_equal $mrbtest_io_msg[0, 5], io.gets(' ', 5), "gets with rs, limit"
io.close
assert_equal true, io.closed?, "close success"
# reading many-lines file.
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
io.write "0123456789" * 2 + "\na"
assert_equal 22 + $cr, io.pos
io.close
assert_equal true, io.closed?
fd = IO.sysopen $mrbtest_io_wfname
io = IO.new fd
line = io.gets
# gets first line
assert_equal "0123456789" * 2 + "\n", line, "gets first line"
assert_equal 21, line.size
assert_equal 21 + $cr, io.pos
# gets second line
assert_equal "a", io.gets, "gets second line"
# gets third line
assert_equal nil, io.gets, "gets third line; returns nil"
io.close
end
assert('IO#gets - paragraph mode') do
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
io.write "0" * 10 + "\n"
io.write "1" * 10 + "\n\n"
io.write "2" * 10 + "\n"
assert_equal 34 + $cr * 4, io.pos
io.close
fd = IO.sysopen $mrbtest_io_wfname
io = IO.new fd
para1 = "#{'0' * 10}\n#{'1' * 10}\n\n"
text1 = io.gets("")
assert_equal para1, text1
para2 = "#{'2' * 10}\n"
text2 = io.gets("")
assert_equal para2, text2
io.close
end
assert('IO.popen') do
begin
$? = nil
io = IO.popen("#{$cmd}echo mruby-io")
assert_true io.close_on_exec?
assert_equal Fixnum, io.pid.class
out = io.read
assert_equal out.class, String
assert_include out, 'mruby-io'
io.close
if Object.const_defined? :Process
assert_true $?.success?
else
assert_equal 0, $?
end
assert_true io.closed?
rescue NotImplementedError => e
skip e.message
end
end
assert('IO.popen with in option') do
begin
IO.pipe do |r, w|
w.write 'hello'
w.close
assert_equal "hello", IO.popen("cat", "r", in: r) { |i| i.read }
assert_equal "", r.read
end
assert_raise(ArgumentError) { IO.popen("hello", "r", in: Object.new) }
rescue NotImplementedError => e
skip e.message
end
end
assert('IO.popen with out option') do
begin
IO.pipe do |r, w|
IO.popen("echo 'hello'", "w", out: w) {}
w.close
assert_equal "hello\n", r.read
end
rescue NotImplementedError => e
skip e.message
end
end
assert('IO.popen with err option') do
begin
IO.pipe do |r, w|
assert_equal "", IO.popen("echo 'hello' 1>&2", "r", err: w) { |i| i.read }
w.close
assert_equal "hello\n", r.read
end
rescue NotImplementedError => e
skip e.message
end
end
assert('IO.read') do
# empty file
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
io.close
assert_equal "", IO.read($mrbtest_io_wfname)
assert_equal nil, IO.read($mrbtest_io_wfname, 1)
# one byte file
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
io.write "123"
io.close
assert_equal "123", IO.read($mrbtest_io_wfname)
assert_equal "", IO.read($mrbtest_io_wfname, 0)
assert_equal "1", IO.read($mrbtest_io_wfname, 1)
assert_equal "", IO.read($mrbtest_io_wfname, 0, 10)
assert_equal "23", IO.read($mrbtest_io_wfname, 2, 1)
assert_equal "23", IO.read($mrbtest_io_wfname, 10, 1)
assert_equal "", IO.read($mrbtest_io_wfname, nil, 10)
assert_equal nil, IO.read($mrbtest_io_wfname, 1, 10)
end
assert('IO#fileno') do
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new fd
assert_equal io.fileno, fd
assert_equal io.to_i, fd
io.close
end
assert('IO#close_on_exec') do
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new fd, "w"
begin
# IO.sysopen opens a file descripter with O_CLOEXEC flag.
assert_true io.close_on_exec?
rescue ScriptError
io.close
skip "IO\#close_on_exec is not implemented."
end
io.close_on_exec = false
assert_equal(false, io.close_on_exec?)
io.close_on_exec = true
assert_equal(true, io.close_on_exec?)
io.close_on_exec = false
assert_equal(false, io.close_on_exec?)
io.close
begin
r, w = IO.pipe
assert_equal(true, r.close_on_exec?)
r.close_on_exec = false
assert_equal(false, r.close_on_exec?)
r.close_on_exec = true
assert_equal(true, r.close_on_exec?)
assert_equal(true, w.close_on_exec?)
w.close_on_exec = false
assert_equal(false, w.close_on_exec?)
w.close_on_exec = true
assert_equal(true, w.close_on_exec?)
ensure
r.close unless r.closed?
w.close unless w.closed?
end
end
assert('IO#sysseek') do
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
assert_equal 2, io.sysseek(2)
assert_equal 5, io.sysseek(3, IO::SEEK_CUR) # 2 + 3 => 5
assert_equal $mrbtest_io_msg.size - 4, io.sysseek(-4, IO::SEEK_END)
end
end
assert('IO#pread') do
skip "IO#pread is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE
IO.open(IO.sysopen($mrbtest_io_rfname, 'r'), 'r') do |io|
assert_equal $mrbtest_io_msg.byteslice(5, 8), io.pread(8, 5)
assert_equal 0, io.pos
assert_equal $mrbtest_io_msg.byteslice(1, 5), io.pread(5, 1)
assert_equal 0, io.pos
assert_raise(RuntimeError) { io.pread(20, -9) }
end
end
assert('IO#pwrite') do
skip "IO#pwrite is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE
IO.open(IO.sysopen($mrbtest_io_wfname, 'w+'), 'w+') do |io|
assert_equal 6, io.pwrite("Warld!", 7)
assert_equal 0, io.pos
assert_equal 7, io.pwrite("Hello, ", 0)
assert_equal 0, io.pos
assert_equal "Hello, Warld!", io.read
assert_equal 6, io.pwrite("world!", 7)
assert_equal 13, io.pos
io.pos = 0
assert_equal "Hello, world!", io.read
end
end
assert('IO.pipe') do
begin
called = false
IO.pipe do |r, w|
assert_true r.kind_of?(IO)
assert_true w.kind_of?(IO)
assert_false r.closed?
assert_false w.closed?
assert_true FileTest.pipe?(r)
assert_true FileTest.pipe?(w)
assert_nil r.pid
assert_nil w.pid
assert_true 2 < r.fileno
assert_true 2 < w.fileno
assert_true r.fileno != w.fileno
assert_false r.sync
assert_true w.sync
assert_equal 8, w.write('test for')
assert_equal 'test', r.read(4)
assert_equal ' for', r.read(4)
assert_equal 5, w.write(' pipe')
assert_equal nil, w.close
assert_equal ' pipe', r.read
called = true
assert_raise(IOError) { r.write 'test' }
# TODO:
# This assert expect raise IOError but got RuntimeError
# Because mruby-io not have flag for I/O readable
# assert_raise(IOError) { w.read }
end
assert_true called
assert_nothing_raised do
IO.pipe { |r, w| r.close; w.close }
end
rescue NotImplementedError => e
skip e.message
end
end
assert('`cmd`') do
begin
assert_equal `#{$cmd}echo foo`, "foo#{$crlf}"
rescue NotImplementedError => e
skip e.message
end
end
MRubyIOTestUtil.io_test_cleanup
@@ -0,0 +1,245 @@
#include <mruby/common.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#include <winsock.h>
#include <io.h>
#include <fcntl.h>
#include <direct.h>
#include <stdlib.h>
#include <malloc.h>
#if (!defined __MINGW64__) && (!defined __MINGW32__)
typedef int mode_t;
#endif
#define open _open
#define close _close
#if defined(_MSC_VER) || \
(defined(MRB_MINGW32_VERSION) && MRB_MINGW32_VERSION < 3021) || \
(defined(MRB_MINGW64_VERSION) && MRB_MINGW64_VERSION < 4000)
#include <sys/stat.h>
static int
mkstemp(char *p)
{
int fd;
char* fname = _mktemp(p);
if (fname == NULL)
return -1;
fd = open(fname, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE);
if (fd >= 0)
return fd;
return -1;
}
#endif
static char*
mkdtemp(char *temp)
{
char *path = _mktemp(temp);
if (path[0] == 0) return NULL;
if (_mkdir(path) < 0) return NULL;
return path;
}
#define umask(mode) _umask(mode)
#define rmdir(path) _rmdir(path)
#else
#include <sys/socket.h>
#include <unistd.h>
#include <sys/un.h>
#include <fcntl.h>
#include <libgen.h>
#endif
#include <sys/stat.h>
#include <stdlib.h>
#include "mruby.h"
#include "mruby/array.h"
#include "mruby/error.h"
#include "mruby/string.h"
#include "mruby/variable.h"
#include <mruby/ext/io.h>
static mrb_value
mrb_io_test_io_setup(mrb_state *mrb, mrb_value self)
{
#define GVNAME(n) "$mrbtest_io_" #n "name"
enum {IDX_READ, IDX_WRITE, IDX_LINK, IDX_SOCKET, IDX_COUNT};
const char *gvnames[] = {GVNAME(rf), GVNAME(wf), GVNAME(symlink), GVNAME(socket)};
char *fnames[IDX_COUNT];
int fds[IDX_COUNT];
char msg[] = "mruby io test\n";
mode_t mask;
FILE *fp;
int i;
#if !defined(_WIN32) && !defined(_WIN64)
struct sockaddr_un sun0;
#endif
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_msg"), mrb_str_new_cstr(mrb, msg));
mask = umask(077);
for (i = 0; i < IDX_COUNT; i++) {
mrb_value fname = mrb_str_new_capa(mrb, 0);
#if !defined(_WIN32) && !defined(_WIN64)
/*
* Workaround for not being able to bind a socket to some file systems
* (e.g. vboxsf, NFS). [#4981]
*/
char *tmpdir = getenv("TMPDIR");
if (tmpdir && strlen(tmpdir) > 0) {
mrb_str_cat_cstr(mrb, fname, tmpdir);
if (*(RSTRING_END(fname)-1) != '/') mrb_str_cat_lit(mrb, fname, "/");
} else {
mrb_str_cat_lit(mrb, fname, "/tmp/");
}
#endif
mrb_str_cat_cstr(mrb, fname, gvnames[i]+1);
mrb_str_cat_cstr(mrb, fname, ".XXXXXXXX");
fnames[i] = RSTRING_PTR(fname);
fds[i] = mkstemp(fnames[i]);
if (fds[i] == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't create temporary file");
}
close(fds[i]);
mrb_gv_set(mrb, mrb_intern_cstr(mrb, gvnames[i]), fname);
}
umask(mask);
fp = fopen(fnames[IDX_READ], "wb");
if (fp == NULL) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't open temporary file");
return mrb_nil_value();
}
fputs(msg, fp);
fclose(fp);
fp = fopen(fnames[IDX_WRITE], "wb");
if (fp == NULL) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't open temporary file");
return mrb_nil_value();
}
fclose(fp);
#if !defined(_WIN32) && !defined(_WIN64)
unlink(fnames[IDX_LINK]);
if (symlink(basename(fnames[IDX_READ]), fnames[IDX_LINK]) == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a symbolic link");
}
unlink(fnames[IDX_SOCKET]);
fds[IDX_SOCKET] = socket(AF_UNIX, SOCK_STREAM, 0);
if (fds[IDX_SOCKET] == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a socket");
}
sun0.sun_family = AF_UNIX;
strncpy(sun0.sun_path, fnames[IDX_SOCKET], sizeof(sun0.sun_path)-1);
sun0.sun_path[sizeof(sun0.sun_path)-1] = 0;
if (bind(fds[IDX_SOCKET], (struct sockaddr *)&sun0, sizeof(sun0)) == -1) {
mrb_raisef(mrb, E_RUNTIME_ERROR, "can't bind AF_UNIX socket to %s: %d",
sun0.sun_path,
errno);
}
close(fds[IDX_SOCKET]);
#endif
return mrb_true_value();
#undef GVNAME
}
static mrb_value
mrb_io_test_io_cleanup(mrb_state *mrb, mrb_value self)
{
mrb_value rfname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_rfname"));
mrb_value wfname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_wfname"));
mrb_value symlinkname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_symlinkname"));
mrb_value socketname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_socketname"));
if (mrb_string_p(rfname)) {
remove(RSTRING_PTR(rfname));
}
if (mrb_string_p(wfname)) {
remove(RSTRING_PTR(wfname));
}
if (mrb_string_p(symlinkname)) {
remove(RSTRING_PTR(symlinkname));
}
if (mrb_string_p(socketname)) {
remove(RSTRING_PTR(socketname));
}
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_rfname"), mrb_nil_value());
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_wfname"), mrb_nil_value());
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_symlinkname"), mrb_nil_value());
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_socketname"), mrb_nil_value());
mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_msg"), mrb_nil_value());
return mrb_nil_value();
}
static mrb_value
mrb_io_test_mkdtemp(mrb_state *mrb, mrb_value klass)
{
mrb_value str;
char *cp;
mrb_get_args(mrb, "S", &str);
cp = mrb_str_to_cstr(mrb, str);
if (mkdtemp(cp) == NULL) {
mrb_sys_fail(mrb, "mkdtemp");
}
return mrb_str_new_cstr(mrb, cp);
}
static mrb_value
mrb_io_test_rmdir(mrb_state *mrb, mrb_value klass)
{
const char *cp;
mrb_get_args(mrb, "z", &cp);
if (rmdir(cp) == -1) {
mrb_sys_fail(mrb, "rmdir");
}
return mrb_true_value();
}
static mrb_value
mrb_io_win_p(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32) || defined(_WIN64)
# if defined(__CYGWIN__) || defined(__CYGWIN32__)
return mrb_false_value();
# else
return mrb_true_value();
# endif
#else
return mrb_false_value();
#endif
}
#ifdef MRB_WITH_IO_PREAD_PWRITE
# define MRB_WITH_IO_PREAD_PWRITE_ENABLED TRUE
#else
# define MRB_WITH_IO_PREAD_PWRITE_ENABLED FALSE
#endif
void
mrb_mruby_io_gem_test(mrb_state* mrb)
{
struct RClass *io_test = mrb_define_module(mrb, "MRubyIOTestUtil");
mrb_define_class_method(mrb, io_test, "io_test_setup", mrb_io_test_io_setup, MRB_ARGS_NONE());
mrb_define_class_method(mrb, io_test, "io_test_cleanup", mrb_io_test_io_cleanup, MRB_ARGS_NONE());
mrb_define_class_method(mrb, io_test, "mkdtemp", mrb_io_test_mkdtemp, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, io_test, "rmdir", mrb_io_test_rmdir, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, io_test, "win?", mrb_io_win_p, MRB_ARGS_NONE());
mrb_define_const(mrb, io_test, "MRB_WITH_IO_PREAD_PWRITE", mrb_bool_value(MRB_WITH_IO_PREAD_PWRITE_ENABLED));
}