lib/gorillib/exception/raisers.rb in gorillib-0.4.1pre vs lib/gorillib/exception/raisers.rb in gorillib-0.4.2pre

- old
+ new

@@ -1,27 +1,45 @@ Exception.class_eval do - # @return [Array] __FILE__, __LINE__, description - def self.caller_parts - caller_line = caller[1] - mg = %r{\A([^:]+):(\d+):in \`([^\']+)\'\z}.match(caller_line) or return [caller_line, 1, 'unknown'] + # @return [Array] file, line, method_name + def self.caller_parts(depth=1) + caller_line = caller(depth).first + mg = %r{\A([^:]+):(\d+):in \`([^\']+)\'\z}.match(caller_line) or return [caller_line, 1, '(unknown)'] [mg[1], mg[2].to_i, mg[3]] + rescue + warn "problem in #{self}.caller_parts" + return [__FILE__, __LINE__, '(unknown)'] end # - # @note !! Be sure to rescue the call to this method; few things suck worse than debugging your rescue blocks/ + # Add context to the backtrace of exceptions ocurring downstream from caller. + # This is expecially useful in metaprogramming. Follow the implementation in + # the example. + # + # @note !! Be sure to rescue the call to this method; few things suck worse + # than debugging your rescue blocks. + # + # @example + # define_method(:cromulate) do |level| + # begin + # adjust_cromulance(cromulator, level) + # rescue StandardError => err ; err.polish("setting cromulance #{level} for #{cromulator}") rescue nil ; raise ; end + # end + # def polish(extra_info) filename, _, method_name = self.class.caller_parts method_name.gsub!(/rescue in /, '') - most_recent_line = backtrace.detect{|line| line.include?(filename) && line.include?(method_name) } - most_recent_line.sub!(/'$/, " for #{extra_info}'"[0..300]) + most_recent_line = backtrace.detect{|line| + line.include?(filename) && line.include?(method_name) && line.end_with?("'") } + most_recent_line.sub!(/'$/, "' for [#{extra_info.to_s[0..300]}]") end end ArgumentError.class_eval do - # Raise an error just like Ruby's native message if the array of arguments - # doesn't match the expected length or range of lengths. + # Raise an error if there are a different number of arguments than expected. + # The message will have the same format used by Ruby internal methods. + # @see #arity_at_least! # # @example want `getset(:foo)` to be different from `getset(:foo, nil)` # def getset(key, *args) # ArgumentError.check_arity!(args, 0..1) # return self[key] if args.empty? @@ -33,18 +51,20 @@ # @param [Integer] val expected length # @overload check_arity!(args, x..y) # @param [Array] args splat args as handed to the caller # @param [#include?] val expected range/list/set of lengths # @raise ArgumentError when there are - def self.check_arity!(args, val) + def self.check_arity!(args, val, &block) allowed_arity = val.is_a?(Integer) ? (val..val) : val return true if allowed_arity.include?(args.length) - raise self.new("wrong number of arguments (#{args.length} for #{val})") + info = " #{block.call}" rescue nil if block_given? + raise self.new("wrong number of arguments (#{args.length} for #{val})#{info}") end - # Raise an error just like Ruby's native message if there are fewer arguments - # than expected + # Raise an error if there are fewer arguments than expected. The message will + # have the same format used by Ruby internal methods. + # @see #check_arity! # # @example want to use splat args, requiring at least one # def assemble_path(*pathsegs) # ArgumentError.arity_at_least!(pathsegs, 1) # # ... @@ -55,24 +75,74 @@ def self.arity_at_least!(args, min_arity) check_arity!(args, min_arity .. Float::INFINITY) end end -NoMethodError.class_eval do - MESSAGE = "undefined method `%s' for %s:%s" +class TypeMismatchError < ArgumentError ; end - def self.undefined_method(obj) - file, line, meth = caller_parts - self.new(MESSAGE % [meth, obj, obj.class]) +class ArgumentError + # + # @param [Array[Symbol,Class,Module]] types + # + # @example simple + # TypeMismatchError.mismatched!(:foo) + # #=> "TypeMismatchError: :foo has mismatched type + # + # @example Can supply the types or duck-types that are expected: + # TypeMismatchError.mismatched!(:foo, [:to_str, Integer]) + # #=> "TypeMismatchError: :foo has mismatched type; expected #to_str or Integer" + # + def self.mismatched!(obj, types=[], msg=nil, *args) + types = Array(types) + message = (obj.inspect rescue '(uninspectable object)') + message << " has mismatched type" + message << ': ' << msg if msg + unless types.empty? + message << '; expected ' << types.map{|type| type.is_a?(Symbol) ? "##{type}" : type.to_s }.join(" or ") + end + raise self, message, *args end - def self.unimplemented_method(obj) + # + # @param obj [Object] Object to check + # @param types [Array[Symbol,Class,Module]] Types or methods to compare + # + # @example simple + # TypeMismatchError.mismatched!(:foo) + # #=> "TypeMismatchError: :foo has mismatched type + # + # @example Can supply the types or duck-types that are expected: + # TypeMismatchError.mismatched!(:foo, [:to_str, Integer]) + # #=> "TypeMismatchError: :foo has mismatched type; expected #to_str or Integer" + # + def self.check_type!(obj, types, *args) + types = Array(types) + return true if types.any? do |type| + case type + when Module then obj.is_a?(type) + when Symbol then obj.respond_to?(type) + else raise StandardError, "Can't check type #{type} -- this is an error in the call to the type-checker, not in the object the type-checker is checking" + end + end + self.mismatched!(obj, types, *args) + end + +end + +# +class AbstractMethodError < NoMethodError ; end + +NoMethodError.class_eval do + MESSAGE_FMT = "undefined method `%s' for %s:%s" + + # Raise an error with the same format used by Ruby internal methods + def self.undefined_method!(obj) file, line, meth = caller_parts - self.new("#{MESSAGE} -- not implemented yet" % [meth, obj, obj.class]) + raise self.new(MESSAGE_FMT % [meth, obj, obj.class]) end - def self.abstract(obj) + def self.abstract_method!(obj) file, line, meth = caller_parts - self.new("#{MESSAGE} -- must be implemented by the subclass" % [meth, obj, obj.class]) + raise AbstractMethodError.new("#{MESSAGE} -- must be implemented by the subclass" % [meth, obj, obj.class]) end end