From 524936eea0f6111c9da756e5043676c03d17dd19 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 6 Jan 2020 14:42:58 -0600 Subject: [PATCH] Add support for JRuby. This fixes #10. Whenever a gem is pushed for the default platform a gem must be pushed for the 'java' platform to avoid breaking JRuby users. --- io-console.gemspec | 26 +++- lib/io/console.rb | 7 + lib/io/console/jruby.rb | 67 +++++++++ lib/io/console/jruby/bsd_console.rb | 166 ++++++++++++++++++++ lib/io/console/jruby/common.rb | 35 +++++ lib/io/console/jruby/linux_console.rb | 200 +++++++++++++++++++++++++ lib/io/console/jruby/native_console.rb | 153 +++++++++++++++++++ lib/io/console/jruby/stty_console.rb | 82 ++++++++++ lib/io/console/jruby/stub_console.rb | 45 ++++++ 9 files changed, 777 insertions(+), 4 deletions(-) create mode 100644 lib/io/console.rb create mode 100644 lib/io/console/jruby.rb create mode 100644 lib/io/console/jruby/bsd_console.rb create mode 100644 lib/io/console/jruby/common.rb create mode 100644 lib/io/console/jruby/linux_console.rb create mode 100644 lib/io/console/jruby/native_console.rb create mode 100644 lib/io/console/jruby/stty_console.rb create mode 100644 lib/io/console/jruby/stub_console.rb diff --git a/io-console.gemspec b/io-console.gemspec index 0c9b854..d3f7f0c 100644 --- a/io-console.gemspec +++ b/io-console.gemspec @@ -15,11 +15,29 @@ Gem::Specification.new do |s| s.files = %w[ LICENSE.txt README.md - ext/io/console/console.c - ext/io/console/extconf.rb - ext/io/console/win32_vk.inc + lib/io/console.rb lib/io/console/size.rb ] - s.extensions = %w[ext/io/console/extconf.rb] + + if RUBY_ENGINE == 'jruby' + s.platform = 'java' + s.files += %w[ + lib/io/console/jruby.rb + lib/io/console/jruby/bsd_console.rb + lib/io/console/jruby/common.rb + lib/io/console/jruby/linux_console.rb + lib/io/console/jruby/native_console.rb + lib/io/console/jruby/stty_console.rb + lib/io/console/jruby/stub_console.rb + ] + else + s.files += %w[ + ext/io/console/console.c + ext/io/console/extconf.rb + ext/io/console/win32_vk.inc + ] + s.extensions = %w[ext/io/console/extconf.rb] + end + s.license = "BSD-2-Clause" end diff --git a/lib/io/console.rb b/lib/io/console.rb new file mode 100644 index 0000000..14452a4 --- /dev/null +++ b/lib/io/console.rb @@ -0,0 +1,7 @@ +# Choose io/console implementation appropriate for RUBY_ENGINE + +if RUBY_ENGINE == 'jruby' + require 'io/console/jruby' +else + require 'io/console.so' +end \ No newline at end of file diff --git a/lib/io/console/jruby.rb b/lib/io/console/jruby.rb new file mode 100644 index 0000000..d6dfe1e --- /dev/null +++ b/lib/io/console/jruby.rb @@ -0,0 +1,67 @@ +# This implementation of io/console is a little hacky. It shells out to `stty` +# for most operations, which does not work on Windows, in secured environments, +# and so on. In addition, because on Java 6 we can't actually launch +# subprocesses with tty control, stty will not actually manipulate the +# controlling terminal. +# +# For platforms where shelling to stty does not work, most operations will +# just be pass-throughs. This allows them to function, but does not actually +# change any tty flags. +# +# Finally, since we're using stty to shell out, we can only manipulate stdin/ +# stdout tty rather than manipulating whatever terminal is actually associated +# with the IO we're calling against. This will produce surprising results if +# anyone is actually using io/console against non-stdio ttys...but that case +# seems like it would be pretty rare. +# +# Note: we are incorporating this into 1.7.0 since RubyGems uses io/console +# when pushing gems, in order to mask the password entry. Worst case is that +# we don't actually disable echo and the password is shown...we will try to +# do a better version of this in 1.7.1. + +require 'rbconfig' + +require_relative 'jruby/common' + +# If Windows, always use the stub version +if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/ + require_relative 'jruby/stub_console' +else + + # If Linux or BSD, try to load the native version + if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/ + begin + + # Attempt to load the native Linux and BSD console logic + require_relative 'jruby/native_console' + ready = true + + rescue Exception => ex + + warn "failed to load native console support: #{ex}" if $VERBOSE + ready = false + + end + end + + # Native failed, try to use stty + if !ready + begin + + require_relative 'jruby/stty_console' + ready = true + + rescue Exception + + warn "failed to load stty console support: #{ex}" if $VERBOSE + ready = false + + end + end + + # If still not ready, just use stubbed version + if !ready + require_relative 'jruby/stub_console' + end + +end diff --git a/lib/io/console/jruby/bsd_console.rb b/lib/io/console/jruby/bsd_console.rb new file mode 100644 index 0000000..d32804c --- /dev/null +++ b/lib/io/console/jruby/bsd_console.rb @@ -0,0 +1,166 @@ +require 'ffi' + +module IO::LibC + extend FFI::Library + ffi_lib FFI::Library::LIBC + + if RbConfig::CONFIG['host_os'].downcase =~ /darwin/ + typedef :ulong, :tcflag_t + typedef :ulong, :speed_t + else + typedef :uint, :tcflag_t + typedef :uint, :speed_t + end + + # Special Control Characters + VEOF = 0 # ICANON + VEOL = 1 # ICANON + VEOL2 = 2 # ICANON together with IEXTEN + VERASE = 3 # ICANON + VWERASE = 4 # ICANON together with IEXTEN + VKILL = 5 # ICANON + VREPRINT = 6 # ICANON together with IEXTEN + VINTR = 8 # ISIG + VQUIT = 9 # ISIG + VSUSP = 10 # ISIG + VDSUSP = 11 # ISIG together with IEXTEN + VSTART = 12 # IXON, IXOFF + VSTOP = 13 # IXON, IXOFF + VLNEXT = 14 # IEXTEN + VDISCARD = 15 # IEXTEN + VMIN = 16 # !ICANON + VTIME = 17 # !ICANON + VSTATUS = 18 # ICANON together with IEXTEN + NCCS = 20 + + # Input flags - software input processing + IGNBRK = 0x00000001 # ignore BREAK condition + BRKINT = 0x00000002 # map BREAK to SIGINTR + IGNPAR = 0x00000004 # ignore (discard) parity errors + PARMRK = 0x00000008 # mark parity and framing errors + INPCK = 0x00000010 # enable checking of parity errors + ISTRIP = 0x00000020 # strip 8th bit off chars + INLCR = 0x00000040 # map NL into CR + IGNCR = 0x00000080 # ignore CR + ICRNL = 0x00000100 # map CR to NL (ala CRMOD) + IXON = 0x00000200 # enable output flow control + IXOFF = 0x00000400 # enable input flow control + IXANY = 0x00000800 # any char will restart after stop + IMAXBEL = 0x00002000 # ring bell on input queue full + IUTF8 = 0x00004000 # maintain state for UTF-8 VERASE + + # Output flags - software output processing + OPOST = 0x00000001 # enable following output processing + ONLCR = 0x00000002 # map NL to CR-NL (ala CRMOD) + OXTABS = 0x00000004 # expand tabs to spaces + ONOEOT = 0x00000008 # discard EOT's (^D) on output) + OCRNL = 0x00000010 # map CR to NL on output + ONOCR = 0x00000020 # no CR output at column 0 + ONLRET = 0x00000040 # NL performs CR function + + # Control flags - hardware control of terminal + CIGNORE = 0x00000001 # ignore control flags + CSIZE = 0x00000300 # character size mask + CS5 = 0x00000000 # 5 bits (pseudo) + CS6 = 0x00000100 # 6 bits + CS7 = 0x00000200 # 7 bits + CS8 = 0x00000300 # 8 bits + CSTOPB = 0x00000400 # send 2 stop bits + CREAD = 0x00000800 # enable receiver + PARENB = 0x00001000 # parity enable + PARODD = 0x00002000 # odd parity, else even + HUPCL = 0x00004000 # hang up on last close + CLOCAL = 0x00008000 # ignore modem status lines + CCTS_OFLOW = 0x00010000 # CTS flow control of output + CRTS_IFLOW = 0x00020000 # RTS flow control of input + CDTR_IFLOW = 0x00040000 # DTR flow control of input + CDSR_OFLOW = 0x00080000 # DSR flow control of output + CCAR_OFLOW = 0x00100000 # DCD flow control of output + CRTSCTS = CCTS_OFLOW | CRTS_IFLOW + MDMBUF = 0x00100000 # old name for CCAR_OFLOW + + + # "Local" flags - dumping ground for other state + ECHOKE = 0x00000001 # visual erase for line kill + ECHOE = 0x00000002 # visually erase chars + ECHOK = 0x00000004 # echo NL after line kill + ECHO = 0x00000008 # enable echoing + ECHONL = 0x00000010 # echo NL even if ECHO is off + ECHOPRT = 0x00000020 # visual erase mode for hardcopy + ECHOCTL = 0x00000040 # echo control chars as ^(Char) + ISIG = 0x00000080 # enable signals INTR, QUIT, [D]SUSP + ICANON = 0x00000100 # canonicalize input lines + ALTWERASE = 0x00000200 # use alternate WERASE algorithm + IEXTEN = 0x00000400 # enable DISCARD and LNEXT + EXTPROC = 0x00000800 # external processing + TOSTOP = 0x00400000 # stop background jobs from output + FLUSHO = 0x00800000 # output being flushed (state) + NOKERNINFO = 0x02000000 # no kernel output from VSTATUS + PENDIN = 0x20000000 # XXX retype pending input (state) + NOFLSH = 0x80000000 # don't flush after interrupt + + + # Commands passed to tcsetattr() for setting the termios structure. + TCSANOW = 0 # make change immediate + TCSADRAIN = 1 # drain output, then change + TCSAFLUSH = 2 # drain output, flush input + TCSASOFT = 0x10 # flag - don't alter h.w. state + + + TCIFLUSH = 1 + TCOFLUSH = 2 + TCIOFLUSH = 3 + TCOOFF = 1 + TCOON = 2 + TCIOFF = 3 + TCION = 4 + + IOCPARM_MASK = 0x1fff + IOC_OUT = 0x40000000 + IOC_IN = 0x80000000 + + def self._IOC(inout,group,num,len) + inout | ((len & IOCPARM_MASK) << 16) | ((group.ord << 8) | num) + end + + def self._IOR(g,n,t) + self._IOC(IOC_OUT, g, n, find_type(t).size) + end + + def self._IOW(g,n,t) + self._IOC(IOC_IN, g, n, find_type(t).size) + end + + + class Termios < FFI::Struct + layout \ + :c_iflag, :tcflag_t, + :c_oflag, :tcflag_t, + :c_cflag, :tcflag_t, + :c_lflag, :tcflag_t, + :cc_c, [ :uchar, NCCS ], + :c_ispeed, :speed_t, + :c_ospeed, :speed_t + end + + class Winsize < FFI::Struct + layout \ + :ws_row, :ushort, + :ws_col, :ushort, + :ws_xpixel, :ushort, + :ws_ypixel, :ushort + end + + TIOCGWINSZ = _IOR('t', 104, Winsize) # get window size + TIOCSWINSZ = _IOW('t', 103, Winsize) # set window size + + attach_function :tcsetattr, [ :int, :int, Termios ], :int + attach_function :tcgetattr, [ :int, Termios ], :int + attach_function :cfgetispeed, [ Termios ], :speed_t + attach_function :cfgetospeed, [ Termios ], :speed_t + attach_function :cfsetispeed, [ Termios, :speed_t ], :int + attach_function :cfsetospeed, [ Termios, :speed_t ], :int + attach_function :cfmakeraw, [ Termios ], :int + attach_function :tcflush, [ :int, :int ], :int + attach_function :ioctl, [ :int, :ulong, :varargs ], :int +end diff --git a/lib/io/console/jruby/common.rb b/lib/io/console/jruby/common.rb new file mode 100644 index 0000000..10f7fc0 --- /dev/null +++ b/lib/io/console/jruby/common.rb @@ -0,0 +1,35 @@ +# Methods common to all backend impls +class IO + def getch(*) + raw do + getc + end + end + + def getpass(prompt = nil) + wio = self == $stdin ? $stderr : self + wio.write(prompt) if prompt + begin + str = nil + noecho do + str = gets + end + ensure + puts($/) + end + str.chomp + end + + module GenericReadable + def getch(*) + getc + end + + def getpass(prompt = nil) + write(prompt) if prompt + str = gets.chomp + puts($/) + str + end + end +end \ No newline at end of file diff --git a/lib/io/console/jruby/linux_console.rb b/lib/io/console/jruby/linux_console.rb new file mode 100644 index 0000000..bb6e43c --- /dev/null +++ b/lib/io/console/jruby/linux_console.rb @@ -0,0 +1,200 @@ +require 'ffi' + +raise LoadError.new("native console only supported on i386, x86_64 and powerpc64") unless FFI::Platform::ARCH =~ /i386|x86_64|powerpc64/ + +module IO::LibC + extend FFI::Library + ffi_lib FFI::Library::LIBC + + typedef :uint, :tcflag_t + typedef :uint, :speed_t + + VINTR = 0 + VQUIT = 1 + VERASE = 2 + VKILL = 3 + VEOF = 4 + VTIME = 5 + VMIN = 6 + VSWTC = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VEOL = 11 + VREPRINT = 12 + VDISCARD = 13 + VWERASE = 14 + VLNEXT = 15 + VEOL2 = 16 + + # c_iflag bits + IGNBRK = 0000001 + BRKINT = 0000002 + IGNPAR = 0000004 + PARMRK = 0000010 + INPCK = 0000020 + ISTRIP = 0000040 + INLCR = 0000100 + IGNCR = 0000200 + ICRNL = 0000400 + IUCLC = 0001000 + IXON = 0002000 + IXANY = 0004000 + IXOFF = 0010000 + IMAXBEL = 0020000 + IUTF8 = 0040000 + + # c_oflag bits + OPOST = 0000001 + OLCUC = 0000002 + ONLCR = 0000004 + OCRNL = 0000010 + ONOCR = 0000020 + ONLRET = 0000040 + OFILL = 0000100 + OFDEL = 0000200 + NLDLY = 0000400 + NL0 = 0000000 + NL1 = 0000400 + CRDLY = 0003000 + CR0 = 0000000 + CR1 = 0001000 + CR2 = 0002000 + CR3 = 0003000 + TABDLY = 0014000 + TAB0 = 0000000 + TAB1 = 0004000 + TAB2 = 0010000 + TAB3 = 0014000 + XTABS = 0014000 + BSDLY = 0020000 + BS0 = 0000000 + BS1 = 0020000 + VTDLY = 0040000 + VT0 = 0000000 + VT1 = 0040000 + FFDLY = 0100000 + FF0 = 0000000 + FF1 = 0100000 + + # c_cflag bit meaning + CBAUD = 0010017 + B0 = 0000000 + B50 = 0000001 + B75 = 0000002 + B110 = 0000003 + B134 = 0000004 + B150 = 0000005 + B200 = 0000006 + B300 = 0000007 + B600 = 0000010 + B1200 = 0000011 + B1800 = 0000012 + B2400 = 0000013 + B4800 = 0000014 + B9600 = 0000015 + B19200 = 0000016 + B38400 = 0000017 + EXTA = B19200 + EXTB = B38400 + CSIZE = 0000060 + CS5 = 0000000 + CS6 = 0000020 + CS7 = 0000040 + CS8 = 0000060 + CSTOPB = 0000100 + CREAD = 0000200 + PARENB = 0000400 + PARODD = 0001000 + HUPCL = 0002000 + CLOCAL = 0004000 + CBAUDEX = 0010000 + BOTHER = 0010000 + B57600 = 0010001 + B115200 = 0010002 + B230400 = 0010003 + B460800 = 0010004 + B500000 = 0010005 + B576000 = 0010006 + B921600 = 0010007 + B1000000 = 0010010 + B1152000 = 0010011 + B1500000 = 0010012 + B2000000 = 0010013 + B2500000 = 0010014 + B3000000 = 0010015 + B3500000 = 0010016 + B4000000 = 0010017 + CIBAUD = 002003600000 + CMSPAR = 010000000000 + CRTSCTS = 020000000000 + + IBSHIFT = 16 + + # c_lflag bits + ISIG = 0000001 + ICANON = 0000002 + XCASE = 0000004 + ECHO = 0000010 + ECHOE = 0000020 + ECHOK = 0000040 + ECHONL = 0000100 + NOFLSH = 0000200 + TOSTOP = 0000400 + ECHOCTL = 0001000 + ECHOPRT = 0002000 + ECHOKE = 0004000 + FLUSHO = 0010000 + PENDIN = 0040000 + IEXTEN = 0100000 + + # tcflow() and TCXONC use these + TCOOFF = 0 + TCOON = 1 + TCIOFF = 2 + TCION = 3 + + # tcflush() and TCFLSH use these + TCIFLUSH = 0 + TCOFLUSH = 1 + TCIOFLUSH = 2 + + # tcsetattr uses these + TCSANOW = 0 + TCSADRAIN = 1 + TCSAFLUSH = 2 + NCCS = 19 + class Termios < FFI::Struct + layout \ + :c_iflag, :tcflag_t, + :c_oflag, :tcflag_t, + :c_cflag, :tcflag_t, + :c_lflag, :tcflag_t, + :c_line, :uchar, + :cc_c, [ :uchar, NCCS ], + :c_ispeed, :speed_t, + :c_ospeed, :speed_t + end + + class Winsize < FFI::Struct + layout \ + :ws_row, :ushort, + :ws_col, :ushort, + :ws_xpixel, :ushort, + :ws_ypixel, :ushort + end + + + TIOCGWINSZ = 0x5413 + TIOCSWINSZ = 0x5414 + + attach_function :tcsetattr, [ :int, :int, Termios ], :int + attach_function :tcgetattr, [ :int, Termios ], :int + attach_function :cfgetispeed, [ Termios ], :speed_t + attach_function :cfgetospeed, [ Termios ], :speed_t + attach_function :cfsetispeed, [ Termios, :speed_t ], :int + attach_function :cfsetospeed, [ Termios, :speed_t ], :int + attach_function :cfmakeraw, [ Termios ], :int + attach_function :tcflush, [ :int, :int ], :int + attach_function :ioctl, [ :int, :ulong, :varargs ], :int +end diff --git a/lib/io/console/jruby/native_console.rb b/lib/io/console/jruby/native_console.rb new file mode 100644 index 0000000..792e4b1 --- /dev/null +++ b/lib/io/console/jruby/native_console.rb @@ -0,0 +1,153 @@ +# Load appropriate native bits for BSD or Linux +case RbConfig::CONFIG['host_os'].downcase +when /darwin|openbsd|freebsd|netbsd/ + require_relative 'bsd_console' +when /linux/ + require_relative 'linux_console' +else + raise LoadError.new("no native io/console support") +end + +# Common logic that uses native calls for console +class IO + def ttymode + termios = LibC::Termios.new + if LibC.tcgetattr(self.fileno, termios) != 0 + raise SystemCallError.new("tcgetattr", FFI.errno) + end + + if block_given? + yield tmp = termios.dup + if LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, tmp) != 0 + raise SystemCallError.new("tcsetattr", FFI.errno) + end + end + termios + end + private :ttymode + + def ttymode_yield(block, &setup) + begin + orig_termios = ttymode { |t| setup.call(t) } + block.call(self) + ensure + if orig_termios && LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, orig_termios) != 0 + raise SystemCallError.new("tcsetattr", FFI.errno) + end + end + end + private :ttymode_yield + + TTY_RAW = Proc.new do |t| + LibC.cfmakeraw(t) + t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK) + end + + def raw(*, &block) + ttymode_yield(block, &TTY_RAW) + end + + def raw!(*) + ttymode(&TTY_RAW) + end + + TTY_COOKED = Proc.new do |t| + t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON) + t[:c_oflag] |= LibC::OPOST + t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN) + end + + def cooked(*, &block) + ttymode_yield(block, &TTY_COOKED) + end + + def cooked!(*) + ttymode(&TTY_COOKED) + end + + TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL + def echo=(echo) + ttymode do |t| + if echo + t[:c_lflag] |= TTY_ECHO + else + t[:c_lflag] &= ~TTY_ECHO + end + end + end + + def echo? + (ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0 + end + + def noecho(&block) + ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) } + end + + def winsize + ws = LibC::Winsize.new + if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) + end + [ ws[:ws_row], ws[:ws_col] ] + end + + def winsize=(size) + ws = LibC::Winsize.new + if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) + end + + ws[:ws_row] = size[0] + ws[:ws_col] = size[1] + if LibC.ioctl(self.fileno, LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno) + end + end + + def iflush + raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIFLUSH) == 0 + end + + def oflush + raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCOFLUSH) == 0 + end + + def ioflush + raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIOFLUSH) == 0 + end + + # TODO: Windows version uses "conin$" and "conout$" instead of /dev/tty + def self.console(sym = nil, *args) + raise TypeError, "expected Symbol, got #{sym.class}" unless sym.nil? || sym.kind_of?(Symbol) + + # klass = self == IO ? File : self + if defined?(@console) # using ivar instead of hidden const as in MRI + con = @console + # MRI checks IO internals : (!RB_TYPE_P(con, T_FILE) || (!(fptr = RFILE(con)->fptr) || GetReadFD(fptr) == -1)) + if !con.kind_of?(File) || (con.kind_of?(IO) && (con.closed? || !FileTest.readable?(con))) + remove_instance_variable :@console + con = nil + end + end + + if sym + if sym == :close + if con + con.close + remove_instance_variable :@console if defined?(@console) + end + return nil + end + end + + if !con && $stdin.tty? + con = File.open('/dev/tty', 'r+') + con.sync = true + @console = con + end + + return con.send(sym, *args) if sym + return con + end +end diff --git a/lib/io/console/jruby/stty_console.rb b/lib/io/console/jruby/stty_console.rb new file mode 100644 index 0000000..1832a9a --- /dev/null +++ b/lib/io/console/jruby/stty_console.rb @@ -0,0 +1,82 @@ +# attempt to call stty; if failure, raise error +`stty 2> /dev/null` +if $?.exitstatus != 0 + raise "stty command returned nonzero exit status" +end + +warn "io/console on JRuby shells out to stty for most operations" + +# Non-Windows assumes stty command is available +class IO + if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd") + def stty(*args) + `stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}` + end + else + def stty(*args) + `stty #{args.join(' ')}` + end + end + + def raw(*) + saved = stty('-g') + stty('raw') + yield self + ensure + stty(saved) + end + + def raw!(*) + stty('raw') + end + + def cooked(*) + saved = stty('-g') + stty('-raw') + yield self + ensure + stty(saved) + end + + def cooked!(*) + stty('-raw') + end + + def echo=(echo) + stty(echo ? 'echo' : '-echo') + end + + def echo? + (stty('-a') =~ / -echo /) ? false : true + end + + def noecho + saved = stty('-g') + stty('-echo') + yield self + ensure + stty(saved) + end + + # Not all systems return same format of stty -a output + IEEE_STD_1003_2 = '(?\d+) rows; (?\d+) columns' + UBUNTU = 'rows (?\d+); columns (?\d+)' + + def winsize + match = stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/) + [match[:rows].to_i, match[:columns].to_i] + end + + def winsize=(size) + stty("rows #{size[0]} cols #{size[1]}") + end + + def iflush + end + + def oflush + end + + def ioflush + end +end \ No newline at end of file diff --git a/lib/io/console/jruby/stub_console.rb b/lib/io/console/jruby/stub_console.rb new file mode 100644 index 0000000..2ebe36c --- /dev/null +++ b/lib/io/console/jruby/stub_console.rb @@ -0,0 +1,45 @@ +warn "io/console not supported; tty will not be manipulated" if $VERBOSE + +# Windows version is always stubbed for now +class IO + def raw(*) + yield self + end + + def raw!(*) + end + + def cooked(*) + yield self + end + + def cooked!(*) + end + + def echo=(echo) + end + + def echo? + true + end + + def noecho + yield self + end + + def winsize + [25, 80] + end + + def winsize=(size) + end + + def iflush + end + + def oflush + end + + def ioflush + end +end \ No newline at end of file