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