lib/eturem/base.rb in eturem-0.1.0 vs lib/eturem/base.rb in eturem-0.2.0
- old
+ new
@@ -1,197 +1,270 @@
+require "eturem/version"
+
module Eturem
- def self.set_eturem_class(klass)
+ def self.eturem_class
+ @eturem_class
+ end
+
+ def self.eturem_class=(klass)
@eturem_class = klass
end
- def self.output_error(exception, eturem_path)
- @eturem_class.new(exception, eturem_path).output_error
+ def self.load(file)
+ begin
+ Kernel.load file
+ rescue Exception => exception
+ return @eturem_class.new(exception) unless exception.is_a? SystemExit
+ end
+ return nil
end
+ def self.eval(expr, bind = nil, fname = "(eval)", lineno = 1)
+ begin
+ bind ? Kernel.eval(expr, bind, fname, lineno) : Kernel.eval(expr)
+ rescue Exception => exception
+ return @eturem_class.new(exception) unless exception.is_a? SystemExit
+ end
+ return nil
+ end
+
class Base
- def initialize(exception, eturem_path)
+ attr_reader :exception
+
+ @@output_backtrace = true
+ @@output_original = true
+ @@output_script = true
+ @@max_backtrace = 16
+ @@before_line_num = 2
+ @@after_line_num = 2
+
+ @inspect_methods = {}
+
+ def self.inspect_methods
+ return @inspect_methods
+ end
+
+ def self.output_backtrace=(value)
+ @@output_backtrace = value
+ end
+
+ def self.output_original=(value)
+ @@output_original = value
+ end
+
+ def self.output_script=(value)
+ @@output_script = value
+ end
+
+ def self.max_backtrace=(value)
+ @@max_backtrace = value
+ end
+
+ def self.before_line_num=(value)
+ @@before_line_num = value
+ end
+
+ def self.after_line_num=(value)
+ @@after_line_num = value
+ end
+
+ # prepare
+ def initialize(exception)
@exception = exception
@exception_s = exception.to_s
- @eturem_path = eturem_path
- @locations = @exception.backtrace_locations.select do |location|
+
+ eturem_path = File.dirname(File.absolute_path(__FILE__))
+ @backtrace_locations = @exception.backtrace_locations.reject do |location|
path = File.absolute_path(location.path)
- path !~ /\/rubygems\/core_ext\/kernel_require\.rb$/ &&
- path != @eturem_path &&
- File.basename(File.dirname(path)) != "eturem"
+ path.start_with?(eturem_path) || path.end_with?("/rubygems/core_ext/kernel_require.rb")
end
- @locations = @locations[0, max_backtrace]
- if @exception_s.match(/\A(?<path>[^:]+)\:(?<lineno>\d+)/)
- @lineno_in_exception_s = true
+ @backtrace_locations.each do |location|
+ if $0 == location.path
+ def location.label
+ super.sub("<top (required)>", "<main>")
+ end
+ end
+ end
+
+ if @exception.is_a?(SyntaxError) && @exception_s.match(/\A(?<path>[^:]+?)\:(?<lineno>\d+)/)
@path = Regexp.last_match(:path)
@lineno = Regexp.last_match(:lineno).to_i
else
- @lineno_in_exception_s = false
- @path = @locations.first.path
- @lineno = @locations.first.lineno
+ backtrace_locations_shift
end
+
+ load_script
+ prepare
+ end
+
+ def load_script
@script = ""
if @path && File.exist?(@path)
@script = File.binread(@path)
- encoding = @script.lines[0..2].join("\n").match(/coding:\s*(?<encoding>\S+)/) ?
- Regexp.last_match(:encoding) : "UTF-8"
+ encoding = "utf-8"
+ if @script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/)
+ encoding = Regexp.last_match(:encoding)
+ end
@script.force_encoding(encoding)
- @script.encode("UTF-8")
end
@script_lines = @script.lines
@script_lines.unshift("")
- @range = default_range
- prepare
+ @output_lines = default_output_lines
end
def prepare
case @exception
- when SyntaxError
- return unless @exception_s.match(/unexpected (?<unexpected>\S+)\,\s*expecting (?<expected>\S+)/)
- @unexpected = Regexp.last_match(:unexpected)
- @expected = Regexp.last_match(:expected)
- when NameError
- highlight!(@script_lines[@lineno], @exception.name.to_s, "\e[31m\e[4m")
- return unless @exception_s.match(/Did you mean\?/)
- @did_you_mean = Regexp.last_match.post_match.strip.split(/\s+/)
- new_range = []
- @did_you_mean.each do |did_you_mean|
- index = @script_lines.index{|line| line.match(did_you_mean)}
- next unless index
- highlight!(@script_lines[index], did_you_mean, "\e[33m")
- new_range.push(index)
- end
- before = new_range.select{|i| i < @range.min}
- after = new_range.select{|i| i > @range.max}
- unless before.empty?
- @range.unshift(0) unless @range.include?(before.max + 1)
- @range.unshift(*before.sort)
- end
- unless after.empty?
- @range.push(0) unless @range.include?(after.min - 1)
- @range.push(*after.sort)
- end
- when ArgumentError
- @method = @locations.first.label
- shift_locations
- return unless @exception_s.match(/given (?<given>\d+)\, expected (?<expected>[^)]+)/)
- @given = Regexp.last_match(:given).to_i
- @expected = Regexp.last_match(:expected)
- when TypeError
- @method = @locations.first.label
- shift_locations
+ when SyntaxError then prepare_syntax_error
+ when NameError then prepare_name_error
+ when ArgumentError then prepare_argument_error
+ when TypeError then prepare_type_error
end
end
- def shift_locations
- @locations.shift
- @path = @locations.first.path
- @lineno = @locations.first.lineno
- @range = default_range
+ def prepare_syntax_error
+ @unexpected = @exception_s.match(/unexpected (?<unexpected>(?:','|[^,])+)/) ?
+ Regexp.last_match(:unexpected) : "end-of-input"
+ @expected = @exception_s.match(/[,\s]expecting (?<expected>\S+)/) ?
+ Regexp.last_match(:expected) : "end-of-input"
end
- def default_range
- from = [1, @lineno - before_line].max
- to = [@script_lines.size - 1, @lineno + after_line].min
- (from..to).to_a
+ def prepare_name_error
+ highlight!(@script_lines[@lineno], @exception.name.to_s, "\e[31m\e[4m")
+ return unless @exception_s.match(/Did you mean\?/)
+ @did_you_mean = Regexp.last_match.post_match.strip.scan(/\S+/)
+ return if @script.empty?
+
+ new_range = []
+ @did_you_mean.each do |name|
+ index = @script_lines.index { |line| line.include?(name) }
+ next unless index
+ highlight!(@script_lines[index], name, "\e[33m")
+ new_range.push(index)
+ end
+ new_range.sort!
+ before = new_range.select { |i| i < @output_lines.first }
+ after = new_range.select { |i| i > @output_lines.last }
+ unless before.empty?
+ @output_lines.unshift(nil) if before.last + 1 != @output_lines.first
+ @output_lines.unshift(*before)
+ end
+ unless after.empty?
+ @output_lines.push(nil) if @output_lines.last + 1 != after.first
+ @output_lines.push(*after)
+ end
end
+ def prepare_argument_error
+ @method = @label
+ old_path = @path
+ backtrace_locations_shift
+ load_script unless old_path == @path
+ @output_lines = default_output_lines
+ if @exception_s.match(/given (?<given>\d+)\, expected (?<expected>[^)]+)/)
+ @given = Regexp.last_match(:given).to_i
+ @expected = Regexp.last_match(:expected)
+ end
+ end
+
+ def prepare_type_error
+ @method = @label
+ end
+
+ # main
def output_error
- output_backtrace
- output_exception
- output_script
+ output_backtrace if @@output_backtrace
+ error_message = exception_inspect
+ if error_message.to_s.empty?
+ puts original_exception_inspect
+ else
+ puts original_exception_inspect if @@output_original
+ puts error_message
+ end
+ output_script if @@output_script
end
# backtrace
def output_backtrace
- return if backtrace.empty?
+ return if @backtrace_locations.empty?
puts traceback_most_recent_call_last
- backtrace.each_with_index do |location, i|
- label =
- $0 == location.path && location.label == "<top (required)>" ?
- "<main>" : location.label
- puts sprintf("%9d: %s", backtrace.size - i, location_inspect(location, label))
+ @backtrace_locations[0, @@max_backtrace].reverse.each_with_index do |location, i|
+ puts sprintf("%9d: %s", @backtrace_locations.size - i, location_inspect(location))
end
end
- def backtrace
- get_backtrace unless @backtrace
- return @backtrace
+ def backtrace_locations_shift
+ @label = @backtrace_locations.first.label
+ @path = @backtrace_locations.first.path
+ @lineno = @backtrace_locations.first.lineno
+ @backtrace_locations.shift
end
- def get_backtrace
- @backtrace = @locations.reverse.dup
- @backtrace.pop unless @lineno_in_exception_s
- end
-
- def max_backtrace
- 16
- end
-
def traceback_most_recent_call_last
"Traceback (most recent call last):"
end
- def location_inspect(location, label = nil)
- "from #{location.path}:#{location.lineno}:in `#{label || location.label}'"
+ def location_inspect(location)
+ "from #{location.path}:#{location.lineno}:in `#{location.label}'"
end
# exception
- def output_exception
- puts exception_inspect
- end
-
def exception_inspect
- original_exception_inspect
+ inspect_methods = self.class.inspect_methods
+ inspect_methods.keys.reverse_each do |key|
+ case key
+ when Class
+ return public_send(inspect_methods[key]) if @exception.is_a? key
+ when String
+ return public_send(inspect_methods[key]) if @exception.class.to_s == key
+ end
+ end
+ return nil
end
def original_exception_inspect
- if @lineno_in_exception_s
+ if @exception.is_a? SyntaxError
return @exception_s
else
- label = @locations.first.label
- label = "<main>" if $0 == @path && label == "<top (required)>"
- location_str = "#{@path}:#{@lineno}:in `#{label}'"
- if @exception_s.match(/\A(.*?)\n/)
- matched = Regexp.last_match
- return "#{location_str}: #{matched[1]} (#{@exception.class})\n#{matched.post_match}"
+ location_str = "#{@path}:#{@lineno}:in `#{@label}'"
+ if @exception_s.match(/\A(?<first_line>.*?)\n/)
+ return "#{location_str}: #{Regexp.last_match(:first_line)} (#{@exception.class})\n" +
+ "#{Regexp.last_match.post_match}"
else
- return "#{location_str}: #{@exception} (#{@exception.class})"
+ return "#{location_str}: #{@exception_s} (#{@exception.class})"
end
end
end
# script
def output_script
- max_lineno_length = @range.max.to_s.length
- @range.each do |i|
- if i == 0
- puts " \e[34m#{" " * max_lineno_length} :\e[0m"
+ return if @script.empty?
+
+ max_lineno_length = @output_lines.compact.max.to_s.length
+ @output_lines.each do |i|
+ if @lineno == i
+ puts sprintf(" => \e[34m%#{max_lineno_length}d:\e[0m %s", i, @script_lines[i])
+ elsif i
+ puts sprintf(" \e[34m%#{max_lineno_length}d:\e[0m %s", i, @script_lines[i])
else
- if @lineno == i
- puts sprintf(" => \e[34m%#{max_lineno_length}d:\e[0m %s", i, @script_lines[i])
- else
- puts sprintf(" \e[34m%#{max_lineno_length}d:\e[0m %s", i, @script_lines[i])
- end
+ puts " \e[34m#{" " * max_lineno_length} :\e[0m"
end
end
end
- def before_line
- 2
+ def highlight(str, keyword, color)
+ str.to_s.gsub(keyword){ color + ($1 || $&) + "\e[0m" }
end
- def after_line
- 2
+ def highlight!(str, keyword, color)
+ str.gsub!(keyword){ color + ($1 || $&) + "\e[0m" } if str
end
- def highlight(str, keyword, color)
- str.gsub(keyword){ color + ($1 || $&) + "\e[0m" }
+ def default_output_lines
+ from = [1, @lineno - @@before_line_num].max
+ to = [@script_lines.size - 1, @lineno + @@after_line_num].min
+ (from..to).to_a
end
-
- def highlight!(str, keyword, color)
- str.gsub!(keyword){ color + ($1 || $&) + "\e[0m" }
- end
+
+ Eturem.eturem_class = self
end
-
- set_eturem_class Base
end