Skip to content

Instantly share code, notes, and snippets.

@ms-ati
Created April 12, 2018 17:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ms-ati/c11658166c8844dfa6b1caed480d02b3 to your computer and use it in GitHub Desktop.
Save ms-ati/c11658166c8844dfa6b1caed480d02b3 to your computer and use it in GitHub Desktop.
##
# Monkey-patch IRB in Ruby 2.3.4 to address the following Ruby issue:
#
# * https://bugs.ruby-lang.org/issues/14685
# IRB doesn't print exception cause
##
# After loading this file, one should observe in an IRB session:
#
# * Printed exceptions contain the `#cause`
# In fact, this should work for a chain of `#cause` relationships
##
# This file is a copied and modified version of the method `IRB::Irb#eval_input`
# from Ruby version 2.3.4.
#
# It would be great to turn this into a PR to Ruby. To do so, one would first
# need to:
# a. Make an updated version based on the latest trunk Ruby
# b. Add tests
#
# To make a later PR easier, the modified lines are called out with comments
# below.
#
# Author: "Marc Siegel" <msiegel@panoramaed.com>
##
require "irb"
module IRB
class Irb
# Monkey-patch this large method by copying it and modifying.
def eval_input
@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
unless ltype
ind = prompt(@context.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
end
@scanner.set_input(@context.io) 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"
end
end
l
end
end
@scanner.each_top_level_statement do |line, line_no|
signal_status(:IN_EVAL) do
begin
line.untaint
@context.evaluate(line, line_no)
output_value if @context.echo?
exc = nil
rescue Interrupt => exc
rescue SystemExit, SignalException
raise
rescue Exception => exc
end
## BEGIN Monkey-patch 1
#- if exc
is_cause = false
while exc
#- print exc.class, ": ", exc, "\n"
print (is_cause ? "Caused by: " : ""), exc.class, ": ", exc, "\n"
## END Monkey-patch 1
if exc.backtrace && exc.backtrace[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
!(SyntaxError === exc)
irb_bug = true
else
irb_bug = false
end
messages = []
lasts = []
levels = 0
if exc.backtrace
for m in exc.backtrace
m = @context.workspace.filter_backtrace(m) unless irb_bug
if m
if messages.size < @context.back_trace_limit
messages.push "\tfrom "+m
else
lasts.push "\tfrom "+m
if lasts.size > @context.back_trace_limit
lasts.shift
levels += 1
end
end
end
end
end
print messages.join("\n"), "\n"
unless lasts.empty?
printf "... %d levels...\n", levels if levels > 0
print lasts.join("\n"), "\n"
end
print "Maybe IRB bug!\n" if irb_bug
## BEGIN Monkey-patch 2
exc = exc.cause
is_cause = true
## END Monkey-patch 2
end
if $SAFE > 2
abort "Error: irb does not work for $SAFE level higher than 2"
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment