зеркало из https://github.com/github/ruby.git
539 строки
15 KiB
Ruby
539 строки
15 KiB
Ruby
# frozen_string_literal: true
|
|
#
|
|
# irb/init.rb - irb initialize module
|
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
|
#
|
|
|
|
module IRB # :nodoc:
|
|
@CONF = {}
|
|
@INITIALIZED = false
|
|
# Displays current configuration.
|
|
#
|
|
# Modifying the configuration is achieved by sending a message to IRB.conf.
|
|
#
|
|
# See IRB@Configuration for more information.
|
|
def IRB.conf
|
|
@CONF
|
|
end
|
|
|
|
def @CONF.inspect
|
|
array = []
|
|
for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name}
|
|
case k
|
|
when :MAIN_CONTEXT, :__TMP__EHV__
|
|
array.push format("CONF[:%s]=...myself...", k.id2name)
|
|
when :PROMPT
|
|
s = v.collect{
|
|
|kk, vv|
|
|
ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"}
|
|
format(":%s=>{%s}", kk.id2name, ss.join(", "))
|
|
}
|
|
array.push format("CONF[:%s]={%s}", k.id2name, s.join(", "))
|
|
else
|
|
array.push format("CONF[:%s]=%s", k.id2name, v.inspect)
|
|
end
|
|
end
|
|
array.join("\n")
|
|
end
|
|
|
|
# Returns the current version of IRB, including release version and last
|
|
# updated date.
|
|
def IRB.version
|
|
format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE)
|
|
end
|
|
|
|
def IRB.initialized?
|
|
!!@INITIALIZED
|
|
end
|
|
|
|
# initialize config
|
|
def IRB.setup(ap_path, argv: ::ARGV)
|
|
IRB.init_config(ap_path)
|
|
IRB.init_error
|
|
IRB.parse_opts(argv: argv)
|
|
IRB.run_config
|
|
IRB.validate_config
|
|
IRB.load_modules
|
|
|
|
unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]]
|
|
fail UndefinedPromptMode, @CONF[:PROMPT_MODE]
|
|
end
|
|
@INITIALIZED = true
|
|
end
|
|
|
|
# @CONF default setting
|
|
def IRB.init_config(ap_path)
|
|
# default configurations
|
|
unless ap_path and @CONF[:AP_NAME]
|
|
ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb")
|
|
end
|
|
@CONF[:VERSION] = version
|
|
@CONF[:AP_NAME] = File::basename(ap_path, ".rb")
|
|
|
|
@CONF[:IRB_NAME] = "irb"
|
|
@CONF[:IRB_LIB_PATH] = File.dirname(__FILE__)
|
|
|
|
@CONF[:RC] = true
|
|
@CONF[:LOAD_MODULES] = []
|
|
@CONF[:IRB_RC] = nil
|
|
|
|
@CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod)
|
|
@CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty?
|
|
@CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false"
|
|
@CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym
|
|
@CONF[:INSPECT_MODE] = true
|
|
@CONF[:USE_TRACER] = false
|
|
@CONF[:USE_LOADER] = false
|
|
@CONF[:IGNORE_SIGINT] = true
|
|
@CONF[:IGNORE_EOF] = false
|
|
@CONF[:USE_PAGER] = true
|
|
@CONF[:EXTRA_DOC_DIRS] = []
|
|
@CONF[:ECHO] = nil
|
|
@CONF[:ECHO_ON_ASSIGNMENT] = nil
|
|
@CONF[:VERBOSE] = nil
|
|
|
|
@CONF[:EVAL_HISTORY] = nil
|
|
@CONF[:SAVE_HISTORY] = 1000
|
|
|
|
@CONF[:BACK_TRACE_LIMIT] = 16
|
|
|
|
@CONF[:PROMPT] = {
|
|
:NULL => {
|
|
:PROMPT_I => nil,
|
|
:PROMPT_S => nil,
|
|
:PROMPT_C => nil,
|
|
:RETURN => "%s\n"
|
|
},
|
|
:DEFAULT => {
|
|
:PROMPT_I => "%N(%m):%03n> ",
|
|
:PROMPT_S => "%N(%m):%03n%l ",
|
|
:PROMPT_C => "%N(%m):%03n* ",
|
|
:RETURN => "=> %s\n"
|
|
},
|
|
:CLASSIC => {
|
|
:PROMPT_I => "%N(%m):%03n:%i> ",
|
|
:PROMPT_S => "%N(%m):%03n:%i%l ",
|
|
:PROMPT_C => "%N(%m):%03n:%i* ",
|
|
:RETURN => "%s\n"
|
|
},
|
|
:SIMPLE => {
|
|
:PROMPT_I => ">> ",
|
|
:PROMPT_S => "%l> ",
|
|
:PROMPT_C => "?> ",
|
|
:RETURN => "=> %s\n"
|
|
},
|
|
:INF_RUBY => {
|
|
:PROMPT_I => "%N(%m):%03n> ",
|
|
:PROMPT_S => nil,
|
|
:PROMPT_C => nil,
|
|
:RETURN => "%s\n",
|
|
:AUTO_INDENT => true
|
|
},
|
|
:XMP => {
|
|
:PROMPT_I => nil,
|
|
:PROMPT_S => nil,
|
|
:PROMPT_C => nil,
|
|
:RETURN => " ==>%s\n"
|
|
}
|
|
}
|
|
|
|
@CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL)
|
|
@CONF[:AUTO_INDENT] = true
|
|
|
|
@CONF[:CONTEXT_MODE] = 4 # use a copy of TOPLEVEL_BINDING
|
|
@CONF[:SINGLE_IRB] = false
|
|
|
|
@CONF[:MEASURE] = false
|
|
@CONF[:MEASURE_PROC] = {}
|
|
@CONF[:MEASURE_PROC][:TIME] = proc { |context, code, line_no, &block|
|
|
time = Time.now
|
|
result = block.()
|
|
now = Time.now
|
|
puts 'processing time: %fs' % (now - time) if IRB.conf[:MEASURE]
|
|
result
|
|
}
|
|
# arg can be either a symbol for the mode (:cpu, :wall, ..) or a hash for
|
|
# a more complete configuration.
|
|
# See https://github.com/tmm1/stackprof#all-options.
|
|
@CONF[:MEASURE_PROC][:STACKPROF] = proc { |context, code, line_no, arg, &block|
|
|
return block.() unless IRB.conf[:MEASURE]
|
|
success = false
|
|
begin
|
|
require 'stackprof'
|
|
success = true
|
|
rescue LoadError
|
|
puts 'Please run "gem install stackprof" before measuring by StackProf.'
|
|
end
|
|
if success
|
|
result = nil
|
|
arg = { mode: arg || :cpu } unless arg.is_a?(Hash)
|
|
stackprof_result = StackProf.run(**arg) do
|
|
result = block.()
|
|
end
|
|
case stackprof_result
|
|
when File
|
|
puts "StackProf report saved to #{stackprof_result.path}"
|
|
when Hash
|
|
StackProf::Report.new(stackprof_result).print_text
|
|
else
|
|
puts "Stackprof ran with #{arg.inspect}"
|
|
end
|
|
result
|
|
else
|
|
block.()
|
|
end
|
|
}
|
|
@CONF[:MEASURE_CALLBACKS] = []
|
|
|
|
@CONF[:LC_MESSAGES] = Locale.new
|
|
|
|
@CONF[:AT_EXIT] = []
|
|
|
|
@CONF[:COMMAND_ALIASES] = {
|
|
# Symbol aliases
|
|
:'$' => :show_source,
|
|
:'@' => :whereami,
|
|
}
|
|
end
|
|
|
|
def IRB.set_measure_callback(type = nil, arg = nil, &block)
|
|
added = nil
|
|
if type
|
|
type_sym = type.upcase.to_sym
|
|
if IRB.conf[:MEASURE_PROC][type_sym]
|
|
added = [type_sym, IRB.conf[:MEASURE_PROC][type_sym], arg]
|
|
end
|
|
elsif IRB.conf[:MEASURE_PROC][:CUSTOM]
|
|
added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg]
|
|
elsif block_given?
|
|
added = [:BLOCK, block, arg]
|
|
found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] }
|
|
if found
|
|
found[1] = block
|
|
return added
|
|
else
|
|
IRB.conf[:MEASURE_CALLBACKS] << added
|
|
return added
|
|
end
|
|
else
|
|
added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg]
|
|
end
|
|
if added
|
|
IRB.conf[:MEASURE] = true
|
|
found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] }
|
|
if found
|
|
# already added
|
|
nil
|
|
else
|
|
IRB.conf[:MEASURE_CALLBACKS] << added if added
|
|
added
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def IRB.unset_measure_callback(type = nil)
|
|
if type.nil?
|
|
IRB.conf[:MEASURE_CALLBACKS].clear
|
|
else
|
|
type_sym = type.upcase.to_sym
|
|
IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym }
|
|
end
|
|
IRB.conf[:MEASURE] = nil if IRB.conf[:MEASURE_CALLBACKS].empty?
|
|
end
|
|
|
|
def IRB.init_error
|
|
@CONF[:LC_MESSAGES].load("irb/error.rb")
|
|
end
|
|
|
|
# option analyzing
|
|
def IRB.parse_opts(argv: ::ARGV)
|
|
load_path = []
|
|
while opt = argv.shift
|
|
case opt
|
|
when "-f"
|
|
@CONF[:RC] = false
|
|
when "-d"
|
|
$DEBUG = true
|
|
$VERBOSE = true
|
|
when "-w"
|
|
Warning[:deprecated] = $VERBOSE = true
|
|
when /^-W(.+)?/
|
|
opt = $1 || argv.shift
|
|
case opt
|
|
when "0"
|
|
$VERBOSE = nil
|
|
when "1"
|
|
$VERBOSE = false
|
|
else
|
|
Warning[:deprecated] = $VERBOSE = true
|
|
end
|
|
when /^-r(.+)?/
|
|
opt = $1 || argv.shift
|
|
@CONF[:LOAD_MODULES].push opt if opt
|
|
when /^-I(.+)?/
|
|
opt = $1 || argv.shift
|
|
load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt
|
|
when '-U'
|
|
set_encoding("UTF-8", "UTF-8")
|
|
when /^-E(.+)?/, /^--encoding(?:=(.+))?/
|
|
opt = $1 || argv.shift
|
|
set_encoding(*opt.split(':', 2))
|
|
when "--inspect"
|
|
if /^-/ !~ argv.first
|
|
@CONF[:INSPECT_MODE] = argv.shift
|
|
else
|
|
@CONF[:INSPECT_MODE] = true
|
|
end
|
|
when "--noinspect"
|
|
@CONF[:INSPECT_MODE] = false
|
|
when "--no-pager"
|
|
@CONF[:USE_PAGER] = false
|
|
when "--singleline", "--readline", "--legacy"
|
|
@CONF[:USE_SINGLELINE] = true
|
|
when "--nosingleline", "--noreadline"
|
|
@CONF[:USE_SINGLELINE] = false
|
|
when "--multiline", "--reidline"
|
|
if opt == "--reidline"
|
|
warn <<~MSG.strip
|
|
--reidline is deprecated, please use --multiline instead.
|
|
MSG
|
|
end
|
|
|
|
@CONF[:USE_MULTILINE] = true
|
|
when "--nomultiline", "--noreidline"
|
|
if opt == "--noreidline"
|
|
warn <<~MSG.strip
|
|
--noreidline is deprecated, please use --nomultiline instead.
|
|
MSG
|
|
end
|
|
|
|
@CONF[:USE_MULTILINE] = false
|
|
when /^--extra-doc-dir(?:=(.+))?/
|
|
opt = $1 || argv.shift
|
|
@CONF[:EXTRA_DOC_DIRS] << opt
|
|
when "--echo"
|
|
@CONF[:ECHO] = true
|
|
when "--noecho"
|
|
@CONF[:ECHO] = false
|
|
when "--echo-on-assignment"
|
|
@CONF[:ECHO_ON_ASSIGNMENT] = true
|
|
when "--noecho-on-assignment"
|
|
@CONF[:ECHO_ON_ASSIGNMENT] = false
|
|
when "--truncate-echo-on-assignment"
|
|
@CONF[:ECHO_ON_ASSIGNMENT] = :truncate
|
|
when "--verbose"
|
|
@CONF[:VERBOSE] = true
|
|
when "--noverbose"
|
|
@CONF[:VERBOSE] = false
|
|
when "--colorize"
|
|
@CONF[:USE_COLORIZE] = true
|
|
when "--nocolorize"
|
|
@CONF[:USE_COLORIZE] = false
|
|
when "--autocomplete"
|
|
@CONF[:USE_AUTOCOMPLETE] = true
|
|
when "--noautocomplete"
|
|
@CONF[:USE_AUTOCOMPLETE] = false
|
|
when "--regexp-completor"
|
|
@CONF[:COMPLETOR] = :regexp
|
|
when "--type-completor"
|
|
@CONF[:COMPLETOR] = :type
|
|
when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/
|
|
opt = $1 || argv.shift
|
|
prompt_mode = opt.upcase.tr("-", "_").intern
|
|
@CONF[:PROMPT_MODE] = prompt_mode
|
|
when "--noprompt"
|
|
@CONF[:PROMPT_MODE] = :NULL
|
|
when "--script"
|
|
noscript = false
|
|
when "--noscript"
|
|
noscript = true
|
|
when "--inf-ruby-mode"
|
|
@CONF[:PROMPT_MODE] = :INF_RUBY
|
|
when "--sample-book-mode", "--simple-prompt"
|
|
@CONF[:PROMPT_MODE] = :SIMPLE
|
|
when "--tracer"
|
|
@CONF[:USE_TRACER] = true
|
|
when /^--back-trace-limit(?:=(.+))?/
|
|
@CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i
|
|
when /^--context-mode(?:=(.+))?/
|
|
@CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i
|
|
when "--single-irb"
|
|
@CONF[:SINGLE_IRB] = true
|
|
when "-v", "--version"
|
|
print IRB.version, "\n"
|
|
exit 0
|
|
when "-h", "--help"
|
|
require_relative "help"
|
|
IRB.print_usage
|
|
exit 0
|
|
when "--"
|
|
if !noscript && (opt = argv.shift)
|
|
@CONF[:SCRIPT] = opt
|
|
$0 = opt
|
|
end
|
|
break
|
|
when /^-./
|
|
fail UnrecognizedSwitch, opt
|
|
else
|
|
if noscript
|
|
argv.unshift(opt)
|
|
else
|
|
@CONF[:SCRIPT] = opt
|
|
$0 = opt
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
load_path.collect! do |path|
|
|
/\A\.\// =~ path ? path : File.expand_path(path)
|
|
end
|
|
$LOAD_PATH.unshift(*load_path)
|
|
end
|
|
|
|
# Run the config file
|
|
def IRB.run_config
|
|
if @CONF[:RC]
|
|
irbrc_files.each do |rc|
|
|
load rc
|
|
rescue StandardError, ScriptError => e
|
|
warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}"
|
|
end
|
|
end
|
|
end
|
|
|
|
IRBRC_EXT = "rc"
|
|
|
|
def IRB.rc_file(ext)
|
|
prepare_irbrc_name_generators
|
|
|
|
# When irbrc exist in default location
|
|
if (rcgen = @existing_rc_name_generators.first)
|
|
return rcgen.call(ext)
|
|
end
|
|
|
|
# When irbrc does not exist in default location
|
|
rc_file_generators do |rcgen|
|
|
return rcgen.call(ext)
|
|
end
|
|
|
|
# When HOME and XDG_CONFIG_HOME are not available
|
|
nil
|
|
end
|
|
|
|
def IRB.irbrc_files
|
|
prepare_irbrc_name_generators
|
|
@irbrc_files
|
|
end
|
|
|
|
def IRB.validate_config
|
|
conf[:IRB_NAME] = conf[:IRB_NAME].to_s
|
|
|
|
irb_rc = conf[:IRB_RC]
|
|
unless irb_rc.nil? || irb_rc.respond_to?(:call)
|
|
raise_validation_error "IRB.conf[:IRB_RC] should be a callable object. Got #{irb_rc.inspect}."
|
|
end
|
|
|
|
back_trace_limit = conf[:BACK_TRACE_LIMIT]
|
|
unless back_trace_limit.is_a?(Integer)
|
|
raise_validation_error "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got #{back_trace_limit.inspect}."
|
|
end
|
|
|
|
prompt = conf[:PROMPT]
|
|
unless prompt.is_a?(Hash)
|
|
msg = "IRB.conf[:PROMPT] should be a Hash. Got #{prompt.inspect}."
|
|
|
|
if prompt.is_a?(Symbol)
|
|
msg += " Did you mean to set `IRB.conf[:PROMPT_MODE]`?"
|
|
end
|
|
|
|
raise_validation_error msg
|
|
end
|
|
|
|
eval_history = conf[:EVAL_HISTORY]
|
|
unless eval_history.nil? || eval_history.is_a?(Integer)
|
|
raise_validation_error "IRB.conf[:EVAL_HISTORY] should be an integer. Got #{eval_history.inspect}."
|
|
end
|
|
end
|
|
|
|
def IRB.raise_validation_error(msg)
|
|
raise TypeError, msg, @irbrc_files
|
|
end
|
|
|
|
# loading modules
|
|
def IRB.load_modules
|
|
for m in @CONF[:LOAD_MODULES]
|
|
begin
|
|
require m
|
|
rescue LoadError => err
|
|
warn "#{err.class}: #{err}", uplevel: 0
|
|
end
|
|
end
|
|
end
|
|
|
|
class << IRB
|
|
private
|
|
|
|
def prepare_irbrc_name_generators
|
|
return if @existing_rc_name_generators
|
|
|
|
@existing_rc_name_generators = []
|
|
@irbrc_files = []
|
|
rc_file_generators do |rcgen|
|
|
irbrc = rcgen.call(IRBRC_EXT)
|
|
if File.exist?(irbrc)
|
|
@irbrc_files << irbrc
|
|
@existing_rc_name_generators << rcgen
|
|
end
|
|
end
|
|
generate_current_dir_irbrc_files.each do |irbrc|
|
|
@irbrc_files << irbrc if File.exist?(irbrc)
|
|
end
|
|
@irbrc_files.uniq!
|
|
end
|
|
|
|
# enumerate possible rc-file base name generators
|
|
def rc_file_generators
|
|
if irbrc = ENV["IRBRC"]
|
|
yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
|
|
end
|
|
if xdg_config_home = ENV["XDG_CONFIG_HOME"]
|
|
irb_home = File.join(xdg_config_home, "irb")
|
|
if File.directory?(irb_home)
|
|
yield proc{|rc| irb_home + "/irb#{rc}"}
|
|
end
|
|
end
|
|
if home = ENV["HOME"]
|
|
yield proc{|rc| home+"/.irb#{rc}"}
|
|
if xdg_config_home.nil? || xdg_config_home.empty?
|
|
yield proc{|rc| home+"/.config/irb/irb#{rc}"}
|
|
end
|
|
end
|
|
end
|
|
|
|
# possible irbrc files in current directory
|
|
def generate_current_dir_irbrc_files
|
|
current_dir = Dir.pwd
|
|
%w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" }
|
|
end
|
|
|
|
def set_encoding(extern, intern = nil, override: true)
|
|
verbose, $VERBOSE = $VERBOSE, nil
|
|
Encoding.default_external = extern unless extern.nil? || extern.empty?
|
|
Encoding.default_internal = intern unless intern.nil? || intern.empty?
|
|
[$stdin, $stdout, $stderr].each do |io|
|
|
io.set_encoding(extern, intern)
|
|
end
|
|
if override
|
|
@CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern)
|
|
else
|
|
@CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern)
|
|
end
|
|
ensure
|
|
$VERBOSE = verbose
|
|
end
|
|
end
|
|
end
|