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

- old
+ new

@@ -1,309 +1,313 @@ require "eturem/version" module Eturem - # 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) - rescue Exception => exception - raise exception if exception.is_a? SystemExit - eturem.exception = exception - end - eturem.exception ? eturem : nil + @program_name = $PROGRAM_NAME.encode("utf-8") + + def self.rescue + yield + rescue Exception => exception + raise exception if exception.is_a? SystemExit + exception end - - 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__) + + # load script and return exception if exception raised + # @param [String] filename script file + # @return [Exception] if exception raised + # @return [true] if exception did not raise + def self.load(filename, wrap = false) + self.rescue do + Kernel.load(filename, wrap) end - script = read_script(file) - begin - 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 - eturem.exception = exception - $stderr.write eturem.inspect - rescue Exception => e - 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 + self.rescue do bind ? Kernel.eval(expr, bind, fname, lineno) : Kernel.eval(expr) - rescue Exception => exception - raise exception if exception.is_a? SystemExit - eturem.exception = exception end - return eturem.exception ? eturem : nil end - - def self.set_config(config) - @eturem_class.set_config(config) + + def self.extend_exception(exception) + ext = _extend_exception(exception) || + case exception + when SyntaxError then SyntaxErrorExt + when NameError then NameErrorExt + when ArgumentError then ArgumentErrorExt + else ExceptionExt + end + exception.extend ext + exception.eturem_prepare 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 + + def self.program_name + @program_name end - - class Base - attr_reader :exception, :backtrace_locations, :path, :lineno, :label - - @@output_backtrace = true - @@output_original = true - @@output_script = true - @@use_coderay = false - @@before_line_num = 2 - @@after_line_num = 2 - - @inspect_methods = {} - - def self.inspect_methods - return @inspect_methods + + + module Base + @@eturem_output_backtrace = true + @@eturem_output_original = true + @@eturem_output_script = true + @@eturem_use_coderay = false + @@eturem_before_line_num = 2 + @@eturem_after_line_num = 2 + + def self.output_backtrace=(value) + @@eturem_output_backtrace = value end - - 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) - @@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) + + def self.output_original=(value) + @@eturem_output_original = value end - - def initialize(load_file, replace_stderr = false) - @load_file = load_file.encode("utf-8") - @scripts = {} - @decoration = {} - if replace_stderr - @stderr = $stderr - $stderr = self - end + + def self.output_script=(value) + @@eturem_output_script = value end - - def exception=(exception) - @exception = exception - @exception_s = exception.to_s - - eturem_path = File.dirname(File.expand_path(__FILE__)) - @backtrace_locations = (@exception.backtrace_locations || []).reject do |location| - path = File.expand_path(location.path) - path.start_with?(eturem_path) || path.end_with?("/rubygems/core_ext/kernel_require.rb") - 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 - - @script_lines = read_script(@path) || [] - @output_linenos = default_output_linenos - prepare + + def self.use_coderay=(value) + @@eturem_use_coderay = value end - - def inspect - str = @@output_backtrace ? backtrace_inspect : "" - error_message = exception_inspect - if error_message.empty? - str << original_exception_inspect - else - str = "#{original_exception_inspect}\n#{str}" if @@output_original - str << "#{error_message}\n" - end - str << script_inspect if @@output_script - return str + + def self.before_line_num=(value) + @@eturem_before_line_num = value end - - def backtrace_inspect - return "" if @backtrace_locations.empty? - - 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 + + def self.after_line_num=(value) + @@eturem_after_line_num = value end - - def exception_inspect - inspect_methods = self.class.inspect_methods - inspect_methods.keys.reverse_each do |key| - if (key.is_a?(Class) && @exception.is_a?(key)) || - (key.is_a?(String) && @exception.class.to_s == key) - method = inspect_methods[key] - return method ? public_send(method) : "" - end - end - return "" + + def self.highlight(str, pattern, highlight) + str.sub!(pattern) { "#{highlight}#{$1 || $&}\e[0m#{$2}" } if str end - - def original_exception_inspect - if @exception.is_a? SyntaxError - 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" + + def self.unhighlight(str) + str.gsub(/\e\[[0-9;]*m/, "") + end + + def self.read_script(filename) + return [] unless File.exist?(filename) + script = File.binread(filename) + encoding = "utf-8" + if script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/) + encoding = Regexp.last_match(:encoding) end + script.force_encoding(encoding).encode!("utf-8") + if @@eturem_use_coderay + require "coderay" + script = CodeRay.scan(script, :ruby).terminal + end + [""] + script.lines(chomp: true) end - - def script_inspect(path = @path, linenos = @output_linenos, lineno = @lineno, decoration = @decoration) - script_lines = read_script(path) - return "" unless script_lines - + + def self.script(lines, linenos, lineno) str = "" max_lineno_length = linenos.max.to_s.length - last_i = linenos.min - 1 + last_i = linenos.min.to_i - 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) + line = lines[i] + next unless line + str += " #{' ' * max_lineno_length} :\n" if last_i + 1 != i + str += (lineno == i ? " => " : " ") + str += sprintf("\e[1;34m%#{max_lineno_length}d\e[0m: %s\e[0m\n", i, line) last_i = i end - return str + 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) + + + def eturem_prepare + this_filepath = File.expand_path(__FILE__) + @eturem_backtrace_locations = self.backtrace_locations || [] + index = @eturem_backtrace_locations.index do |location| + File.expand_path(location.path) == this_filepath end - if message - @stderr.write(message) + @eturem_backtrace_locations = @eturem_backtrace_locations[0...index] if index + + program_filepath = File.expand_path(Eturem.program_name) + @eturem_backtrace_locations.each do |location| + if File.expand_path(location.path) == program_filepath + def location.path; Eturem.program_name; end + if location.label == "<top (required)>" + def location.label; "<main>"; end + end + end + end + + @eturem_message = self.message + unless @eturem_message.encoding == Encoding::UTF_8 + @eturem_message.force_encoding("utf-8") + unless @eturem_message.valid_encoding? + @eturem_message.force_encoding(Encoding.locale_charmap).encode!("utf-8") + end + end + + if self.is_a?(SyntaxError) && @eturem_message.match(/\A(?<path>.+?)\:(?<lineno>\d+):\s*/) + @eturem_path = Regexp.last_match(:path) + @eturem_lineno = Regexp.last_match(:lineno).to_i + @eturem_message = Regexp.last_match.post_match + @eturem_path = Eturem.program_name if @eturem_path == program_filepath else - @stderr.write(*str) + eturem_backtrace_locations_shift end + + @eturem_script_lines = Eturem::Base.read_script(@eturem_path) + @eturem_output_linenos = eturem_default_output_linenos end - - def comeback_stderr - $stderr = @stderr || STDERR + + def eturem_original_error_message() + @eturem_message.match(/\A(?<first_line>.*)/) + "#{@eturem_path}:#{@eturem_lineno}:in `#{@eturem_label}': " + + "\e[1m#{Regexp.last_match(:first_line)} (\e[4m#{self.class}\e[0;1m)" + + "#{Regexp.last_match.post_match.chomp}\e[0m\n" end - - def to_s - @exception_s + + def eturem_backtrace_str(order = :bottom) + str = @eturem_backtrace_locations.empty? ? "" : eturem_traceback(order) + str + (order == :top ? eturem_backtrace_str_top : eturem_backtrace_str_bottom) end - - private - - def prepare - case @exception - when NameError then prepare_name_error - when ArgumentError then prepare_argument_error + + def eturem_backtrace + eturem_backtrace_locations.map do |location| + eturem_location_to_s(location) end end - - def prepare_name_error - return unless @exception_s.match(/Did you mean\?/) - @did_you_mean = Regexp.last_match.post_match.strip.scan(/\S+/) - @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 - @decoration[index] = [name, "\e[1;33m"] - @output_linenos.push(index) - end + + def eturem_location_to_s(location) + "#{location.path}:#{location.lineno}:in `#{location.label}'" end - - def prepare_argument_error - @method = @label - backtrace_locations_shift - @script_lines = read_script(@path) - @output_linenos = default_output_linenos + + def eturem_backtrace_locations + @eturem_backtrace_locations end - - def backtrace_locations_shift - @label = @backtrace_locations.first.label - @path = @backtrace_locations.first.path - @lineno = @backtrace_locations.first.lineno - @backtrace_locations.shift + + def eturem_message + "" end - - def traceback_most_recent_call_last - "Traceback (most recent call last):" + + def eturem_script + Eturem::Base.script(@eturem_script_lines, @eturem_output_linenos, @eturem_lineno) end - - def location_inspect(location) - "from #{location.path}:#{location.lineno}:in `#{location.label}'" + + def eturem_full_message(highlight: true, order: :bottom) + unless $stderr == STDERR && $stderr.tty? + highlight = false + order = :top + end + + str = @@eturem_output_backtrace ? eturem_backtrace_str(order) : "" + ext_message = eturem_message + if ext_message.empty? + str += eturem_original_error_message + else + str = "#{eturem_original_error_message}\n#{str}" if @@eturem_output_original + str += "#{ext_message}\n" + end + str += eturem_script if @@eturem_output_script + + highlight ? str : Eturem::Base.unhighlight(str) end - - def default_output_linenos - from = [1, @lineno - @@before_line_num].max - to = [@script_lines.size - 1, @lineno + @@after_line_num].min + + private + + def eturem_backtrace_locations_shift + location = @eturem_backtrace_locations.shift + @eturem_label = location.label + @eturem_path = location.path + @eturem_lineno = location.lineno + end + + def eturem_default_output_linenos + from = [1, @eturem_lineno - @@eturem_before_line_num].max + to = [@eturem_script_lines.size - 1, @eturem_lineno + @@eturem_after_line_num].min (from..to).to_a end - - def highlight(str, keyword, color) - str.to_s.gsub(keyword){ "#{color}#{$&}\e[0m" } + + def eturem_traceback(order = :bottom) + order == :top ? "" : "\e[1mTraceback\e[0m (most recent call last):\n" end - - 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 + + def eturem_backtrace_str_bottom + lines = [] + backtrace = eturem_backtrace + size = backtrace.size + format = "%#{8 + size.to_s.length}d: %s\n" + backtrace.reverse.each_with_index do |bt, i| + lines.push(sprintf(format, size - i, bt)) end + + if @eturem_message == "stack level too deep" + lines = lines[-4..-1] + + [" ... #{lines.size - 12} levels...\n"] + + lines[0..7] + end + lines.join end - - 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) + + def eturem_backtrace_str_top + lines = eturem_backtrace.map do |bt| + " from #{bt}\n" end - return @scripts[file] + if @eturem_message == "stack level too deep" + lines = lines[0..7] + + [" ... #{lines.size - 12} levels...\n"] + + lines[-4..-1] + end + lines.join end end + + + module ExceptionExt + include Base + end + + + module NameErrorExt + include ExceptionExt + + def eturem_prepare() + @eturem_corrections = self.respond_to?(:corrections) ? self.corrections : [] + @eturem_corrections += Object.constants.select do |const| + const.casecmp?(self.name) + end + @eturem_corrections.uniq! + def self.corrections; @eturem_corrections; end + super + uname = self.name.to_s.encode("utf-8") + if @eturem_script_lines[@eturem_lineno] + Eturem::Base.highlight(@eturem_script_lines[@eturem_lineno], uname, "\e[1;4;31m") + end + @eturem_corrections.each do |name| + index = @eturem_script_lines.index { |line| line.include?(name.to_s.encode("utf-8")) } + next unless index + Eturem::Base.highlight(@eturem_script_lines[index], name.to_s.encode("utf-8"), "\e[1;33m") + @eturem_output_linenos.push(index) + end + end + end + + + module ArgumentErrorExt + include ExceptionExt + + def eturem_prepare() + super + @eturem_method = @eturem_label + eturem_backtrace_locations_shift + @eturem_script_lines = Eturem::Base.read_script(@eturem_path) + @eturem_output_linenos = eturem_default_output_linenos + end + end + + + module SyntaxErrorExt + include ExceptionExt - @eturem_class = Base + def eturem_original_error_message() + ret = "#{@eturem_path}:#{@eturem_lineno}: #{@eturem_message}" + unless @eturem_path == Eturem.program_name + ret = "\e[1m#{ret} (\e[4m#{self.class}\e[0;1m)\e[0m" + end + ret + "\n" + end + end end