lib/eturem/base.rb in eturem-0.3.3 vs lib/eturem/base.rb in eturem-0.4.0

- old
+ new

@@ -4,51 +4,84 @@ # load script and return eturem_exception if exception raised # @param [String] file script file # @return [Eturem::Base] if exception raised # @return [nil] if exception did not raise def self.load(file) + eturem = @eturem_class.new(file) begin - Kernel.load(File.absolute_path(file)) + Kernel.load(file) rescue Exception => exception - return @eturem_class.new(exception, file) unless exception.is_a? SystemExit + raise exception if exception.is_a? SystemExit + eturem.exception = exception end - return nil + eturem.exception ? eturem : nil end - def self.load_and_output(file, debug = false) + def self.load_and_output(file, repl = nil, debug = false) + eturem = @eturem_class.new(file, true) + last_binding = nil + tp = TracePoint.trace(:raise) do |t| + last_binding = t.binding unless File.expand_path(t.path) == File.expand_path(__FILE__) + end + script = read_script(file) begin - Kernel.load(File.absolute_path(file)) + TOPLEVEL_BINDING.eval(script, file, 1) + tp.disable + eturem.comeback_stderr rescue Exception => exception + tp.disable + eturem.comeback_stderr + raise exception if exception.is_a? SystemExit + repl ||= $eturem_repl + use_repl = repl && last_binding && exception.is_a?(StandardError) begin - puts @eturem_class.new(exception, file).inspect unless exception.is_a? SystemExit + eturem.exception = exception + $stderr.write eturem.inspect rescue Exception => e - raise debug ? e : exception + raise debug ? e : eturem.exception end + return unless use_repl + require repl + last_binding.public_send(repl) end end def self.eval(expr, bind = nil, fname = "(eval)", lineno = 1) + eturem = @eturem_class.new(fname) begin bind ? Kernel.eval(expr, bind, fname, lineno) : Kernel.eval(expr) rescue Exception => exception - return @eturem_class.new(exception, fname) unless exception.is_a? SystemExit + raise exception if exception.is_a? SystemExit + eturem.exception = exception end - return nil + return eturem.exception ? eturem : nil end def self.set_config(config) @eturem_class.set_config(config) end + def self.read_script(file) + script = nil + if File.exist?(file) + script = File.binread(file) + encoding = "utf-8" + if script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/) + encoding = Regexp.last_match(:encoding) + end + script.force_encoding(encoding) + end + return script + end + class Base - attr_reader :exception + attr_reader :exception, :backtrace_locations, :path, :lineno, :label @@output_backtrace = true @@output_original = true @@output_script = true @@use_coderay = false - @@max_backtrace = 16 @@before_line_num = 2 @@after_line_num = 2 @inspect_methods = {} @@ -59,69 +92,77 @@ def self.set_config(config) @@output_backtrace = config[:output_backtrace] if config.has_key?(:output_backtrace) @@output_original = config[:output_original] if config.has_key?(:output_original) @@output_script = config[:output_script] if config.has_key?(:output_script) @@use_coderay = config[:use_coderay] if config.has_key?(:use_coderay) - @@max_backtrace = config[:max_backtrace] if config.has_key?(:max_backtrace) @@before_line_num = config[:before_line_num] if config.has_key?(:before_line_num) @@after_line_num = config[:after_line_num] if config.has_key?(:after_line_num) end - def initialize(exception, load_file) + def initialize(load_file, replace_stderr = false) + @load_file = load_file.encode("utf-8") + @scripts = {} + @decoration = {} + if replace_stderr + @stderr = $stderr + $stderr = self + end + end + + def exception=(exception) @exception = exception @exception_s = exception.to_s - eturem_path = File.dirname(File.absolute_path(__FILE__)) + eturem_path = File.dirname(File.expand_path(__FILE__)) @backtrace_locations = (@exception.backtrace_locations || []).reject do |location| - path = File.absolute_path(location.path) + path = File.expand_path(location.path) path.start_with?(eturem_path) || path.end_with?("/rubygems/core_ext/kernel_require.rb") end - @backtrace_locations.each do |location| - if File.absolute_path(load_file) == location.path - if load_file == $0 - def location.path - $0 - end - end - 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 backtrace_locations_shift end - load_script + @script_lines = read_script(@path) || [] + @output_linenos = default_output_linenos prepare end def inspect - str = "" - str = backtrace_inspect if @@output_backtrace + str = @@output_backtrace ? backtrace_inspect : "" error_message = exception_inspect if error_message.empty? - str += original_exception_inspect + str << original_exception_inspect else - str += original_exception_inspect + "\n" if @@output_original - str += error_message + "\n" + str = "#{original_exception_inspect}\n#{str}" if @@output_original + str << "#{error_message}\n" end - str += script_inspect if @@output_script + str << script_inspect if @@output_script return str end def backtrace_inspect return "" if @backtrace_locations.empty? - str = traceback_most_recent_call_last + "\n" - @backtrace_locations[0, @@max_backtrace].reverse.each_with_index do |location, i| - str += sprintf("%9d: %s\n", @backtrace_locations.size - i, location_inspect(location)) + str = "#{traceback_most_recent_call_last}\n" + backtraces = [] + size = @backtrace_locations.size + format = "%#{8 + size.to_s.length}d: %s\n" + @backtrace_locations.reverse.each_with_index do |location, i| + backtraces.push(sprintf(format, size - i, location_inspect(location))) end + + if @exception_s == "stack level too deep" + str << backtraces[0..7].join + str << " ... #{backtraces.size - 12} levels...\n" + str << backtraces[-4..-1].join + else + str << backtraces.join + end return str end def exception_inspect inspect_methods = self.class.inspect_methods @@ -135,63 +176,85 @@ return "" end def original_exception_inspect if @exception.is_a? SyntaxError - return @exception_s + return "#{@exception_s.chomp}\n" else location_str = "#{@path}:#{@lineno}:in `#{@label}'" @exception_s.match(/\A(?<first_line>.*)/) return "#{location_str}: #{Regexp.last_match(:first_line)} (\e[4m#{@exception.class}\e[0m)" + "#{Regexp.last_match.post_match.chomp}\n" end end - def script_inspect - return "" if @script.empty? + def script_inspect(path = @path, linenos = @output_linenos, lineno = @lineno, decoration = @decoration) + script_lines = read_script(path) + return "" unless script_lines str = "" - max_lineno_length = @output_lines.max.to_s.length - last_i = @output_lines.min - 1 - @output_lines.sort.each do |i| - str += "\e[0m #{' ' * max_lineno_length} :\n" if last_i + 1 != i - str += @lineno == i ? "\e[0m => \e[1;34m" : "\e[0m \e[1;34m" - str += sprintf("%#{max_lineno_length}d\e[0m: %s", i, @script_lines[i]) + max_lineno_length = linenos.max.to_s.length + last_i = linenos.min - 1 + linenos.uniq.sort.each do |i| + line = script_lines[i] + line = highlight(line, decoration[i][0], decoration[i][1]) if decoration[i] + str << "\e[0m #{' ' * max_lineno_length} :\n" if last_i + 1 != i + str << (lineno == i ? "\e[0m => \e[1;34m" : "\e[0m \e[1;34m") + str << sprintf("%#{max_lineno_length}d\e[0m: %s\n", i, line) last_i = i end return str end + def write(*str) + message = nil + if str.join.force_encoding("utf-8").match(/^(.+?):(\d+):\s*warning:\s*/) + path, lineno, warning = $1, $2.to_i, $'.strip + message = warning_message(path, lineno, warning) + end + if message + @stderr.write(message) + else + @stderr.write(*str) + end + end + + def comeback_stderr + $stderr = @stderr || STDERR + end + + def to_s + @exception_s + end + private def prepare case @exception when NameError then prepare_name_error when ArgumentError then prepare_argument_error end end def prepare_name_error - highlight!(@script_lines[@lineno], @exception.name.to_s, "\e[1;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? + @decoration[@lineno] = [@exception.name.to_s, "\e[1;31m\e[4m"] @did_you_mean.each do |name| index = @script_lines.index { |line| line.include?(name) } next unless index - highlight!(@script_lines[index], name, "\e[1;33m") - @output_lines.push(index) + @decoration[index] = [name, "\e[1;33m"] + @output_linenos.push(index) 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 + @script_lines = read_script(@path) + @output_linenos = default_output_linenos end def backtrace_locations_shift @label = @backtrace_locations.first.label @path = @backtrace_locations.first.path @@ -205,40 +268,41 @@ def location_inspect(location) "from #{location.path}:#{location.lineno}:in `#{location.label}'" end - def load_script - @script ||= "" - if @path && File.exist?(@path) - @script = File.binread(@path) - encoding = "utf-8" - if @script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/) - encoding = Regexp.last_match(:encoding) - end - @script.force_encoding(encoding) - end - if @@use_coderay - require "coderay" - @script = CodeRay.scan(@script, :ruby).terminal - end - @script_lines = @script.lines - @script_lines.unshift("") - @output_lines = default_output_lines + def default_output_linenos + 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.to_s.gsub(keyword){ color + ($1 || $&) + "\e[0m" } + str.to_s.gsub(keyword){ "#{color}#{$&}\e[0m" } end - def highlight!(str, keyword, color) - str.gsub!(keyword){ color + ($1 || $&) + "\e[0m" } if str + def warning_message(file, line, warning) + case warning + when "found `= literal' in conditional, should be ==" + "#{file}:#{line}: warning: #{warning}\n" + + script_inspect(file, [line], line, { line => [/(?<![><!=])=(?!=)/, "\e[1;31m\e[4m"] }) + else + nil + end end - 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 + def read_script(file) + unless @scripts[file] + script = Eturem.read_script(file) + return nil unless script + script.encode!("utf-8") + if @@use_coderay + require "coderay" + script = CodeRay.scan(script, :ruby).terminal + end + @scripts[file] = [""] + script.lines(chomp: true) + end + return @scripts[file] end end @eturem_class = Base end