blob: ae534f6f00b36b361e4d12a9752c529eca1368f8 [file]
#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'rbconfig'
module IRB
WINDOZE = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
# Subclass of IRB so can intercept methods
class HIRB < Irb
def self.command_names
@command_names ||= ::Shell.commands.keys.map(&:to_sym).freeze
end
def initialize(workspace = nil, interactive = true, input_method = nil)
# This is ugly. Our 'help' method above provokes the following message
# on irb construction: 'irb: warn: can't alias help from irb_help.'
# Below, we reset the output so its pointed at /dev/null during irb
# construction just so this message does not come out after we emit
# the banner. Other attempts at playing with the hash of methods
# down in IRB didn't seem to work. I think the worst thing that can
# happen is the shell exiting because of failed IRB construction with
# no error (though we're not blanking STDERR)
# Map the '/dev/null' according to the running platform
# Under Windows platform the 'dev/null' is not fully compliant with unix,
# and the 'NUL' object need to be use instead.
devnull = '/dev/null'
devnull = 'NUL' if WINDOZE
f = File.open(devnull, 'w')
$stdout = f
# This is a workaround for the jruby issue 1372.
# The stderr is an input to stty to re-adjust the terminal for the error('stdin isnt a terminal')
# in case the command is piped with hbase shell(eg - >echo 'list' | bin/hbase shell)
if $stdin.tty?
`stty icrnl <&2`
end
@interactive = interactive
super(workspace, input_method)
ensure
f.close
$stdout = STDOUT
end
def set_context_workspace(workspace)
if @context.respond_to?(:workspace=)
@context.workspace = workspace
else
@context.instance_variable_set(:@workspace, workspace)
end
end
def output_value(omit = false)
# Suppress output if last_value is 'nil'
# Otherwise, when user types help, get ugly 'nil'
# after all output.
super(omit) unless @context.last_value.nil?
end
# Copied from https://github.com/ruby/irb/blob/v1.4.2/lib/irb.rb
# We override the rescue Exception block so the
# Shell::exception_handler can deal with the exceptions.
def eval_input
exc = nil
@scanner.set_prompt do
|ltype, indent, continue, line_no|
if ltype
f = @context.prompt_s
elsif continue
f = @context.prompt_c
elsif indent > 0
f = @context.prompt_n
else
f = @context.prompt_i
end
f = "" unless f
if @context.prompting?
@context.io.prompt = p = prompt(f, ltype, indent, line_no)
else
@context.io.prompt = p = ""
end
if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent)
unless ltype
prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i
ind = prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size +
indent * 2 - p.size
ind += 2 if continue
@context.io.prompt = p + " " * ind if ind > 0
end
end
@context.io.prompt
end
@scanner.set_input(@context.io, context: @context) do
signal_status(:IN_INPUT) do
if l = @context.io.gets
print l if @context.verbose?
else
if @context.ignore_eof? and @context.io.readable_after_eof?
l = "\n"
if @context.verbose?
printf "Use \"exit\" to leave %s\n", @context.ap_name
end
else
print "\n" if @context.prompting?
end
end
l
end
end
@scanner.set_auto_indent(@context) if @context.auto_indent_mode
@scanner.each_top_level_statement do |line, line_no|
signal_status(:IN_EVAL) do
begin
line.untaint if RUBY_VERSION < '2.7'
if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty?
IRB.set_measure_callback
end
if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
result = nil
last_proc = proc{ result = @context.evaluate(line, line_no, exception: exc) }
IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) { |chain, item|
_name, callback, arg = item
proc {
callback.(@context, line, line_no, arg, exception: exc) do
chain.call
end
}
}.call
@context.set_last_value(result)
else
@context.evaluate(line, line_no, exception: exc)
end
if @context.echo?
if assignment_expression?(line)
if @context.echo_on_assignment?
output_value(@context.echo_on_assignment? == :truncate)
end
else
output_value
end
end
rescue Interrupt => exc
rescue SystemExit, SignalException
raise
rescue SyntaxError => exc
# HBASE-27726: Ignore SyntaxError to prevent exiting Shell on unexpected syntax.
raise exc unless @interactive
rescue NameError => exc
raise exc unless @interactive
# HBASE-26880: Ignore NameError to prevent exiting Shell on mistyped commands.
rescue Exception => exc
# HBASE-26741: Raise exception so Shell::exception_handler can catch it.
# This modifies this copied method from JRuby so that the HBase shell can
# manage the exception and set a proper exit code on the process.
raise exc
else
exc = nil
next
ensure
# HBASE-28660: Prevent command shadowing by incorrectly parsed local variables
cmd_names = self.class.command_names
workspace_binding = @context.workspace.binding
shadowing_vars = workspace_binding.local_variables & cmd_names
if shadowing_vars.any?
shadowing_vars.each do |var|
warn "WARN: '#{var}' is a reserved HBase command. Local variable assignment ignored."
end
new_binding = @context.workspace.main.to_binding
(workspace_binding.local_variables - shadowing_vars).each do |var|
new_binding.local_variable_set(var, workspace_binding.local_variable_get(var))
end
set_context_workspace(::IRB::WorkSpace.new(new_binding))
end
end
handle_exception(exc)
@context.workspace.local_variable_set(:_, exc)
exc = nil
end
end
end
end
##
# HBaseLoader serves a similar purpose to IRB::IrbLoader, but with a different separation of
# concerns. This loader allows us to directly get the path for a filename in ruby's load path,
# and then use that in IRB::Irb
module HBaseLoader
##
# Determine the loadable path for a given filename by searching through $LOAD_PATH
#
# Uses $LOAD_PATH.resolve_feature_path, which resolves files on disk as well as
# entries inside jars on the classpath. Returns nil on miss.
#
# @param [String] filename
# @return [String] path
def self.path_for_load(filename)
return File.absolute_path(filename) if File.exist? filename
resolved = $LOAD_PATH.resolve_feature_path(filename)
raise LoadError, "no such file to load -- #{filename}" if resolved.nil?
_type, path = resolved
path
end
##
# Return a file handle for the given file found in the load path
#
# @param [String] filename
# @return [FileInputMethod] InputMethod for passing to IRB session
def self.file_for_load(filename)
FileInputMethod.new(File.new(path_for_load(filename)))
end
end
end