From d1bfedce7fb06eff99967213e970f4e57ca34083 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 31 Jul 2026 18:49:10 +0900 Subject: [PATCH] [ruby/io-console] Accept multiple mode arguments https://github.com/ruby/io-console/commit/d140425f9f --- ext/io/console/console.c | 94 ++++++++++++++++-------------- test/io/console/test_io_console.rb | 44 +++++++++++--- 2 files changed, 86 insertions(+), 52 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 9c1277edf9..edc882efe0 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -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; diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 55187da3c5..e40de3e1ce 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -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)