require 'cli/kit' require 'English' module CLI module Kit class ErrorHandler def initialize(log_file:, exception_reporter:, tool_name: nil) @log_file = log_file @exception_reporter_or_proc = exception_reporter || NullExceptionReporter @tool_name = tool_name end module NullExceptionReporter def self.report(_exception, _logs) nil end end def call(&block) install! handle_abort(&block) end def handle_exception(error) if (notify_with = exception_for_submission(error)) logs = begin File.read(@log_file) rescue => e "(#{e.class}: #{e.message})" end exception_reporter.report(notify_with, logs) end end # maybe we can get rid of this. attr_writer :exception private def exception_for_submission(error) case error when nil # normal, non-error termination nil when Interrupt # ctrl-c nil when CLI::Kit::Abort, CLI::Kit::AbortSilent # Not a bug nil when SignalException skip = %w(SIGTERM SIGHUP SIGINT) skip.include?(error.message) ? nil : error when SystemExit # "exit N" called case error.status when CLI::Kit::EXIT_SUCCESS # submit nothing if it was `exit 0` nil when CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG # if it was `exit 30`, translate the exit code to 1, and submit nothing. # 30 is used to signal normal failures that are not indicative of bugs. # However, users should see it presented as 1. exit(1) else # A weird termination status happened. `error.exception "message"` will maintain backtrace # but allow us to set a message error.exception("abnormal termination status: #{error.status}") end else error end end def install! at_exit { handle_exception(@exception || $ERROR_INFO) } end def handle_abort yield CLI::Kit::EXIT_SUCCESS rescue CLI::Kit::GenericAbort => e is_bug = e.is_a?(CLI::Kit::Bug) || e.is_a?(CLI::Kit::BugSilent) is_silent = e.is_a?(CLI::Kit::AbortSilent) || e.is_a?(CLI::Kit::BugSilent) print_error_message(e) unless is_silent (@exception = e) if is_bug CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG rescue Interrupt stderr_puts_message('Interrupt') CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG rescue Errno::ENOSPC message = if @tool_name "Your disk is full - {{command:#{@tool_name}}} requires free space to operate" else "Your disk is full - free space is required to operate" end stderr_puts_message(message) CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG end def stderr_puts_message(message) $stderr.puts(format_error_message(message)) rescue Errno::EPIPE nil end def exception_reporter if @exception_reporter_or_proc.respond_to?(:report) @exception_reporter_or_proc else @exception_reporter_or_proc.call end end def format_error_message(msg) CLI::UI.fmt("{{red:#{msg}}}") end def print_error_message(e) $stderr.puts(format_error_message(e.message)) end end end end