Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for JRuby #11

Merged
merged 6 commits into from Jan 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions Rakefile
Expand Up @@ -2,6 +2,8 @@ require "bundler/gem_tasks"
require "rake/testtask"

name = "io/console"
specfile = name.tr("/", "-")+".gemspec"
spec = eval(File.read(specfile), nil, specfile)

Rake::TestTask.new(:test) do |t|
t.libs << "test" << "test/lib"
Expand All @@ -16,6 +18,33 @@ Rake::ExtensionTask.new(name)
task :default => [:compile, :test]

task "build" => "date_epoch"

java_pkg = nil
task 'build:java' => 'date_epoch' do |t|
file_name = "#{spec.full_name}-java.gem"
gem_command = ENV["GEM_COMMAND"]
gem_command &&= gem_command.shellsplit
Bundler::GemHelper.instance.instance_eval do
sh([*(gem_command || "gem"), "build", "-V", specfile, "--", "--platform=java"]) do
FileUtils.mkdir_p("pkg")
FileUtils.mv(file_name, "pkg")
Bundler.ui.confirm "#{spec.name} #{spec.version} built to pkg/#{file_name}."
end
java_pkg = File.join("pkg", file_name)
end
end

task 'release:rubygem_push' => 'release:rubygem_push:java'
desc 'Push binary gems for Java platform'
task 'release:rubygem_push:java' => 'build:java' do
Bundler::GemHelper.instance.instance_eval do
if gem_push?
Bundler.ui.confirm "Pushing #{java_pkg}"
rubygem_push(java_pkg)
end
end
end

task "date_epoch" do
ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp
end
Expand Down
21 changes: 21 additions & 0 deletions io-console.gemspec
Expand Up @@ -21,5 +21,26 @@ Gem::Specification.new do |s|
lib/io/console/size.rb
]
s.extensions = %w[ext/io/console/extconf.rb]

if i = ARGV.index("--") and !(argv = ARGV[i+1..-1]).empty?
OptionParser.new(__FILE__) do |opt|
opt.on("--platform=PLATFORM") {|p| s.platform = p}
end.parse!(argv)
end
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("jruby")
s.files.concat(%w[
jruby/io/console.rb
jruby/io/console/bsd_console.rb
jruby/io/console/common.rb
jruby/io/console/linux_console.rb
jruby/io/console/native_console.rb
jruby/io/console/stty_console.rb
jruby/io/console/stub_console.rb
])
end

s.licenses = ["Ruby", "BSD-2-Clause"]
end
67 changes: 67 additions & 0 deletions jruby/io/console.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 'console/common'

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

end
166 changes: 166 additions & 0 deletions jruby/io/console/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 jruby/io/console/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