diff options
Diffstat (limited to 'ext/io')
-rw-r--r-- | ext/io/console/.document | 2 | ||||
-rw-r--r-- | ext/io/console/console.c | 859 | ||||
-rw-r--r-- | ext/io/console/depend | 4 | ||||
-rw-r--r-- | ext/io/console/extconf.rb | 7 | ||||
-rw-r--r-- | ext/io/console/io-console.gemspec | 31 | ||||
-rw-r--r-- | ext/io/nonblock/depend | 4 | ||||
-rw-r--r-- | ext/io/nonblock/extconf.rb | 7 | ||||
-rw-r--r-- | ext/io/nonblock/io-nonblock.gemspec | 16 | ||||
-rw-r--r-- | ext/io/nonblock/nonblock.c | 132 | ||||
-rw-r--r-- | ext/io/wait/depend | 4 | ||||
-rw-r--r-- | ext/io/wait/extconf.rb | 33 | ||||
-rw-r--r-- | ext/io/wait/io-wait.gemspec | 23 | ||||
-rw-r--r-- | ext/io/wait/wait.c | 223 |
13 files changed, 821 insertions, 524 deletions
diff --git a/ext/io/console/.document b/ext/io/console/.document new file mode 100644 index 0000000000..945a377256 --- /dev/null +++ b/ext/io/console/.document @@ -0,0 +1,2 @@ +console.c +lib/size.rb diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 5dec1a4c06..7130c29a8b 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -2,6 +2,10 @@ /* * console IO module */ + +static const char *const +IO_CONSOLE_VERSION = "0.7.2"; + #include "ruby.h" #include "ruby/io.h" #include "ruby/thread.h" @@ -75,10 +79,10 @@ getattr(int fd, conmode *t) #define SET_LAST_ERROR (0) #endif -static ID id_getc, id_console, id_close, id_min, id_time, id_intr; -#if ENABLE_IO_GETPASS -static ID id_gets, id_chomp_bang; -#endif +#define CSI "\x1b\x5b" + +static ID id_getc, id_console, id_close; +static ID id_gets, id_flush, id_chomp_bang; #if defined HAVE_RUBY_FIBER_SCHEDULER_H # include "ruby/fiber/scheduler.h" @@ -87,9 +91,47 @@ extern VALUE rb_scheduler_timeout(struct timeval *timeout); # define rb_fiber_scheduler_make_timeout rb_scheduler_timeout #endif -#define sys_fail_fptr(fptr) rb_sys_fail_str((fptr)->pathv) +#ifndef HAVE_RB_IO_DESCRIPTOR +static int +io_descriptor_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->fd; +} +#define rb_io_descriptor io_descriptor_fallback +#endif + +#ifndef HAVE_RB_IO_PATH +static VALUE +io_path_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->pathv; +} +#define rb_io_path io_path_fallback +#endif + +#ifndef HAVE_RB_IO_GET_WRITE_IO +static VALUE +io_get_write_io_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + VALUE wio = fptr->tied_io_for_writing; + return wio ? wio : io; +} +#define rb_io_get_write_io io_get_write_io_fallback +#endif + +#define sys_fail(io) rb_sys_fail_str(rb_io_path(io)) #ifndef HAVE_RB_F_SEND +#ifndef RB_PASS_CALLED_KEYWORDS +# define rb_funcallv_kw(recv, mid, arg, argv, kw_splat) rb_funcallv(recv, mid, arg, argv) +#endif + static ID id___send__; static VALUE @@ -104,22 +146,38 @@ rb_f_send(int argc, VALUE *argv, VALUE recv) else { vid = id___send__; } - return rb_funcallv(recv, vid, argc, argv); + return rb_funcallv_kw(recv, vid, argc, argv, RB_PASS_CALLED_KEYWORDS); } #endif +enum rawmode_opt_ids { + kwd_min, + kwd_time, + kwd_intr, + rawmode_opt_id_count +}; +static ID rawmode_opt_ids[rawmode_opt_id_count]; + typedef struct { int vmin; int vtime; int intr; } rawmode_arg_t; +#ifndef UNDEF_P +# define UNDEF_P(obj) ((obj) == Qundef) +#endif +#ifndef NIL_OR_UNDEF_P +# define NIL_OR_UNDEF_P(obj) (NIL_P(obj) || UNDEF_P(obj)) +#endif + static rawmode_arg_t * rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts) { int argc = *argcp; rawmode_arg_t *optp = NULL; VALUE vopts = Qnil; + VALUE optvals[rawmode_opt_id_count]; #ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS argc = rb_scan_args(argc, argv, "*:", NULL, &vopts); #else @@ -134,19 +192,20 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t * } #endif rb_check_arity(argc, min_argc, max_argc); - if (!NIL_P(vopts)) { - VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min)); - VALUE vtime = rb_hash_aref(vopts, ID2SYM(id_time)); - VALUE intr = rb_hash_aref(vopts, ID2SYM(id_intr)); + if (rb_get_kwargs(vopts, rawmode_opt_ids, + 0, rawmode_opt_id_count, optvals)) { + VALUE vmin = optvals[kwd_min]; + VALUE vtime = optvals[kwd_time]; + VALUE intr = optvals[kwd_intr]; /* default values by `stty raw` */ opts->vmin = 1; opts->vtime = 0; opts->intr = 0; - if (!NIL_P(vmin)) { + if (!NIL_OR_UNDEF_P(vmin)) { opts->vmin = NUM2INT(vmin); optp = opts; } - if (!NIL_P(vtime)) { + if (!NIL_OR_UNDEF_P(vtime)) { VALUE v10 = INT2FIX(10); vtime = rb_funcall3(vtime, '*', 1, &v10); opts->vtime = NUM2INT(vtime); @@ -161,6 +220,7 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t * opts->intr = 0; optp = opts; break; + case Qundef: case Qnil: break; default: @@ -271,33 +331,21 @@ set_ttymode(int fd, conmode *t, void (*setter)(conmode *, void *), void *arg) return setattr(fd, &r); } -#define GetReadFD(fptr) ((fptr)->fd) - -static inline int -get_write_fd(const rb_io_t *fptr) -{ - VALUE wio = fptr->tied_io_for_writing; - rb_io_t *ofptr; - if (!wio) return fptr->fd; - GetOpenFile(wio, ofptr); - return ofptr->fd; -} -#define GetWriteFD(fptr) get_write_fd(fptr) +#define GetReadFD(io) rb_io_descriptor(io) +#define GetWriteFD(io) rb_io_descriptor(rb_io_get_write_io(io)) #define FD_PER_IO 2 static VALUE ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg) { - rb_io_t *fptr; int status = -1; int error = 0; int fd[FD_PER_IO]; conmode t[FD_PER_IO]; VALUE result = Qnil; - GetOpenFile(io, fptr); - fd[0] = GetReadFD(fptr); + fd[0] = GetReadFD(io); if (fd[0] != -1) { if (set_ttymode(fd[0], t+0, setter, arg)) { status = 0; @@ -307,7 +355,7 @@ ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, vo fd[0] = -1; } } - fd[1] = GetWriteFD(fptr); + fd[1] = GetWriteFD(io); if (fd[1] != -1 && fd[1] != fd[0]) { if (set_ttymode(fd[1], t+1, setter, arg)) { status = 0; @@ -320,14 +368,13 @@ ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, vo if (status == 0) { result = rb_protect(func, farg, &status); } - GetOpenFile(io, fptr); - if (fd[0] != -1 && fd[0] == GetReadFD(fptr)) { + if (fd[0] != -1 && fd[0] == GetReadFD(io)) { if (!setattr(fd[0], t+0)) { error = errno; status = -1; } } - if (fd[1] != -1 && fd[1] != fd[0] && fd[1] == GetWriteFD(fptr)) { + if (fd[1] != -1 && fd[1] != fd[0] && fd[1] == GetWriteFD(io)) { if (!setattr(fd[1], t+1)) { error = errno; status = -1; @@ -413,15 +460,11 @@ static VALUE console_set_raw(int argc, VALUE *argv, VALUE io) { conmode t; - rb_io_t *fptr; - int fd; rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); - - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!getattr(fd, &t)) sys_fail_fptr(fptr); + int fd = GetReadFD(io); + if (!getattr(fd, &t)) sys_fail(io); set_rawmode(&t, optp); - if (!setattr(fd, &t)) sys_fail_fptr(fptr); + if (!setattr(fd, &t)) sys_fail(io); return io; } @@ -457,14 +500,10 @@ static VALUE console_set_cooked(VALUE io) { conmode t; - rb_io_t *fptr; - int fd; - - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!getattr(fd, &t)) sys_fail_fptr(fptr); + int fd = GetReadFD(io); + if (!getattr(fd, &t)) sys_fail(io); set_cookedmode(&t, NULL); - if (!setattr(fd, &t)) sys_fail_fptr(fptr); + if (!setattr(fd, &t)) sys_fail(io); return io; } @@ -555,8 +594,8 @@ console_getch(int argc, VALUE *argv, VALUE io) if (w < 0) rb_eof_error(); if (!(w & RB_WAITFD_IN)) return Qnil; # else - VALUE result = rb_io_wait(io, RUBY_IO_READABLE, timeout); - if (result == Qfalse) return Qnil; + VALUE result = rb_io_wait(io, RB_INT2NUM(RUBY_IO_READABLE), timeout); + if (!RTEST(result)) return Qnil; # endif } else if (optp->vtime) { @@ -616,17 +655,17 @@ static VALUE console_set_echo(VALUE io, VALUE f) { conmode t; - rb_io_t *fptr; - int fd; + int fd = GetReadFD(io); + + if (!getattr(fd, &t)) sys_fail(io); - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!getattr(fd, &t)) sys_fail_fptr(fptr); if (RTEST(f)) - set_echo(&t, NULL); + set_echo(&t, NULL); else - set_noecho(&t, NULL); - if (!setattr(fd, &t)) sys_fail_fptr(fptr); + set_noecho(&t, NULL); + + if (!setattr(fd, &t)) sys_fail(io); + return io; } @@ -642,12 +681,9 @@ static VALUE console_echo_p(VALUE io) { conmode t; - rb_io_t *fptr; - int fd; + int fd = GetReadFD(io); - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!getattr(fd, &t)) sys_fail_fptr(fptr); + if (!getattr(fd, &t)) sys_fail(io); return echo_p(&t) ? Qtrue : Qfalse; } @@ -726,12 +762,9 @@ static VALUE console_conmode_get(VALUE io) { conmode t; - rb_io_t *fptr; - int fd; + int fd = GetReadFD(io); - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!getattr(fd, &t)) sys_fail_fptr(fptr); + if (!getattr(fd, &t)) sys_fail(io); return conmode_new(cConmode, &t); } @@ -748,14 +781,12 @@ static VALUE console_conmode_set(VALUE io, VALUE mode) { conmode *t, r; - rb_io_t *fptr; - int fd; + int fd = GetReadFD(io); TypedData_Get_Struct(mode, conmode, &conmode_type, t); r = *t; - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); - if (!setattr(fd, &r)) sys_fail_fptr(fptr); + + if (!setattr(fd, &r)) sys_fail(io); return mode; } @@ -791,13 +822,9 @@ typedef CONSOLE_SCREEN_BUFFER_INFO rb_console_size_t; static VALUE console_winsize(VALUE io) { - rb_io_t *fptr; - int fd; rb_console_size_t ws; - - GetOpenFile(io, fptr); - fd = GetWriteFD(fptr); - if (!getwinsize(fd, &ws)) sys_fail_fptr(fptr); + int fd = GetWriteFD(io); + if (!getwinsize(fd, &ws)) sys_fail(io); return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws))); } @@ -813,7 +840,6 @@ console_winsize(VALUE io) static VALUE console_set_winsize(VALUE io, VALUE size) { - rb_io_t *fptr; rb_console_size_t ws; #if defined _WIN32 HANDLE wh; @@ -822,20 +848,17 @@ console_set_winsize(VALUE io, VALUE size) #endif VALUE row, col, xpixel, ypixel; const VALUE *sz; - int fd; long sizelen; + int fd; - GetOpenFile(io, fptr); size = rb_Array(size); if ((sizelen = RARRAY_LEN(size)) != 2 && sizelen != 4) { - rb_raise(rb_eArgError, - "wrong number of arguments (given %ld, expected 2 or 4)", - sizelen); + rb_raise(rb_eArgError, "wrong number of arguments (given %ld, expected 2 or 4)", sizelen); } sz = RARRAY_CONST_PTR(size); row = sz[0], col = sz[1], xpixel = ypixel = Qnil; if (sizelen == 4) xpixel = sz[2], ypixel = sz[3]; - fd = GetWriteFD(fptr); + fd = GetWriteFD(io); #if defined TIOCSWINSZ ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = 0; #define SET(m) ws.ws_##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) @@ -844,7 +867,7 @@ console_set_winsize(VALUE io, VALUE size) SET(xpixel); SET(ypixel); #undef SET - if (!setwinsize(fd, &ws)) sys_fail_fptr(fptr); + if (!setwinsize(fd, &ws)) sys_fail(io); #elif defined _WIN32 wh = (HANDLE)rb_w32_get_osfhandle(fd); #define SET(m) new##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) @@ -879,15 +902,23 @@ console_set_winsize(VALUE io, VALUE size) #endif #ifdef _WIN32 +/* + * call-seq: + * io.check_winsize_changed { ... } -> io + * + * Yields while console input events are queued. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ static VALUE console_check_winsize_changed(VALUE io) { - rb_io_t *fptr; HANDLE h; DWORD num; - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(fptr)); + h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io)); while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) { INPUT_RECORD rec; if (ReadConsoleInput(h, &rec, 1, &num)) { @@ -913,15 +944,11 @@ console_check_winsize_changed(VALUE io) static VALUE console_iflush(VALUE io) { - rb_io_t *fptr; - int fd; - - GetOpenFile(io, fptr); - fd = GetReadFD(fptr); #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H - if (tcflush(fd, TCIFLUSH)) sys_fail_fptr(fptr); + int fd = GetReadFD(io); + if (tcflush(fd, TCIFLUSH)) sys_fail(io); #endif - (void)fd; + return io; } @@ -936,13 +963,9 @@ console_iflush(VALUE io) static VALUE console_oflush(VALUE io) { - rb_io_t *fptr; - int fd; - - GetOpenFile(io, fptr); - fd = GetWriteFD(fptr); + int fd = GetWriteFD(io); #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H - if (tcflush(fd, TCOFLUSH)) sys_fail_fptr(fptr); + if (tcflush(fd, TCOFLUSH)) sys_fail(io); #endif (void)fd; return io; @@ -959,40 +982,38 @@ console_oflush(VALUE io) static VALUE console_ioflush(VALUE io) { - rb_io_t *fptr; #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H - int fd1, fd2; -#endif + int fd1 = GetReadFD(io); + int fd2 = GetWriteFD(io); - GetOpenFile(io, fptr); -#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H - fd1 = GetReadFD(fptr); - fd2 = GetWriteFD(fptr); if (fd2 != -1 && fd1 != fd2) { - if (tcflush(fd1, TCIFLUSH)) sys_fail_fptr(fptr); - if (tcflush(fd2, TCOFLUSH)) sys_fail_fptr(fptr); + if (tcflush(fd1, TCIFLUSH)) sys_fail(io); + if (tcflush(fd2, TCOFLUSH)) sys_fail(io); } else { - if (tcflush(fd1, TCIOFLUSH)) sys_fail_fptr(fptr); + if (tcflush(fd1, TCIOFLUSH)) sys_fail(io); } #endif + return io; } +/* + * call-seq: + * io.beep + * + * Beeps on the output console. + * + * You must require 'io/console' to use this method. + */ static VALUE console_beep(VALUE io) { - rb_io_t *fptr; - int fd; - - GetOpenFile(io, fptr); - fd = GetWriteFD(fptr); #ifdef _WIN32 - (void)fd; MessageBeep(0); #else - if (write(fd, "\a", 1) < 0) - sys_fail_fptr(fptr); + int fd = GetWriteFD(io); + if (write(fd, "\a", 1) < 0) sys_fail(io); #endif return io; } @@ -1013,79 +1034,6 @@ mode_in_range(VALUE val, int high, const char *modename) } #if defined _WIN32 -static VALUE -console_goto(VALUE io, VALUE y, VALUE x) -{ - rb_io_t *fptr; - int fd; - COORD pos; - - GetOpenFile(io, fptr); - fd = GetWriteFD(fptr); - pos.X = NUM2UINT(x); - pos.Y = NUM2UINT(y); - if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - -static VALUE -console_cursor_pos(VALUE io) -{ - rb_io_t *fptr; - int fd; - rb_console_size_t ws; - - GetOpenFile(io, fptr); - fd = GetWriteFD(fptr); - if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); -} - -static VALUE -console_move(VALUE io, int y, int x) -{ - rb_io_t *fptr; - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - pos->X += x; - pos->Y += y; - if (!SetConsoleCursorPosition(h, *pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - -static VALUE -console_goto_column(VALUE io, VALUE val) -{ - rb_io_t *fptr; - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - pos->X = NUM2INT(val); - if (!SetConsoleCursorPosition(h, *pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - static void constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos) { @@ -1096,86 +1044,12 @@ constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos) } static VALUE -console_erase_line(VALUE io, VALUE val) -{ - rb_io_t *fptr; - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - DWORD w; - int mode = mode_in_range(val, 2, "line erase"); - - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - w = winsize_col(&ws); - switch (mode) { - case 0: /* after cursor */ - w -= pos->X; - break; - case 1: /* before *and* cursor */ - w = pos->X + 1; - pos->X = 0; - break; - case 2: /* entire line */ - pos->X = 0; - break; - } - constat_clear(h, ws.wAttributes, w, *pos); - return io; -} - -static VALUE -console_erase_screen(VALUE io, VALUE val) -{ - rb_io_t *fptr; - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - DWORD w; - int mode = mode_in_range(val, 3, "screen erase"); - - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - w = winsize_col(&ws); - switch (mode) { - case 0: /* erase after cursor */ - w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); - break; - case 1: /* erase before *and* cursor */ - w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); - pos->X = 0; - pos->Y = ws.srWindow.Top; - break; - case 2: /* erase entire screen */ - w = (w * winsize_row(&ws)); - pos->X = 0; - pos->Y = ws.srWindow.Top; - break; - case 3: /* erase entire screen */ - w = (w * ws.dwSize.Y); - pos->X = 0; - pos->Y = 0; - break; - } - constat_clear(h, ws.wAttributes, w, *pos); - return io; -} - -static VALUE console_scroll(VALUE io, int line) { - rb_io_t *fptr; HANDLE h; rb_console_size_t ws; - GetOpenFile(io, fptr); - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr)); + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); if (!GetConsoleScreenBufferInfo(h, &ws)) { rb_syserr_fail(LAST_ERROR, 0); } @@ -1199,6 +1073,17 @@ console_scroll(VALUE io, int line) #include "win32_vk.inc" +/* + * call-seq: + * io.pressed?(key) -> bool + * + * Returns +true+ if +key+ is pressed. +key+ may be a virtual key + * code or its name (String or Symbol) with out "VK_" prefix. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ static VALUE console_key_pressed_p(VALUE io, VALUE k) { @@ -1234,23 +1119,11 @@ static int direct_query(VALUE io, const struct query_args *query) { if (RB_TYPE_P(io, T_FILE)) { - rb_io_t *fptr; - VALUE wio; - GetOpenFile(io, fptr); - wio = fptr->tied_io_for_writing; - if (wio) { - VALUE s = rb_str_new_cstr(query->qstr); - rb_io_write(wio, s); - rb_io_flush(wio); - return 1; - } - if (write(fptr->fd, query->qstr, strlen(query->qstr)) != -1) { - return 1; - } - if (fptr->fd == 0 && - write(1, query->qstr, strlen(query->qstr)) != -1) { - return 1; - } + VALUE wio = rb_io_get_write_io(io); + VALUE s = rb_str_new_cstr(query->qstr); + rb_io_write(wio, s); + rb_io_flush(wio); + return 1; } return 0; } @@ -1301,8 +1174,40 @@ console_vt_response(int argc, VALUE *argv, VALUE io, const struct query_args *qa } static VALUE +console_scroll(VALUE io, int line) +{ + if (line) { + VALUE s = rb_sprintf(CSI "%d%c", line < 0 ? -line : line, + line < 0 ? 'T' : 'S'); + rb_io_write(io, s); + } + return io; +} + +# define console_key_pressed_p rb_f_notimplement +#endif + +/* + * call-seq: + * io.cursor -> [row, column] + * + * Returns the current cursor position as a two-element array of integers (row, column) + * + * io.cursor # => [3, 5] + * + * You must require 'io/console' to use this method. + */ +static VALUE console_cursor_pos(VALUE io) { +#ifdef _WIN32 + rb_console_size_t ws; + int fd = GetWriteFD(io); + if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); +#else static const struct query_args query = {"\033[6n", 0}; VALUE resp = console_vt_response(0, 0, io, &query); VALUE row, column, term; @@ -1319,64 +1224,205 @@ console_cursor_pos(VALUE io) RARRAY_ASET(resp, 0, INT2NUM(r)); RARRAY_ASET(resp, 1, INT2NUM(c)); return resp; +#endif } +/* + * call-seq: + * io.goto(line, column) -> io + * + * Set the cursor position at +line+ and +column+. + * + * You must require 'io/console' to use this method. + */ static VALUE console_goto(VALUE io, VALUE y, VALUE x) { - rb_io_write(io, rb_sprintf("\x1b[%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); +#ifdef _WIN32 + COORD pos; + int fd = GetWriteFD(io); + pos.X = NUM2UINT(x); + pos.Y = NUM2UINT(y); + if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); +#endif return io; } static VALUE console_move(VALUE io, int y, int x) { +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X += x; + pos->Y += y; + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else if (x || y) { VALUE s = rb_str_new_cstr(""); - if (y) rb_str_catf(s, "\x1b[%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B'); - if (x) rb_str_catf(s, "\x1b[%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C'); + if (y) rb_str_catf(s, CSI "%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B'); + if (x) rb_str_catf(s, CSI "%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C'); rb_io_write(io, s); rb_io_flush(io); } +#endif return io; } +/* + * call-seq: + * io.goto_column(column) -> io + * + * Set the cursor position at +column+ in the same line of the current + * position. + * + * You must require 'io/console' to use this method. + */ static VALUE console_goto_column(VALUE io, VALUE val) { - rb_io_write(io, rb_sprintf("\x1b[%dG", NUM2UINT(val)+1)); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X = NUM2INT(val); + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%dG", NUM2UINT(val)+1)); +#endif return io; } +/* + * call-seq: + * io.erase_line(mode) -> io + * + * Erases the line at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire line + * + * You must require 'io/console' to use this method. + */ static VALUE console_erase_line(VALUE io, VALUE val) { int mode = mode_in_range(val, 2, "line erase"); - rb_io_write(io, rb_sprintf("\x1b[%dK", mode)); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + w = winsize_col(&ws); + switch (mode) { + case 0: /* after cursor */ + w -= pos->X; + break; + case 1: /* before *and* cursor */ + w = pos->X + 1; + pos->X = 0; + break; + case 2: /* entire line */ + pos->X = 0; + break; + } + constat_clear(h, ws.wAttributes, w, *pos); + return io; +#else + rb_io_write(io, rb_sprintf(CSI "%dK", mode)); +#endif return io; } +/* + * call-seq: + * io.erase_screen(mode) -> io + * + * Erases the screen at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire screen + * + * You must require 'io/console' to use this method. + */ static VALUE console_erase_screen(VALUE io, VALUE val) { int mode = mode_in_range(val, 3, "screen erase"); - rb_io_write(io, rb_sprintf("\x1b[%dJ", mode)); - return io; -} +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; -static VALUE -console_scroll(VALUE io, int line) -{ - if (line) { - VALUE s = rb_sprintf("\x1b[%d%c", line < 0 ? -line : line, - line < 0 ? 'T' : 'S'); - rb_io_write(io, s); + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + w = winsize_col(&ws); + switch (mode) { + case 0: /* erase after cursor */ + w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); + break; + case 1: /* erase before *and* cursor */ + w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 2: /* erase entire screen */ + w = (w * winsize_row(&ws)); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 3: /* erase entire screen */ + w = (w * ws.dwSize.Y); + pos->X = 0; + pos->Y = 0; + break; } + constat_clear(h, ws.wAttributes, w, *pos); +#else + rb_io_write(io, rb_sprintf(CSI "%dJ", mode)); +#endif return io; } -# define console_key_pressed_p rb_f_notimplement -#endif +/* + * call-seq: + * io.cursor = [line, column] -> io + * + * Same as <tt>io.goto(line, column)</tt> + * + * See IO#goto. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_set(VALUE io, VALUE cpos) { @@ -1385,42 +1431,98 @@ console_cursor_set(VALUE io, VALUE cpos) return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1)); } +/* + * call-seq: + * io.cursor_up(n) -> io + * + * Moves the cursor up +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_up(VALUE io, VALUE val) { return console_move(io, -NUM2INT(val), 0); } +/* + * call-seq: + * io.cursor_down(n) -> io + * + * Moves the cursor down +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_down(VALUE io, VALUE val) { return console_move(io, +NUM2INT(val), 0); } +/* + * call-seq: + * io.cursor_left(n) -> io + * + * Moves the cursor left +n+ columns. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_left(VALUE io, VALUE val) { return console_move(io, 0, -NUM2INT(val)); } +/* + * call-seq: + * io.cursor_right(n) -> io + * + * Moves the cursor right +n+ columns. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_right(VALUE io, VALUE val) { return console_move(io, 0, +NUM2INT(val)); } +/* + * call-seq: + * io.scroll_forward(n) -> io + * + * Scrolls the entire scrolls forward +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_scroll_forward(VALUE io, VALUE val) { return console_scroll(io, +NUM2INT(val)); } +/* + * call-seq: + * io.scroll_backward(n) -> io + * + * Scrolls the entire scrolls backward +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_scroll_backward(VALUE io, VALUE val) { return console_scroll(io, -NUM2INT(val)); } +/* + * call-seq: + * io.clear_screen -> io + * + * Clears the entire screen and moves the cursor top-left corner. + * + * You must require 'io/console' to use this method. + */ static VALUE console_clear_screen(VALUE io) { @@ -1429,6 +1531,38 @@ console_clear_screen(VALUE io) return io; } +#ifndef HAVE_RB_IO_OPEN_DESCRIPTOR +static VALUE +io_open_descriptor_fallback(VALUE klass, int descriptor, int mode, VALUE path, VALUE timeout, void *encoding) +{ + rb_update_max_fd(descriptor); + + VALUE arguments[2] = { + INT2NUM(descriptor), + INT2FIX(mode), + }; + + VALUE self = rb_class_new_instance(2, arguments, klass); + + rb_io_t *fptr; + GetOpenFile(self, fptr); + fptr->pathv = path; + fptr->mode |= mode; + + return self; +} +#define rb_io_open_descriptor io_open_descriptor_fallback +#endif + +#ifndef HAVE_RB_IO_CLOSED_P +static VALUE +rb_io_closed_p(VALUE io) +{ + rb_io_t *fptr = RFILE(io)->fptr; + return fptr->fd == -1 ? Qtrue : Qfalse; +} +#endif + /* * call-seq: * IO.console -> #<File:/dev/tty> @@ -1446,34 +1580,37 @@ static VALUE console_dev(int argc, VALUE *argv, VALUE klass) { VALUE con = 0; - rb_io_t *fptr; VALUE sym = 0; rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + if (argc) { - Check_Type(sym = argv[0], T_SYMBOL); + Check_Type(sym = argv[0], T_SYMBOL); } + + // Force the class to be File. if (klass == rb_cIO) klass = rb_cFile; + if (rb_const_defined(klass, id_console)) { - con = rb_const_get(klass, id_console); - if (!RB_TYPE_P(con, T_FILE) || - (!(fptr = RFILE(con)->fptr) || GetReadFD(fptr) == -1)) { - rb_const_remove(klass, id_console); - con = 0; - } + con = rb_const_get(klass, id_console); + if (!RB_TYPE_P(con, T_FILE) || RTEST(rb_io_closed_p(con))) { + rb_const_remove(klass, id_console); + con = 0; + } } + if (sym) { - if (sym == ID2SYM(id_close) && argc == 1) { - if (con) { - rb_io_close(con); - rb_const_remove(klass, id_console); - con = 0; - } - return Qnil; - } + if (sym == ID2SYM(id_close) && argc == 1) { + if (con) { + rb_io_close(con); + rb_const_remove(klass, id_console); + con = 0; + } + return Qnil; + } } + if (!con) { - VALUE args[2]; #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H || defined HAVE_SGTTY_H # define CONSOLE_DEVICE "/dev/tty" #elif defined _WIN32 @@ -1485,44 +1622,36 @@ console_dev(int argc, VALUE *argv, VALUE klass) # define CONSOLE_DEVICE_FOR_READING CONSOLE_DEVICE #endif #ifdef CONSOLE_DEVICE_FOR_WRITING - VALUE out; - rb_io_t *ofptr; + VALUE out; + rb_io_t *ofptr; #endif - int fd; + int fd; + VALUE path = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE)); #ifdef CONSOLE_DEVICE_FOR_WRITING - fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0); - if (fd < 0) return Qnil; - rb_update_max_fd(fd); - args[1] = INT2FIX(O_WRONLY); - args[0] = INT2NUM(fd); - out = rb_class_new_instance(2, args, klass); + fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0); + if (fd < 0) return Qnil; + out = rb_io_open_descriptor(klass, fd, FMODE_WRITABLE | FMODE_SYNC, path, Qnil, NULL); #endif - fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_READING, O_RDWR, 0); - if (fd < 0) { + fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_READING, O_RDWR, 0); + if (fd < 0) { #ifdef CONSOLE_DEVICE_FOR_WRITING - rb_io_close(out); + rb_io_close(out); #endif - return Qnil; - } - rb_update_max_fd(fd); - args[1] = INT2FIX(O_RDWR); - args[0] = INT2NUM(fd); - con = rb_class_new_instance(2, args, klass); - GetOpenFile(con, fptr); - fptr->pathv = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE)); + return Qnil; + } + + con = rb_io_open_descriptor(klass, fd, FMODE_READWRITE | FMODE_SYNC, path, Qnil, NULL); #ifdef CONSOLE_DEVICE_FOR_WRITING - GetOpenFile(out, ofptr); - ofptr->pathv = fptr->pathv; - fptr->tied_io_for_writing = out; - ofptr->mode |= FMODE_SYNC; + rb_io_set_write_io(con, out); #endif - fptr->mode |= FMODE_SYNC; - rb_const_set(klass, id_console, con); + rb_const_set(klass, id_console, con); } + if (sym) { - return rb_f_send(argc, argv, con); + return rb_f_send(argc, argv, con); } + return con; } @@ -1538,7 +1667,6 @@ io_getch(int argc, VALUE *argv, VALUE io) return rb_funcallv(io, id_getc, argc, argv); } -#if ENABLE_IO_GETPASS static VALUE puts_call(VALUE io) { @@ -1546,6 +1674,12 @@ puts_call(VALUE io) } static VALUE +gets_call(VALUE io) +{ + return rb_funcallv(io, id_gets, 0, 0); +} + +static VALUE getpass_call(VALUE io) { return ttymode(io, rb_io_gets, io, set_noecho, NULL); @@ -1565,7 +1699,8 @@ static VALUE str_chomp(VALUE str) { if (!NIL_P(str)) { - rb_funcallv(str, id_chomp_bang, 0, 0); + const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */ + rb_funcallv(str, id_chomp_bang, 1, &rs); } return str; } @@ -1582,6 +1717,12 @@ str_chomp(VALUE str) * see String#chomp!. * * You must require 'io/console' to use this method. + * + * require 'io/console' + * IO::console.getpass("Enter password:") + * Enter password: + * # => "mypassword" + * */ static VALUE console_getpass(int argc, VALUE *argv, VALUE io) @@ -1592,6 +1733,7 @@ console_getpass(int argc, VALUE *argv, VALUE io) wio = rb_io_get_write_io(io); if (wio == io && io == rb_stdin) wio = rb_stderr; prompt(argc, argv, wio); + rb_io_flush(wio); str = rb_ensure(getpass_call, io, puts_call, wio); return str_chomp(str); } @@ -1609,11 +1751,10 @@ io_getpass(int argc, VALUE *argv, VALUE io) rb_check_arity(argc, 0, 1); prompt(argc, argv, io); - str = str_chomp(rb_funcallv(io, id_gets, 0, 0)); - puts_call(io); - return str; + rb_check_funcall(io, id_flush, 0, 0); + str = rb_ensure(gets_call, io, puts_call, io); + return str_chomp(str); } -#endif /* * IO console methods @@ -1623,15 +1764,16 @@ Init_console(void) { #undef rb_intern id_getc = rb_intern("getc"); -#if ENABLE_IO_GETPASS id_gets = rb_intern("gets"); + id_flush = rb_intern("flush"); id_chomp_bang = rb_intern("chomp!"); -#endif id_console = rb_intern("console"); id_close = rb_intern("close"); - id_min = rb_intern("min"); - id_time = rb_intern("time"); - id_intr = rb_intern("intr"); +#define init_rawmode_opt_id(name) \ + rawmode_opt_ids[kwd_##name] = rb_intern(#name) + init_rawmode_opt_id(min); + init_rawmode_opt_id(time); + init_rawmode_opt_id(intr); #ifndef HAVE_RB_F_SEND id___send__ = rb_intern("__send__"); #endif @@ -1672,20 +1814,19 @@ InitVM_console(void) rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0); rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1); rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0); -#if ENABLE_IO_GETPASS rb_define_method(rb_cIO, "getpass", console_getpass, -1); -#endif rb_define_singleton_method(rb_cIO, "console", console_dev, -1); { + /* :stopdoc: */ VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); + /* :startdoc: */ rb_define_method(mReadable, "getch", io_getch, -1); -#if ENABLE_IO_GETPASS rb_define_method(mReadable, "getpass", io_getpass, -1); -#endif } { /* :stopdoc: */ cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject); + rb_define_const(cConmode, "VERSION", rb_str_new_cstr(IO_CONSOLE_VERSION)); rb_define_alloc_func(cConmode, conmode_alloc); rb_undef_method(cConmode, "initialize"); rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1); diff --git a/ext/io/console/depend b/ext/io/console/depend index e6014dcc59..59ca3442c2 100644 --- a/ext/io/console/depend +++ b/ext/io/console/depend @@ -16,6 +16,7 @@ console.o: $(hdrdir)/ruby/defines.h console.o: $(hdrdir)/ruby/encoding.h console.o: $(hdrdir)/ruby/fiber/scheduler.h console.o: $(hdrdir)/ruby/intern.h +console.o: $(hdrdir)/ruby/internal/abi.h console.o: $(hdrdir)/ruby/internal/anyargs.h console.o: $(hdrdir)/ruby/internal/arithmetic.h console.o: $(hdrdir)/ruby/internal/arithmetic/char.h @@ -53,6 +54,7 @@ console.o: $(hdrdir)/ruby/internal/attr/noexcept.h console.o: $(hdrdir)/ruby/internal/attr/noinline.h console.o: $(hdrdir)/ruby/internal/attr/nonnull.h console.o: $(hdrdir)/ruby/internal/attr/noreturn.h +console.o: $(hdrdir)/ruby/internal/attr/packed_struct.h console.o: $(hdrdir)/ruby/internal/attr/pure.h console.o: $(hdrdir)/ruby/internal/attr/restrict.h console.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h @@ -121,7 +123,6 @@ console.o: $(hdrdir)/ruby/internal/intern/enumerator.h console.o: $(hdrdir)/ruby/internal/intern/error.h console.o: $(hdrdir)/ruby/internal/intern/eval.h console.o: $(hdrdir)/ruby/internal/intern/file.h -console.o: $(hdrdir)/ruby/internal/intern/gc.h console.o: $(hdrdir)/ruby/internal/intern/hash.h console.o: $(hdrdir)/ruby/internal/intern/io.h console.o: $(hdrdir)/ruby/internal/intern/load.h @@ -152,7 +153,6 @@ console.o: $(hdrdir)/ruby/internal/memory.h console.o: $(hdrdir)/ruby/internal/method.h console.o: $(hdrdir)/ruby/internal/module.h console.o: $(hdrdir)/ruby/internal/newobj.h -console.o: $(hdrdir)/ruby/internal/rgengc.h console.o: $(hdrdir)/ruby/internal/scan_args.h console.o: $(hdrdir)/ruby/internal/special_consts.h console.o: $(hdrdir)/ruby/internal/static_assert.h diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index e8c5923b18..a72403c7f9 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -1,6 +1,12 @@ # frozen_string_literal: false require 'mkmf' +have_func("rb_io_path") +have_func("rb_io_descriptor") +have_func("rb_io_get_write_io") +have_func("rb_io_closed_p") +have_func("rb_io_open_descriptor") + ok = true if RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby" hdr = nil case @@ -29,7 +35,6 @@ when true elsif have_func("rb_scheduler_timeout") # 3.0 have_func("rb_io_wait") end - $defs << "-D""ENABLE_IO_GETPASS=1" create_makefile("io/console") {|conf| conf << "\n""VK_HEADER = #{vk_header}\n" } diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec index dabe9e68f8..f9c1729cb7 100644 --- a/ext/io/console/io-console.gemspec +++ b/ext/io/console/io-console.gemspec @@ -1,5 +1,13 @@ # -*- ruby -*- -_VERSION = "0.5.9" +_VERSION = ["", "ext/io/console/"].find do |dir| + begin + break File.open(File.join(__dir__, "#{dir}console.c")) {|f| + f.gets("\nIO_CONSOLE_VERSION ") + f.gets[/"(.+)"/, 1] + } + rescue Errno::ENOENT + end +end Gem::Specification.new do |s| s.name = "io-console" @@ -7,12 +15,14 @@ Gem::Specification.new do |s| s.summary = "Console interface" s.email = "nobu@ruby-lang.org" s.description = "add console capabilities to IO instances." - s.required_ruby_version = ">= 2.4.0" + s.required_ruby_version = ">= 2.6.0" s.homepage = "https://github.com/ruby/io-console" s.metadata["source_code_url"] = s.homepage + s.metadata["changelog_uri"] = s.homepage + "/releases" s.authors = ["Nobu Nakada"] s.require_path = %[lib] s.files = %w[ + .document LICENSE.txt README.md ext/io/console/console.c @@ -25,15 +35,16 @@ Gem::Specification.new do |s| if Gem::Platform === s.platform and s.platform =~ 'java' s.files.delete_if {|f| f.start_with?("ext/")} s.extensions.clear + s.require_paths.unshift('lib/ffi') s.files.concat(%w[ - lib/io/console.rb - lib/io/console/ffi/bsd_console.rb - lib/io/console/ffi/common.rb - lib/io/console/ffi/console.rb - lib/io/console/ffi/linux_console.rb - lib/io/console/ffi/native_console.rb - lib/io/console/ffi/stty_console.rb - lib/io/console/ffi/stub_console.rb + lib/ffi/io/console.rb + lib/ffi/io/console/bsd_console.rb + lib/ffi/io/console/common.rb + lib/ffi/io/console/linux_console.rb + lib/ffi/io/console/native_console.rb + lib/ffi/io/console/stty_console.rb + lib/ffi/io/console/stub_console.rb + lib/ffi/io/console/version.rb ]) end diff --git a/ext/io/nonblock/depend b/ext/io/nonblock/depend index 664c262e35..48384fca62 100644 --- a/ext/io/nonblock/depend +++ b/ext/io/nonblock/depend @@ -15,6 +15,7 @@ nonblock.o: $(hdrdir)/ruby/backward/2/stdarg.h nonblock.o: $(hdrdir)/ruby/defines.h nonblock.o: $(hdrdir)/ruby/encoding.h nonblock.o: $(hdrdir)/ruby/intern.h +nonblock.o: $(hdrdir)/ruby/internal/abi.h nonblock.o: $(hdrdir)/ruby/internal/anyargs.h nonblock.o: $(hdrdir)/ruby/internal/arithmetic.h nonblock.o: $(hdrdir)/ruby/internal/arithmetic/char.h @@ -52,6 +53,7 @@ nonblock.o: $(hdrdir)/ruby/internal/attr/noexcept.h nonblock.o: $(hdrdir)/ruby/internal/attr/noinline.h nonblock.o: $(hdrdir)/ruby/internal/attr/nonnull.h nonblock.o: $(hdrdir)/ruby/internal/attr/noreturn.h +nonblock.o: $(hdrdir)/ruby/internal/attr/packed_struct.h nonblock.o: $(hdrdir)/ruby/internal/attr/pure.h nonblock.o: $(hdrdir)/ruby/internal/attr/restrict.h nonblock.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h @@ -120,7 +122,6 @@ nonblock.o: $(hdrdir)/ruby/internal/intern/enumerator.h nonblock.o: $(hdrdir)/ruby/internal/intern/error.h nonblock.o: $(hdrdir)/ruby/internal/intern/eval.h nonblock.o: $(hdrdir)/ruby/internal/intern/file.h -nonblock.o: $(hdrdir)/ruby/internal/intern/gc.h nonblock.o: $(hdrdir)/ruby/internal/intern/hash.h nonblock.o: $(hdrdir)/ruby/internal/intern/io.h nonblock.o: $(hdrdir)/ruby/internal/intern/load.h @@ -151,7 +152,6 @@ nonblock.o: $(hdrdir)/ruby/internal/memory.h nonblock.o: $(hdrdir)/ruby/internal/method.h nonblock.o: $(hdrdir)/ruby/internal/module.h nonblock.o: $(hdrdir)/ruby/internal/newobj.h -nonblock.o: $(hdrdir)/ruby/internal/rgengc.h nonblock.o: $(hdrdir)/ruby/internal/scan_args.h nonblock.o: $(hdrdir)/ruby/internal/special_consts.h nonblock.o: $(hdrdir)/ruby/internal/static_assert.h diff --git a/ext/io/nonblock/extconf.rb b/ext/io/nonblock/extconf.rb index d813a01e7c..a1e6075c9b 100644 --- a/ext/io/nonblock/extconf.rb +++ b/ext/io/nonblock/extconf.rb @@ -2,6 +2,13 @@ require 'mkmf' target = "io/nonblock" +unless RUBY_ENGINE == 'ruby' + File.write("Makefile", dummy_makefile($srcdir).join("")) + return +end + +have_func("rb_io_descriptor") + hdr = %w"fcntl.h" if have_macro("O_NONBLOCK", hdr) and (have_macro("F_GETFL", hdr) or have_macro("F_SETFL", hdr)) diff --git a/ext/io/nonblock/io-nonblock.gemspec b/ext/io/nonblock/io-nonblock.gemspec index 34d736650b..6a16c8b03b 100644 --- a/ext/io/nonblock/io-nonblock.gemspec +++ b/ext/io/nonblock/io-nonblock.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "io-nonblock" - spec.version = "0.1.0" + spec.version = "0.3.0" spec.authors = ["Nobu Nakada"] spec.email = ["nobu@ruby-lang.org"] @@ -13,13 +13,13 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - %x[git ls-files -z].split("\x0").reject do |f| - f.match(%r{\A(?:test|spec|features)/|\A\.(?:git|travis)}) - end - end + spec.files = %w[ + COPYING + README.md + ext/io/nonblock/depend + ext/io/nonblock/extconf.rb + ext/io/nonblock/nonblock.c + ] spec.extensions = %w[ext/io/nonblock/extconf.rb] - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] end diff --git a/ext/io/nonblock/nonblock.c b/ext/io/nonblock/nonblock.c index 1c0bdc68e7..d90538f735 100644 --- a/ext/io/nonblock/nonblock.c +++ b/ext/io/nonblock/nonblock.c @@ -17,16 +17,27 @@ #endif #include <fcntl.h> +#ifndef HAVE_RB_IO_DESCRIPTOR +static int +io_descriptor_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->fd; +} +#define rb_io_descriptor io_descriptor_fallback +#endif + #ifdef F_GETFL static int -io_nonblock_mode(int fd) +get_fcntl_flags(int fd) { int f = fcntl(fd, F_GETFL); if (f == -1) rb_sys_fail(0); return f; } #else -#define io_nonblock_mode(fd) ((void)(fd), 0) +#define get_fcntl_flags(fd) ((void)(fd), 0) #endif #ifdef F_GETFL @@ -39,10 +50,8 @@ io_nonblock_mode(int fd) static VALUE rb_io_nonblock_p(VALUE io) { - rb_io_t *fptr; - GetOpenFile(io, fptr); - if (io_nonblock_mode(fptr->fd) & O_NONBLOCK) - return Qtrue; + if (get_fcntl_flags(rb_io_descriptor(io)) & O_NONBLOCK) + return Qtrue; return Qfalse; } #else @@ -50,6 +59,15 @@ rb_io_nonblock_p(VALUE io) #endif #ifdef F_SETFL +static void +set_fcntl_flags(int fd, int f) +{ + if (fcntl(fd, F_SETFL, f) == -1) + rb_sys_fail(0); +} + +#ifndef RUBY_IO_NONBLOCK_METHODS + static int io_nonblock_set(int fd, int f, int nb) { @@ -63,8 +81,7 @@ io_nonblock_set(int fd, int f, int nb) return 0; f &= ~O_NONBLOCK; } - if (fcntl(fd, F_SETFL, f) == -1) - rb_sys_fail(0); + set_fcntl_flags(fd, f); return 1; } @@ -74,32 +91,77 @@ io_nonblock_set(int fd, int f, int nb) * * Enables non-blocking mode on a stream when set to * +true+, and blocking mode when set to +false+. + * + * This method set or clear O_NONBLOCK flag for the file descriptor + * in <em>ios</em>. + * + * The behavior of most IO methods is not affected by this flag + * because they retry system calls to complete their task + * after EAGAIN and partial read/write. + * (An exception is IO#syswrite which doesn't retry.) + * + * This method can be used to clear non-blocking mode of standard I/O. + * Since nonblocking methods (read_nonblock, etc.) set non-blocking mode but + * they doesn't clear it, this method is usable as follows. + * + * END { STDOUT.nonblock = false } + * STDOUT.write_nonblock("foo") + * + * Since the flag is shared across processes and + * many non-Ruby commands doesn't expect standard I/O with non-blocking mode, + * it would be safe to clear the flag before Ruby program exits. + * + * For example following Ruby program leaves STDIN/STDOUT/STDER non-blocking mode. + * (STDIN, STDOUT and STDERR are connected to a terminal. + * So making one of them nonblocking-mode effects other two.) + * Thus cat command try to read from standard input and + * it causes "Resource temporarily unavailable" error (EAGAIN). + * + * % ruby -e ' + * STDOUT.write_nonblock("foo\n")'; cat + * foo + * cat: -: Resource temporarily unavailable + * + * Clearing the flag makes the behavior of cat command normal. + * (cat command waits input from standard input.) + * + * % ruby -rio/nonblock -e ' + * END { STDOUT.nonblock = false } + * STDOUT.write_nonblock("foo") + * '; cat + * foo + * */ static VALUE -rb_io_nonblock_set(VALUE io, VALUE nb) +rb_io_nonblock_set(VALUE self, VALUE value) { - rb_io_t *fptr; - GetOpenFile(io, fptr); - if (RTEST(nb)) - rb_io_set_nonblock(fptr); - else - io_nonblock_set(fptr->fd, io_nonblock_mode(fptr->fd), RTEST(nb)); - return io; + if (RTEST(value)) { + rb_io_t *fptr; + GetOpenFile(self, fptr); + rb_io_set_nonblock(fptr); + } + else { + int descriptor = rb_io_descriptor(self); + io_nonblock_set(descriptor, get_fcntl_flags(descriptor), RTEST(value)); + } + + return self; } +#endif /* RUBY_IO_NONBLOCK_METHODS */ + static VALUE io_nonblock_restore(VALUE arg) { int *restore = (int *)arg; - if (fcntl(restore[0], F_SETFL, restore[1]) == -1) - rb_sys_fail(0); + set_fcntl_flags(restore[0], restore[1]); return Qnil; } /* * call-seq: - * io.nonblock {|io| } -> io - * io.nonblock(boolean) {|io| } -> io + * io.nonblock {|io| } -> object + * io.nonblock(boolean) {|io| } -> object * * Yields +self+ in non-blocking mode. * @@ -107,24 +169,25 @@ io_nonblock_restore(VALUE arg) * The original mode is restored after the block is executed. */ static VALUE -rb_io_nonblock_block(int argc, VALUE *argv, VALUE io) +rb_io_nonblock_block(int argc, VALUE *argv, VALUE self) { int nb = 1; - rb_io_t *fptr; - int f, restore[2]; - GetOpenFile(io, fptr); + int descriptor = rb_io_descriptor(self); + if (argc > 0) { - VALUE v; - rb_scan_args(argc, argv, "01", &v); - nb = RTEST(v); + VALUE v; + rb_scan_args(argc, argv, "01", &v); + nb = RTEST(v); } - f = io_nonblock_mode(fptr->fd); - restore[0] = fptr->fd; - restore[1] = f; - if (!io_nonblock_set(fptr->fd, f, nb)) - return rb_yield(io); - return rb_ensure(rb_yield, io, io_nonblock_restore, (VALUE)restore); + + int current_flags = get_fcntl_flags(descriptor); + int restore[2] = {descriptor, current_flags}; + + if (!io_nonblock_set(descriptor, current_flags, nb)) + return rb_yield(self); + + return rb_ensure(rb_yield, self, io_nonblock_restore, (VALUE)restore); } #else #define rb_io_nonblock_set rb_f_notimplement @@ -134,7 +197,10 @@ rb_io_nonblock_block(int argc, VALUE *argv, VALUE io) void Init_nonblock(void) { +#ifndef RUBY_IO_NONBLOCK_METHODS rb_define_method(rb_cIO, "nonblock?", rb_io_nonblock_p, 0); rb_define_method(rb_cIO, "nonblock=", rb_io_nonblock_set, 1); +#endif + rb_define_method(rb_cIO, "nonblock", rb_io_nonblock_block, -1); } diff --git a/ext/io/wait/depend b/ext/io/wait/depend index 0426a6a1ed..83cf8f94c8 100644 --- a/ext/io/wait/depend +++ b/ext/io/wait/depend @@ -16,6 +16,7 @@ wait.o: $(hdrdir)/ruby/backward/2/stdarg.h wait.o: $(hdrdir)/ruby/defines.h wait.o: $(hdrdir)/ruby/encoding.h wait.o: $(hdrdir)/ruby/intern.h +wait.o: $(hdrdir)/ruby/internal/abi.h wait.o: $(hdrdir)/ruby/internal/anyargs.h wait.o: $(hdrdir)/ruby/internal/arithmetic.h wait.o: $(hdrdir)/ruby/internal/arithmetic/char.h @@ -53,6 +54,7 @@ wait.o: $(hdrdir)/ruby/internal/attr/noexcept.h wait.o: $(hdrdir)/ruby/internal/attr/noinline.h wait.o: $(hdrdir)/ruby/internal/attr/nonnull.h wait.o: $(hdrdir)/ruby/internal/attr/noreturn.h +wait.o: $(hdrdir)/ruby/internal/attr/packed_struct.h wait.o: $(hdrdir)/ruby/internal/attr/pure.h wait.o: $(hdrdir)/ruby/internal/attr/restrict.h wait.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h @@ -121,7 +123,6 @@ wait.o: $(hdrdir)/ruby/internal/intern/enumerator.h wait.o: $(hdrdir)/ruby/internal/intern/error.h wait.o: $(hdrdir)/ruby/internal/intern/eval.h wait.o: $(hdrdir)/ruby/internal/intern/file.h -wait.o: $(hdrdir)/ruby/internal/intern/gc.h wait.o: $(hdrdir)/ruby/internal/intern/hash.h wait.o: $(hdrdir)/ruby/internal/intern/io.h wait.o: $(hdrdir)/ruby/internal/intern/load.h @@ -152,7 +153,6 @@ wait.o: $(hdrdir)/ruby/internal/memory.h wait.o: $(hdrdir)/ruby/internal/method.h wait.o: $(hdrdir)/ruby/internal/module.h wait.o: $(hdrdir)/ruby/internal/newobj.h -wait.o: $(hdrdir)/ruby/internal/rgengc.h wait.o: $(hdrdir)/ruby/internal/scan_args.h wait.o: $(hdrdir)/ruby/internal/special_consts.h wait.o: $(hdrdir)/ruby/internal/static_assert.h diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index d20ff4553f..e63c046187 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -1,20 +1,25 @@ # frozen_string_literal: false require 'mkmf' -target = "io/wait" -have_func("rb_io_wait") -unless macro_defined?("DOSISH", "#include <ruby.h>") - have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil - fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| - have_macro("FIONREAD", [h, ioctl_h].compact) - end - if fionread - $defs << "-DFIONREAD_HEADER=\"<#{fionread}>\"" - create_makefile(target) - end +if RUBY_VERSION < "2.6" + File.write("Makefile", dummy_makefile($srcdir).join("")) else - if have_func("rb_w32_ioctlsocket", "ruby.h") - have_func("rb_w32_is_socket", "ruby.h") - create_makefile(target) + target = "io/wait" + have_func("rb_io_wait") + have_func("rb_io_descriptor") + unless macro_defined?("DOSISH", "#include <ruby.h>") + have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil + fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| + have_macro("FIONREAD", [h, ioctl_h].compact) + end + if fionread + $defs << "-DFIONREAD_HEADER=\"<#{fionread}>\"" + create_makefile(target) + end + else + if have_func("rb_w32_ioctlsocket", "ruby.h") + have_func("rb_w32_is_socket", "ruby.h") + create_makefile(target) + end end end diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 5150f14848..e850e10bf9 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,27 +1,38 @@ -_VERSION = "0.2.0" +_VERSION = "0.3.1" Gem::Specification.new do |spec| spec.name = "io-wait" spec.version = _VERSION - spec.authors = ["Nobu Nakada"] - spec.email = ["nobu@ruby-lang.org"] + spec.authors = ["Nobu Nakada", "Charles Oliver Nutter"] + spec.email = ["nobu@ruby-lang.org", "headius@headius.com"] spec.summary = %q{Waits until IO is readable or writable without blocking.} spec.description = %q{Waits until IO is readable or writable without blocking.} spec.homepage = "https://github.com/ruby/io-wait" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject do |f| - f.match(%r{\A(?:test|spec|features)/|\A\.(?:git|travis)}) + File.identical?(f, __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) end end - spec.extensions = %w[ext/io/wait/extconf.rb] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] + + jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby' + spec.files.delete_if do |f| + f.end_with?(".java") or + f.start_with?("ext/") && (jruby ^ f.start_with?("ext/java/")) + end + if jruby + spec.platform = 'java' + spec.files << "lib/io/wait.jar" + spec.require_paths += ["ext/java/lib"] + else + spec.extensions = %w[ext/io/wait/extconf.rb] + end end diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c index 8f0d16e168..8835670e59 100644 --- a/ext/io/wait/wait.c +++ b/ext/io/wait/wait.c @@ -41,22 +41,17 @@ #endif #ifndef HAVE_RB_IO_WAIT -static VALUE io_ready_p _((VALUE io)); -static VALUE io_wait_readable _((int argc, VALUE *argv, VALUE io)); -static VALUE io_wait_writable _((int argc, VALUE *argv, VALUE io)); -void Init_wait _((void)); - static struct timeval * get_timeout(int argc, VALUE *argv, struct timeval *timerec) { VALUE timeout = Qnil; rb_check_arity(argc, 0, 1); if (!argc || NIL_P(timeout = argv[0])) { - return NULL; + return NULL; } else { - *timerec = rb_time_interval(timeout); - return timerec; + *timerec = rb_time_interval(timeout); + return timerec; } } @@ -65,7 +60,7 @@ wait_for_single_fd(rb_io_t *fptr, int events, struct timeval *tv) { int i = rb_wait_for_single_fd(fptr->fd, events, tv); if (i < 0) - rb_sys_fail(0); + rb_sys_fail(0); rb_io_check_closed(fptr); return (i & events); } @@ -77,6 +72,8 @@ wait_for_single_fd(rb_io_t *fptr, int events, struct timeval *tv) * * Returns number of bytes that can be read without blocking. * Returns zero if no information available. + * + * You must require 'io/wait' to use this method. */ static VALUE @@ -90,38 +87,51 @@ io_nread(VALUE io) rb_io_check_readable(fptr); len = rb_io_read_pending(fptr); if (len > 0) return INT2FIX(len); - if (!FIONREAD_POSSIBLE_P(fptr->fd)) return INT2FIX(0); - if (ioctl(fptr->fd, FIONREAD, &n)) return INT2FIX(0); + +#ifdef HAVE_RB_IO_DESCRIPTOR + int fd = rb_io_descriptor(io); +#else + int fd = fptr->fd; +#endif + + if (!FIONREAD_POSSIBLE_P(fd)) return INT2FIX(0); + if (ioctl(fd, FIONREAD, &n)) return INT2FIX(0); if (n > 0) return ioctl_arg2num(n); return INT2FIX(0); } #ifdef HAVE_RB_IO_WAIT static VALUE -io_wait_event(VALUE io, int event, VALUE timeout) +io_wait_event(VALUE io, int event, VALUE timeout, int return_io) { VALUE result = rb_io_wait(io, RB_INT2NUM(event), timeout); if (!RB_TEST(result)) { - return Qnil; + return Qnil; } int mask = RB_NUM2INT(result); if (mask & event) { - return io; + if (return_io) + return io; + else + return result; } else { - return Qfalse; + return Qfalse; } } #endif /* * call-seq: - * io.ready? -> true or false + * io.ready? -> truthy or falsy * - * Returns +true+ if input available without blocking, or +false+. + * Returns a truthy value if input available without blocking, or a + * falsy value. + * + * You must require 'io/wait' to use this method. */ static VALUE @@ -137,23 +147,25 @@ io_ready_p(VALUE io) if (rb_io_read_pending(fptr)) return Qtrue; #ifndef HAVE_RB_IO_WAIT - if (wait_for_single_fd(fptr, RB_WAITFD_IN, &tv)) - return Qtrue; + return wait_for_single_fd(fptr, RB_WAITFD_IN, &tv) ? Qtrue : Qfalse; #else - if (RTEST(io_wait_event(io, RUBY_IO_READABLE, RB_INT2NUM(0)))) - return Qtrue; + return io_wait_event(io, RUBY_IO_READABLE, RB_INT2NUM(0), 1); #endif - return Qfalse; } +/* Ruby 3.2+ can define these methods. This macro indicates that case. */ +#ifndef RUBY_IO_WAIT_METHODS + /* * call-seq: - * io.wait_readable -> true or false - * io.wait_readable(timeout) -> true or false + * io.wait_readable -> truthy or falsy + * io.wait_readable(timeout) -> truthy or falsy + * + * Waits until IO is readable and returns a truthy value, or a falsy + * value when times out. Returns a truthy value immediately when + * buffered data is available. * - * Waits until IO is readable and returns +true+, or - * +false+ when times out. - * Returns +true+ immediately when buffered data is available. + * You must require 'io/wait' to use this method. */ static VALUE @@ -175,24 +187,26 @@ io_wait_readable(int argc, VALUE *argv, VALUE io) #ifndef HAVE_RB_IO_WAIT if (wait_for_single_fd(fptr, RB_WAITFD_IN, tv)) { - return io; + return io; } return Qnil; #else rb_check_arity(argc, 0, 1); VALUE timeout = (argc == 1 ? argv[0] : Qnil); - return io_wait_event(io, RUBY_IO_READABLE, timeout); + return io_wait_event(io, RUBY_IO_READABLE, timeout, 1); #endif } /* * call-seq: - * io.wait_writable -> true or false - * io.wait_writable(timeout) -> true or false + * io.wait_writable -> truthy or falsy + * io.wait_writable(timeout) -> truthy or falsy * - * Waits until IO is writable and returns +true+ or - * +false+ when times out. + * Waits until IO is writable and returns a truthy value or a falsy + * value when times out. + * + * You must require 'io/wait' to use this method. */ static VALUE io_wait_writable(int argc, VALUE *argv, VALUE io) @@ -209,25 +223,28 @@ io_wait_writable(int argc, VALUE *argv, VALUE io) #ifndef HAVE_RB_IO_WAIT tv = get_timeout(argc, argv, &timerec); if (wait_for_single_fd(fptr, RB_WAITFD_OUT, tv)) { - return io; + return io; } return Qnil; #else rb_check_arity(argc, 0, 1); VALUE timeout = (argc == 1 ? argv[0] : Qnil); - return io_wait_event(io, RUBY_IO_WRITABLE, timeout); + return io_wait_event(io, RUBY_IO_WRITABLE, timeout, 1); #endif } #ifdef HAVE_RB_IO_WAIT /* * call-seq: - * io.wait_priority -> true or false - * io.wait_priority(timeout) -> true or false + * io.wait_priority -> truthy or falsy + * io.wait_priority(timeout) -> truthy or falsy + * + * Waits until IO is priority and returns a truthy value or a falsy + * value when times out. Priority data is sent and received using + * the Socket::MSG_OOB flag and is typically limited to streams. * - * Waits until IO is priority and returns +true+ or - * +false+ when times out. + * You must require 'io/wait' to use this method. */ static VALUE io_wait_priority(int argc, VALUE *argv, VALUE io) @@ -242,7 +259,7 @@ io_wait_priority(int argc, VALUE *argv, VALUE io) rb_check_arity(argc, 0, 1); VALUE timeout = argc == 1 ? argv[0] : Qnil; - return io_wait_event(io, RUBY_IO_PRIORITY, timeout); + return io_wait_event(io, RUBY_IO_PRIORITY, timeout, 1); } #endif @@ -250,51 +267,65 @@ static int wait_mode_sym(VALUE mode) { if (mode == ID2SYM(rb_intern("r"))) { - return RB_WAITFD_IN; + return RB_WAITFD_IN; } if (mode == ID2SYM(rb_intern("read"))) { - return RB_WAITFD_IN; + return RB_WAITFD_IN; } if (mode == ID2SYM(rb_intern("readable"))) { - return RB_WAITFD_IN; + return RB_WAITFD_IN; } if (mode == ID2SYM(rb_intern("w"))) { - return RB_WAITFD_OUT; + return RB_WAITFD_OUT; } if (mode == ID2SYM(rb_intern("write"))) { - return RB_WAITFD_OUT; + return RB_WAITFD_OUT; } if (mode == ID2SYM(rb_intern("writable"))) { - return RB_WAITFD_OUT; + return RB_WAITFD_OUT; } if (mode == ID2SYM(rb_intern("rw"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; + return RB_WAITFD_IN|RB_WAITFD_OUT; } if (mode == ID2SYM(rb_intern("read_write"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; + return RB_WAITFD_IN|RB_WAITFD_OUT; } if (mode == ID2SYM(rb_intern("readable_writable"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; + return RB_WAITFD_IN|RB_WAITFD_OUT; } rb_raise(rb_eArgError, "unsupported mode: %"PRIsVALUE, mode); return 0; } +#ifdef HAVE_RB_IO_WAIT +static inline rb_io_event_t +io_event_from_value(VALUE value) +{ + int events = RB_NUM2INT(value); + + if (events <= 0) rb_raise(rb_eArgError, "Events must be positive integer!"); + + return events; +} +#endif + /* * call-seq: - * io.wait(events, timeout) -> event mask or false. - * io.wait(timeout = nil, mode = :read) -> event mask or false. + * io.wait(events, timeout) -> event mask, false or nil + * io.wait(timeout = nil, mode = :read) -> self, true, or false * * Waits until the IO becomes ready for the specified events and returns the - * subset of events that become ready, or +false+ when times out. + * subset of events that become ready, or a falsy value when times out. * * The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or * +IO::PRIORITY+. * - * Returns +true+ immediately when buffered data is available. + * Returns a truthy value immediately when buffered data is available. * * Optional parameter +mode+ is one of +:read+, +:write+, or * +:read_write+. + * + * You must require 'io/wait' to use this method. */ static VALUE @@ -309,61 +340,77 @@ io_wait(int argc, VALUE *argv, VALUE io) GetOpenFile(io, fptr); for (i = 0; i < argc; ++i) { - if (SYMBOL_P(argv[i])) { - event |= wait_mode_sym(argv[i]); - } - else { - *(tv = &timerec) = rb_time_interval(argv[i]); - } + if (SYMBOL_P(argv[i])) { + event |= wait_mode_sym(argv[i]); + } + else { + *(tv = &timerec) = rb_time_interval(argv[i]); + } } /* rb_time_interval() and might_mode() might convert the argument */ rb_io_check_closed(fptr); if (!event) event = RB_WAITFD_IN; if ((event & RB_WAITFD_IN) && rb_io_read_pending(fptr)) - return Qtrue; + return Qtrue; if (wait_for_single_fd(fptr, event, tv)) - return io; + return io; return Qnil; #else VALUE timeout = Qundef; rb_io_event_t events = 0; + int i, return_io = 0; + /* The documented signature for this method is actually incorrect. + * A single timeout is allowed in any position, and multiple symbols can be given. + * Whether this is intentional or not, I don't know, and as such I consider this to + * be a legacy/slow path. */ if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) { - for (int i = 0; i < argc; i += 1) { - if (RB_SYMBOL_P(argv[i])) { - events |= wait_mode_sym(argv[i]); - } - else if (timeout == Qundef) { - rb_time_interval(timeout = argv[i]); - } - else { - rb_raise(rb_eArgError, "timeout given more than once"); - } - } - if (timeout == Qundef) timeout = Qnil; - } - else /* argc == 2 */ { - events = RB_NUM2UINT(argv[0]); - timeout = argv[1]; + /* We'd prefer to return the actual mask, but this form would return the io itself: */ + return_io = 1; + + /* Slow/messy path: */ + for (i = 0; i < argc; i += 1) { + if (RB_SYMBOL_P(argv[i])) { + events |= wait_mode_sym(argv[i]); + } + else if (timeout == Qundef) { + rb_time_interval(timeout = argv[i]); + } + else { + rb_raise(rb_eArgError, "timeout given more than once"); + } + } + + if (timeout == Qundef) timeout = Qnil; + + if (events == 0) { + events = RUBY_IO_READABLE; + } } - - if (events == 0) { - events = RUBY_IO_READABLE; + else /* argc == 2 and neither are symbols */ { + /* This is the fast path: */ + events = io_event_from_value(argv[0]); + timeout = argv[1]; } if (events & RUBY_IO_READABLE) { - rb_io_t *fptr = NULL; - RB_IO_POINTER(io, fptr); - - if (rb_io_read_pending(fptr)) { - return Qtrue; - } + rb_io_t *fptr = NULL; + RB_IO_POINTER(io, fptr); + + if (rb_io_read_pending(fptr)) { + /* This was the original behaviour: */ + if (return_io) return Qtrue; + /* New behaviour always returns an event mask: */ + else return RB_INT2NUM(RUBY_IO_READABLE); + } } - return io_wait_event(io, events, timeout); + return io_wait_event(io, events, timeout, return_io); #endif } +#endif /* RUBY_IO_WAIT_METHODS */ + /* * IO wait methods */ @@ -378,6 +425,7 @@ Init_wait(void) rb_define_method(rb_cIO, "nread", io_nread, 0); rb_define_method(rb_cIO, "ready?", io_ready_p, 0); +#ifndef RUBY_IO_WAIT_METHODS rb_define_method(rb_cIO, "wait", io_wait, -1); rb_define_method(rb_cIO, "wait_readable", io_wait_readable, -1); @@ -385,4 +433,5 @@ Init_wait(void) #ifdef HAVE_RB_IO_WAIT rb_define_method(rb_cIO, "wait_priority", io_wait_priority, -1); #endif +#endif } |