class MotherBrain::ErrorHandler
An object to help with the display of errors in a more user-friendly format. An ErrorHandler
is created with an error and a set of options to control the display of the error. Some options can be inferred from the error itself. A typical use case would be to wrap an error generated deep in a call stack, and then add data to the error as it bubbles up.
@example Wrapping and raising an error with more data
ErrorHandler.wrap StandardError.new, file_path: "/a/b/c.rb", method_name: :wat, plugin_name: "hi", plugin_version: "1.2.3", text: "Invalid thing" # Would raise an error with a message of: hi (1.2.3) /a/b/c.rb, on line 1, in 'wat' Invalid thing
@example Wrapping an error at multiple points in the call chain
def load_file(path) load File.read(path) rescue => error ErrorHandler.wrap error, file_path: path end def load(code) eval code rescue => error ErrorHandler.wrap error, plugin_name: code.lines.to_a.first end def method_missing(method_name, *args, &block) ErrorHandler.wrap CodeError.new, method_name: method_name end
Constants
- NEWLINE
- OPTIONS
- SOURCE_RANGE
Attributes
Public Class Methods
@param [StandardError] error
@option options [Array] backtrace
An array of strings containing filenames, line numbers, and method names. Typically comes from `caller`.
@option options [String] file_path
The location of a file on disk to display to the user.
@option options [Symbol] method_name
The name of the method or keyword which generated the error.
@option options [String] plugin_name
The name of the plugin the error relates to.
@option options [String] plugin_version
The version of the plugin the error relates to.
@option options [String] text
A custom error message to display to the user.
# File lib/mb/error_handler.rb, line 94 def initialize(error, options = {}) error = error.new if error.is_a? Class @error = error extract_data_from_options options extract_data_from_error embed_data_in_error end
Wraps an error with additional data and raises it.
@raise [StandardError] @see initialize
# File lib/mb/error_handler.rb, line 51 def wrap(error, options = {}) error_handler = new(error, options) raise error_handler.error, error_handler.message end
Public Instance Methods
Stores the data in the error and defines getters.
# File lib/mb/error_handler.rb, line 133 def embed_data_in_error OPTIONS.each do |option| data = instance_variable_get "@#{option}" if data error.instance_variable_set "@_error_handler_#{option}", data end end end
Extracts data from the error and stores it in instance variables. Does not overwrite existing instance variables.
# File lib/mb/error_handler.rb, line 118 def extract_data_from_error OPTIONS.each do |option| data = error.instance_variable_get "@_error_handler_#{option}" unless instance_variable_get "@#{option}" instance_variable_set "@#{option}", data end end @backtrace ||= error.backtrace @method_name ||= error.name if error.respond_to? :name @text ||= error.message end
Extracts data from an options hash and stores it in instance variables.
@param [Hash] options
@see initialize
# File lib/mb/error_handler.rb, line 110 def extract_data_from_options(options) OPTIONS.each do |option| instance_variable_set "@#{option}", options[option] end end
@return [String]
# File lib/mb/error_handler.rb, line 158 def file_contents return unless file_path and File.exist? file_path File.read file_path end
@return [String]
# File lib/mb/error_handler.rb, line 190 def file_path_and_line_number_and_method_name buffer = [] buffer << file_path if file_path buffer << "on line #{line_number}" if line_number buffer << "in '#{method_name}'" if method_name buffer.join ", " end
Extracts the first line number from the backtrace.
@return [Fixnum]
# File lib/mb/error_handler.rb, line 206 def line_number return unless backtrace and backtrace[0] backtrace[0].split(":")[1].to_i end
@return [String]
# File lib/mb/error_handler.rb, line 144 def message result = [ plugin_name_and_plugin_version, file_path_and_line_number_and_method_name, text, relevant_source_lines ].compact.join NEWLINE result << NEWLINE unless result.end_with? NEWLINE result end
@return [Array]
# File lib/mb/error_handler.rb, line 174 def numbered_source_lines lines = file_contents.lines.to_a.map(&:rstrip) rjust_size = lines.count.to_s.length result = [] lines.each_with_index do |line, index| current_line_number = index + 1 result << "#{current_line_number.to_s.rjust rjust_size}#{line_number == current_line_number ? '>>' : ': '}#{line}" end result end
@return [String]
# File lib/mb/error_handler.rb, line 199 def plugin_name_and_plugin_version "#{plugin_name} (#{plugin_version})" if plugin_name and plugin_version end
@return [String]
# File lib/mb/error_handler.rb, line 165 def relevant_source_lines return unless file_contents and line_number beginning = line_number - (SOURCE_RANGE / 2) - 1 beginning = [beginning, 0].max numbered_source_lines[beginning, SOURCE_RANGE].join NEWLINE end