summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/rbot/core/utils/where_is.rb106
-rw-r--r--lib/rbot/plugins.rb14
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,