Skip to content

Commit 8ec58a9

Browse files
nobumatzbot
authored andcommitted
[ruby/io-console] Add IO#ttyname that returns the tty name or nil
ruby/io-console@fdad351501
1 parent 1090070 commit 8ec58a9

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

ext/io/console/console.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ getattr(int fd, conmode *t)
8484
static ID id_getc, id_close;
8585
static ID id_gets, id_flush, id_chomp_bang;
8686

87+
#ifndef HAVE_RB_INTERNED_STR_CSTR
88+
# define rb_str_to_interned_str(str) rb_str_freeze(str)
89+
# define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str))
90+
#endif
91+
8792
#if defined HAVE_RUBY_FIBER_SCHEDULER_H
8893
# include "ruby/fiber/scheduler.h"
8994
#elif defined HAVE_RB_SCHEDULER_TIMEOUT
@@ -1818,6 +1823,61 @@ io_getpass(int argc, VALUE *argv, VALUE io)
18181823
return str_chomp(str);
18191824
}
18201825

1826+
#if defined(_WIN32) || defined(HAVE_TTYNAME_R) || defined(HAVE_TTYNAME)
1827+
/*
1828+
* call-seq:
1829+
* io.ttyname -> string or nil
1830+
*
1831+
* Returns name of associated terminal (tty) if +io+ is not a tty.
1832+
* Returns +nil+ otherwise.
1833+
*/
1834+
static VALUE
1835+
console_ttyname(VALUE io)
1836+
{
1837+
int fd = rb_io_descriptor(io);
1838+
if (!isatty(fd)) return Qnil;
1839+
# if defined _WIN32
1840+
return rb_usascii_str_new_lit("con");
1841+
# elif defined HAVE_TTYNAME_R
1842+
{
1843+
char termname[1024], *tn = termname;
1844+
size_t size = sizeof(termname);
1845+
int e;
1846+
if (ttyname_r(fd, tn, size) == 0)
1847+
return rb_interned_str_cstr(tn);
1848+
if ((e = errno) == ERANGE) {
1849+
VALUE s = rb_str_new(0, size);
1850+
while (1) {
1851+
tn = RSTRING_PTR(s);
1852+
size = rb_str_capacity(s);
1853+
if (ttyname_r(fd, tn, size) == 0) {
1854+
return rb_str_to_interned_str(rb_str_resize(s, strlen(tn)));
1855+
}
1856+
if ((e = errno) != ERANGE) break;
1857+
if ((size *= 2) >= INT_MAX/2) break;
1858+
rb_str_resize(s, size);
1859+
}
1860+
}
1861+
rb_syserr_fail_str(e, rb_sprintf("ttyname_r(%d)", fd));
1862+
UNREACHABLE_RETURN(Qnil);
1863+
}
1864+
# elif defined HAVE_TTYNAME
1865+
{
1866+
const char *tn = ttyname(fd);
1867+
if (!tn) {
1868+
int e = errno;
1869+
rb_syserr_fail_str(e, rb_sprintf("ttyname(%d)", fd));
1870+
}
1871+
return rb_interned_str_cstr(tn);
1872+
}
1873+
# else
1874+
# error No ttyname function
1875+
# endif
1876+
}
1877+
#else
1878+
# define console_ttyname rb_f_notimplement
1879+
#endif
1880+
18211881
/*
18221882
* IO console methods
18231883
*/
@@ -1885,6 +1945,7 @@ InitVM_console(void)
18851945
rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
18861946
rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
18871947
rb_define_method(rb_cIO, "getpass", console_getpass, -1);
1948+
rb_define_method(rb_cIO, "ttyname", console_ttyname, 0);
18881949
rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
18891950
{
18901951
/* :stopdoc: */

ext/io/console/extconf.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
have_func("rb_syserr_new_str(0, Qnil)") or
1010
abort
1111

12+
have_func("rb_interned_str_cstr")
1213
have_func("rb_io_path")
1314
have_func("rb_io_descriptor")
1415
have_func("rb_io_get_write_io")
@@ -51,6 +52,7 @@
5152
elsif have_func("rb_scheduler_timeout") # 3.0
5253
have_func("rb_io_wait")
5354
end
55+
have_func("ttyname_r") or have_func("ttyname")
5456
create_makefile("io/console") {|conf|
5557
conf << "\n""VK_HEADER = #{vk_header}\n"
5658
}

test/io/console/test_io_console.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,11 @@ def test_console_kw
441441
def test_sync
442442
assert_equal(["true"], run_pty("p IO.console.sync"))
443443
end
444+
445+
def test_ttyname
446+
return unless IO.method_defined?(:ttyname)
447+
assert_equal(["true"], run_pty("p STDIN.ttyname == STDOUT.ttyname"))
448+
end
444449
end
445450

446451
private
@@ -531,6 +536,13 @@ def test_sync
531536
def test_getch_timeout
532537
assert_nil(IO.console.getch(intr: true, time: 0.1, min: 0))
533538
end
539+
540+
def test_ttyname
541+
return unless IO.method_defined?(:ttyname)
542+
ttyname = IO.console.ttyname
543+
assert_not_nil(ttyname)
544+
File.open(ttyname) {|f| assert_predicate(f, :tty?)}
545+
end
534546
end
535547
end
536548

@@ -546,7 +558,7 @@ def test_getch_timeout
546558
if noctty
547559
require 'tempfile'
548560
NOCTTY = noctty
549-
def test_noctty
561+
def run_noctty(src)
550562
t = Tempfile.new("noctty_out")
551563
t.close
552564
t2 = Tempfile.new("noctty_run")
@@ -557,7 +569,7 @@ def test_noctty
557569
'-e', 'STDOUT.reopen(f)',
558570
'-e', 'STDERR.reopen(f)',
559571
'-e', 'require "io/console"',
560-
'-e', 'f.puts IO.console.inspect',
572+
'-e', "f.puts (#{src}).inspect",
561573
'-e', 'f.flush',
562574
'-e', 'File.unlink(ARGV[1])',
563575
'-e', '}',
@@ -568,11 +580,18 @@ def test_noctty
568580
sleep 0.1
569581
end
570582
t.open
571-
assert_equal("nil", t.gets(nil).chomp)
583+
t.gets.lines(chomp: true)
572584
ensure
573585
t.close! if t and !t.closed?
574586
t2.close!
575587
end
588+
589+
def test_noctty
590+
assert_equal(["nil"], run_noctty("IO.console"))
591+
if IO.method_defined?(:ttyname)
592+
assert_equal(["nil"], run_noctty("STDIN.ttyname rescue $!"))
593+
end
594+
end
576595
end
577596
end
578597

0 commit comments

Comments
 (0)