
If the debugger's monkey-patched raise was passed an instantiated exception, the #exception method was called with a potentially nil argument, causing its instance data to be thrown away. This hides potentially useful information from the user. Fix it by allowing instantiated exceptions to be reraised unharmed. Fixes Homebrew/homebrew#17622.
173 lines
4.2 KiB
Ruby
173 lines
4.2 KiB
Ruby
def can_use_readline?
|
|
not ENV['HOMEBREW_NO_READLINE']
|
|
end
|
|
|
|
require 'irb' if can_use_readline?
|
|
require 'continuation' if RUBY_VERSION.to_f >= 1.9
|
|
|
|
class Menu
|
|
attr_accessor :prompt
|
|
attr_accessor :entries
|
|
|
|
def initialize
|
|
@entries = []
|
|
end
|
|
|
|
def choice(name, &action)
|
|
entries << { :name => name, :action => action }
|
|
end
|
|
end
|
|
|
|
def choose
|
|
menu = Menu.new
|
|
yield menu
|
|
|
|
choice = nil
|
|
while choice.nil?
|
|
menu.entries.each_with_index do |entry, i|
|
|
puts "#{i+1}. #{entry[:name]}"
|
|
end
|
|
print menu.prompt unless menu.prompt.nil?
|
|
reply = $stdin.gets.chomp
|
|
|
|
i = reply.to_i
|
|
if i > 0
|
|
choice = menu.entries[i-1]
|
|
else
|
|
possible = menu.entries.find_all {|e| e[:name].to_s.start_with? reply }
|
|
case possible.size
|
|
when 0 then puts "No such option"
|
|
when 1 then choice = possible.first
|
|
else puts "Multiple options match: #{possible.map{|e| e[:name]}.join(' ')}"
|
|
end
|
|
end
|
|
end
|
|
choice[:action].call
|
|
end
|
|
|
|
|
|
module IRB
|
|
@setup_done = false
|
|
|
|
def IRB.start_within(binding)
|
|
unless @setup_done
|
|
# make IRB ignore our command line arguments
|
|
saved_args = ARGV.shift(ARGV.size)
|
|
IRB.setup(nil)
|
|
ARGV.concat(saved_args)
|
|
@setup_done = true
|
|
end
|
|
|
|
workspace = WorkSpace.new(binding)
|
|
irb = Irb.new(workspace)
|
|
|
|
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
|
@CONF[:MAIN_CONTEXT] = irb.context
|
|
|
|
trap("SIGINT") do
|
|
irb.signal_handle
|
|
end
|
|
|
|
begin
|
|
catch(:IRB_EXIT) do
|
|
irb.eval_input
|
|
end
|
|
ensure
|
|
irb_at_exit
|
|
end
|
|
end
|
|
end if can_use_readline?
|
|
|
|
class Exception
|
|
attr_accessor :continuation
|
|
|
|
def restart(&block)
|
|
continuation.call block
|
|
end
|
|
end
|
|
|
|
def has_debugger?
|
|
begin
|
|
require 'rubygems'
|
|
require 'ruby-debug'
|
|
true
|
|
rescue LoadError
|
|
false
|
|
end if can_use_readline?
|
|
end
|
|
|
|
def debrew(exception, formula=nil)
|
|
puts "#{exception.backtrace.first}"
|
|
puts "#{Tty.red}#{exception.class.to_s}#{Tty.reset}: #{exception.to_s}"
|
|
|
|
begin
|
|
again = false
|
|
choose do |menu|
|
|
menu.prompt = "Choose an action: "
|
|
menu.choice(:raise) { original_raise exception }
|
|
menu.choice(:ignore) { exception.restart }
|
|
menu.choice(:backtrace) { puts exception.backtrace; again = true }
|
|
menu.choice(:debug) do
|
|
puts "When you exit the debugger, execution will continue."
|
|
exception.restart { debugger }
|
|
end if has_debugger?
|
|
menu.choice(:irb) do
|
|
puts "When you exit this IRB session, execution will continue."
|
|
exception.restart do
|
|
# we need to capture the binding after returning from raise
|
|
set_trace_func proc { |event, file, line, id, binding, classname|
|
|
if event == 'return'
|
|
set_trace_func nil
|
|
IRB.start_within(binding)
|
|
end
|
|
}
|
|
end
|
|
end if can_use_readline?
|
|
menu.choice(:shell) do
|
|
puts "When you exit this shell, you will return to the menu."
|
|
interactive_shell formula
|
|
again=true
|
|
end
|
|
end
|
|
end while again
|
|
end
|
|
|
|
module RaisePlus
|
|
alias :original_raise :raise
|
|
|
|
def raise(*args)
|
|
exception = case
|
|
when args.size == 0
|
|
$!.nil? ? RuntimeError.exception : $!
|
|
when args.size == 1 && args[0].is_a?(String)
|
|
RuntimeError.exception(args[0])
|
|
when args.size == 2 && args[0].is_a?(Exception)
|
|
args[0].exception(args[1])
|
|
when args[0].is_a?(Class) && args[0].ancestors.include?(Exception)
|
|
args[0].exception(args[1])
|
|
else
|
|
args[0]
|
|
end
|
|
|
|
# passing something other than a String or Exception is illegal, but if someone does it anyway,
|
|
# that object won't have backtrace or continuation methods. in that case, let's pass it on to
|
|
# the original raise, which will reject it
|
|
return super exception unless exception.is_a?(Exception)
|
|
|
|
# keep original backtrace if reraising
|
|
exception.set_backtrace(args.size >= 3 ? args[2] : caller) if exception.backtrace.nil?
|
|
|
|
blk = callcc do |cc|
|
|
exception.continuation = cc
|
|
super exception
|
|
end
|
|
blk.call unless blk.nil?
|
|
end
|
|
|
|
alias :fail :raise
|
|
end
|
|
|
|
class Object
|
|
include RaisePlus
|
|
end
|