diff options
-rw-r--r-- | lib/rbot/core/utils/where_is.rb | 106 | ||||
-rw-r--r-- | lib/rbot/plugins.rb | 14 |
2 files changed, 108 insertions, 12 deletions
diff --git a/lib/rbot/core/utils/where_is.rb b/lib/rbot/core/utils/where_is.rb new file mode 100644 index 00000000..bbf6b76f --- /dev/null +++ b/lib/rbot/core/utils/where_is.rb @@ -0,0 +1,106 @@ +# A little Ruby module for finding the source location where class and methods are defined. +# https://gist.github.com/wtaysom/1236979 +module Where + class <<self + def is_proc(proc) + source_location(proc) + end + + def is_method(klass, method_name) + source_location(klass.method(method_name)) + end + + def is_instance_method(klass, method_name) + source_location(klass.instance_method(method_name)) + end + + def are_methods(klass, method_name) + are_via_extractor(:method, klass, method_name) + end + + def are_instance_methods(klass, method_name) + are_via_extractor(:method, klass, method_name) + end + + def is_class(klass) + methods = defined_methods(klass) + file_groups = methods.group_by{|sl| sl[0]} + file_counts = file_groups.map do |file, sls| + lines = sls.map{|sl| sl[1]} + count = lines.size + line = lines.min + {file: file, count: count, line: line} + end + file_counts.sort_by!{|fc| fc[:count]} + source_locations = file_counts.map{|fc| [fc[:file], fc[:line]]} + source_locations + end + + # Raises ArgumentError if klass does not have any Ruby methods defined in it. + def is_class_primarily(klass) + source_locations = is_class(klass) + if source_locations.empty? + methods = defined_methods(klass) + raise ArgumentError, (methods.empty? ? + "#{klass} has no methods" : + "#{klass} only has built-in methods (#{methods.size} in total)" + ) + end + source_locations[0] + end + + def edit(location) + unless location.kind_of?(Array) + raise TypeError, + "only accepts a [file, line_number] array" + end + location + end + + private + + def source_location(method) + method.source_location || ( + method.to_s =~ /: (.*)>/ + $1 + ) + end + + def are_via_extractor(extractor, klass, method_name) + methods = klass.ancestors.map do |ancestor| + method = ancestor.send(extractor, method_name) + if method.owner == ancestor + source_location(method) + else + nil + end + end + methods.compact! + methods + end + + def defined_methods(klass) + methods = klass.methods(false).map{|m| klass.method(m)} + + klass.instance_methods(false).map{|m| klass.instance_method(m)} + methods.map!(&:source_location) + methods.compact! + methods + end + end +end + +def where_is(klass, method = nil) + begin + Where.edit( + if method + begin + Where.is_instance_method(klass, method) + rescue NameError + Where.is_method(klass, method) + end + else + Where.is_class_primarily(klass) + end).first + end +end + diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb index cf145c83..e40cfcc4 100644 --- a/lib/rbot/plugins.rb +++ b/lib/rbot/plugins.rb @@ -4,6 +4,7 @@ # :title: rbot plugin management require 'singleton' +require_relative './core/utils/where_is.rb' module Irc class Bot @@ -603,17 +604,6 @@ module Plugins debug "loading #{desc}#{fname}" plugin_module.module_eval(plugin_string, fname) - # this sets a BOTMODULE_FNAME constant in all BotModule - # classes defined in the module. This allows us to know - # the filename the plugin was declared in from outside - # the plugin itself (from within, a __FILE__ would work.) - plugin_module.constants.each do |const| - cls = plugin_module.const_get(const) - if cls.is_a? Class and cls < BotModule - cls.const_set("BOTMODULE_FNAME", fname) - end - end - return :loaded rescue Exception => err # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err @@ -800,7 +790,7 @@ module Plugins if botmodule @failed.clear @ignored.clear - filename = botmodule.class::BOTMODULE_FNAME + filename = where_is(botmodule.class) err = load_botmodule_file(filename, "plugin") if err.is_a? Exception @failed << { :name => botmodule.to_s, |