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