[ruby/io-console] Accept multiple mode arguments

https://github.com/ruby/io-console/commit/d140425f9f
This commit is contained in:
Nobuyoshi Nakada 2026-07-31 18:49:10 +09:00 committed by git
parent f407596837
commit d1bfedce7f
2 changed files with 86 additions and 52 deletions

View File

@ -1914,52 +1914,59 @@ console_ttyname(VALUE io)
# define console_ttyname rb_f_notimplement
#endif
typedef enum {
platform_none,
#ifdef HAVE_RB_PREPEND_MODULE
platform_any,
typedef enum {
platform_default,
#if defined _WIN32 || defined __CYGWIN__
platform_cygwin,
platform_msys,
#endif
platform_any,
platform_default_bit = 1U << platform_default,
#if defined _WIN32 || defined __CYGWIN__
platform_cygwin_bit = 1U << platform_cygwin,
platform_msys_bit = 1U << platform_msys,
#endif
platform_max
platform_any_bit = (1U << platform_any) - 1 /* all bits */
} console_platform_t;
#ifdef HAVE_RB_PREPEND_MODULE
/*
* call-seq:
* io.tty?([mode]) -> true or false
* io.tty?([mode, ...]) -> true or false
*
* Returns +true+ if the stream is associated with a terminal device (tty),
* +false+ otherwise.
*
* If non-nil +mode+ is given, platform dependent tty is also checked
* in addition to the default tty.
* If one or more +type+s are given, returns +true+ if the stream is
* associated with any of the specified tty types.
*
* - +:any+ : Returns +true+ for any known kind of tty.
* - +:any+ : Returns +true+ for any known kind of tty, including the
* default tty.
* - +:cygwin+ : Returns +true+ for cygwin tty, on Windows.
* - +:msys+ : Returns +true+ for msys2 tty, on Windows.
*/
static VALUE
console_platform_tty_p(int argc, VALUE *argv, VALUE io)
{
VALUE ret;
console_platform_t mode = platform_none;
VALUE ret = Qfalse;
int mode = 0;
if (rb_check_arity(argc, 0, 1)) {
VALUE m = argv[0];
if (!NIL_P(m)) {
if (argc > 0) {
int i;
for (i = 0; i < argc; ++i) {
VALUE m = argv[i];
if (NIL_P(m)) continue;
Check_Type(m, T_SYMBOL);
if (m == ID2SYM(rb_intern("any"))) {
mode = platform_any;
mode |= platform_any_bit;
}
#if defined _WIN32 || defined __CYGWIN__
else if (m == ID2SYM(rb_intern("cygwin"))) {
mode = platform_cygwin;
mode |= platform_cygwin_bit;
}
else if (m == ID2SYM(rb_intern("msys"))) {
mode = platform_msys;
mode |= platform_msys_bit;
}
#endif
else {
@ -1967,37 +1974,34 @@ console_platform_tty_p(int argc, VALUE *argv, VALUE io)
}
}
}
ret = rb_call_super(0, 0);
if (mode != platform_none && !RTEST(ret)) {
if ((mode & platform_default_bit) || (mode == 0)) {
ret = rb_call_super(0, 0);
}
if ((mode & ~platform_default_bit) && !RTEST(ret)) {
#if defined _WIN32 || defined __CYGWIN__
HANDLE h;
union {
FILE_NAME_INFO info;
WCHAR rest[MAX_PATH];
} buffer;
WCHAR *const name = buffer.info.FileName;
const WCHAR *ptr;
DWORD len;
if (mode & (platform_cygwin_bit | platform_msys_bit)) {
struct {
FILE_NAME_INFO info;
WCHAR rest[MAX_PATH];
} buffer;
h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
if (GetFileType(h) != FILE_TYPE_PIPE) return Qfalse;
if (!GetFileInformationByHandleEx(h, FileNameInfo, &buffer, sizeof(buffer))) return Qfalse;
len = buffer.info.FileNameLength / sizeof(WCHAR);
name[len] = L'\0';
# define skip_platform_tty_prefix(type) \
(memcmp(name, L"\\" #type "-", sizeof(L"\\" #type)) == 0 ? \
&name[rb_strlen_lit(L"\\" #type "-")] : 0)
if (mode == platform_cygwin || mode == platform_any) {
ptr = skip_platform_tty_prefix(cygwin);
HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
if ((GetFileType(h) == FILE_TYPE_PIPE) &&
GetFileInformationByHandleEx(h, FileNameInfo, &buffer, sizeof(buffer))) {
WCHAR *const name = buffer.info.FileName;
DWORD len = buffer.info.FileNameLength / sizeof(WCHAR);
name[len] = L'\0';
# define tty_pipe_p(type) \
(memcmp(name, L"\\" #type "-", sizeof(L"\\" #type)) == 0 && \
wcsstr(&name[rb_strlen_lit("\\" #type "-")], L"-pty") != NULL)
if (!ret && (mode & platform_cygwin_bit)) {
ret = tty_pipe_p(cygwin);
}
if (!ret && (mode & platform_msys_bit)) {
ret = tty_pipe_p(msys);
}
}
}
else if (mode == platform_msys || mode == platform_any) {
ptr = skip_platform_tty_prefix(msys);
}
else {
return Qfalse;
}
if (!ptr) return Qfalse;
if (wcsstr(ptr, L"-pty")) ret = Qtrue;
#endif
}
return ret;

View File

@ -61,16 +61,32 @@ class TestIO_Console < Test::Unit::TestCase
end
end
TTY_ENHANCED = IO.instance_method(:tty?).arity != 0
def test_tty?
omit "not supported" if IO.instance_method(:tty?).arity == 0
assert_include([true, false], STDIN.tty?(:any))
pend "not supported" unless TTY_ENHANCED
tty = STDIN.tty?(:any)
assert_include([true, false], tty)
assert_equal(tty, STDIN.tty?(:any, :any))
end
def test_tty_non_tty
pend "not supported" unless TTY_ENHANCED
File.open(IO::NULL) do |f|
assert_not_predicate(f, :tty?)
assert_not_operator(f, :tty?, :any)
assert_not_send([f, :tty?, :any, :any])
assert_raise(TypeError) {f.tty?("any")}
assert_raise(ArgumentError) {f.tty?(:unknown)}
end
end
end
defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
defined?(PTY) and defined?(IO.console) and \
class TestIO_Console
Bug6116 = '[ruby-dev:45309]'
def test_raw
@ -250,6 +266,19 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
}
end
def test_tty_on_pty
pend "not supported" unless TTY_ENHANCED
helper {|_, s|
assert_predicate(s, :tty?)
assert_operator(s, :tty?, :any)
assert_send([s, :tty?, :any, :any])
assert_raise(TypeError) {s.tty?("any")}
assert_raise(ArgumentError) {s.tty?(:unknown)}
}
end
def test_getpass
run_pty("p IO.console.getpass('> ')") do |r, w|
assert_equal("> ", r.readpartial(10))
@ -503,7 +532,8 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
end
end
defined?(IO.console) and IO.console and TestIO_Console.class_eval do
defined?(IO.console) and IO.console and \
class TestIO_Console
def test_get_winsize_console
s = IO.console.winsize
assert_kind_of(Array, s)
@ -613,8 +643,8 @@ defined?(IO.console) and IO.console and TestIO_Console.class_eval do
end
end
defined?(IO.console) and IO.console and IO.console.respond_to?(:pressed?) and
TestIO_Console.class_eval do
defined?(IO.console) and IO.console and IO.console.respond_to?(:pressed?) and \
class TestIO_Console
def test_pressed_valid
assert_include([true, false], IO.console.pressed?("HOME"))
assert_include([true, false], IO.console.pressed?(:"HOME"))
@ -628,7 +658,7 @@ defined?(IO.console) and IO.console and IO.console.respond_to?(:pressed?) and
end
end
TestIO_Console.class_eval do
class TestIO_Console
def test_stringio_getch
assert_ruby_status %w"--disable=gems -rstringio -rio/console", %q{
abort unless StringIO.method_defined?(:getch)