| # |
| # |
| # 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 |