mirror of
https://github.com/ruby/ruby.git
synced 2025-08-25 05:55:46 +02:00

(https://github.com/ruby/irb/pull/817)
1. Because `IRB.rc_file` always generates an rc file name, even if the
file doesn't exist, we should check the file exists before trying to
load it.
2. If any type of errors occur while loading the rc file, we should
warn the user about it.
37ffdc6b19
476 lines
13 KiB
Ruby
476 lines
13 KiB
Ruby
# frozen_string_literal: false
|
|
#
|
|
# irb/init.rb - irb initialize module
|
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
|
#
|
|
|
|
module IRB # :nodoc:
|
|
@CONF = {}
|
|
# 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
|
|
|
|
# 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.load_modules
|
|
|
|
unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]]
|
|
fail UndefinedPromptMode, @CONF[:PROMPT_MODE]
|
|
end
|
|
end
|
|
|
|
# @CONF default setting
|
|
def IRB.init_config(ap_path)
|
|
# class instance variables
|
|
@TRACER_INITIALIZED = false
|
|
|
|
# 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.fetch("IRB_COMPLETOR", "regexp").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]
|
|
begin
|
|
file = rc_file
|
|
# Because rc_file always returns `HOME/.irbrc` even if no rc file is present, we can't warn users about missing rc files.
|
|
# Otherwise, it'd be very noisy.
|
|
load file if File.exist?(file)
|
|
rescue StandardError, ScriptError => e
|
|
warn "Error loading RC file '#{file}':\n#{e.full_message(highlight: false)}"
|
|
end
|
|
end
|
|
end
|
|
|
|
IRBRC_EXT = "rc"
|
|
def IRB.rc_file(ext = IRBRC_EXT)
|
|
if !@CONF[:RC_NAME_GENERATOR]
|
|
rc_file_generators do |rcgen|
|
|
@CONF[:RC_NAME_GENERATOR] ||= rcgen
|
|
if File.exist?(rcgen.call(IRBRC_EXT))
|
|
@CONF[:RC_NAME_GENERATOR] = rcgen
|
|
break
|
|
end
|
|
end
|
|
end
|
|
case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext)
|
|
when String
|
|
rc_file
|
|
else
|
|
fail IllegalRCNameGenerator
|
|
end
|
|
end
|
|
|
|
# enumerate possible rc-file base name generators
|
|
def IRB.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}"}
|
|
yield proc{|rc| home+"/.config/irb/irb#{rc}"}
|
|
end
|
|
current_dir = Dir.pwd
|
|
yield proc{|rc| current_dir+"/.irb#{rc}"}
|
|
yield proc{|rc| current_dir+"/irb#{rc.sub(/\A_?/, '.')}"}
|
|
yield proc{|rc| current_dir+"/_irb#{rc}"}
|
|
yield proc{|rc| current_dir+"/$irb#{rc}"}
|
|
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 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
|