Skip to content

Commit

Permalink
Add support for JRuby.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
headius committed Jan 6, 2020
1 parent fbabf0b commit 058d2fb
Show file tree
Hide file tree
Showing 9 changed files with 777 additions and 4 deletions.
26 changes: 22 additions & 4 deletions io-console.gemspec
Expand Up @@ -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
7 changes: 7 additions & 0 deletions 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
67 changes: 67 additions & 0 deletions 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 'io/console/jruby/common'

# If Windows, always use the stub version
if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
require 'io/console/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 'io/console/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 'io/console/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 'io/console/jruby/stub_console'
end

end
166 changes: 166 additions & 0 deletions 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
35 changes: 35 additions & 0 deletions 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

0 comments on commit 058d2fb

Please sign in to comment.