# 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