]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/utils/where_is.rb
plugin(search): fix search and gcalc, closes #28, #29
[user/henk/code/ruby/rbot.git] / lib / rbot / core / utils / where_is.rb
1 # A little Ruby module for finding the source location where class and methods are defined.
2 # https://gist.github.com/wtaysom/1236979
3 module Where
4   class <<self
5     def is_proc(proc)
6       source_location(proc)
7     end
8
9     def is_method(klass, method_name)
10       source_location(klass.method(method_name))
11     end
12
13     def is_instance_method(klass, method_name)
14       source_location(klass.instance_method(method_name))
15     end
16
17     def are_methods(klass, method_name)
18       are_via_extractor(:method, klass, method_name)
19     end
20
21     def are_instance_methods(klass, method_name)
22       are_via_extractor(:method, klass, method_name)
23     end
24
25     def is_class(klass)
26       methods = defined_methods(klass)
27       file_groups = methods.group_by{|sl| sl[0]}
28       file_counts = file_groups.map do |file, sls|
29         lines = sls.map{|sl| sl[1]}
30         count = lines.size
31         line = lines.min
32         {file: file, count: count, line: line}
33       end
34       file_counts.sort_by!{|fc| fc[:count]}
35       source_locations = file_counts.map{|fc| [fc[:file], fc[:line]]}
36       source_locations
37     end
38
39     # Raises ArgumentError if klass does not have any Ruby methods defined in it.
40     def is_class_primarily(klass)
41       source_locations = is_class(klass)
42       if source_locations.empty?
43         methods = defined_methods(klass)
44         raise ArgumentError, (methods.empty? ?
45                               "#{klass} has no methods" :
46                               "#{klass} only has built-in methods (#{methods.size} in total)"
47                              )
48       end
49       source_locations[0]
50     end
51
52     def edit(location)
53       unless location.kind_of?(Array)
54         raise TypeError,
55           "only accepts a [file, line_number] array"
56       end
57       location
58     end
59
60     private
61
62     def source_location(method)
63       method.source_location || (
64         method.to_s =~ /: (.*)>/
65         $1
66       )
67     end
68
69     def are_via_extractor(extractor, klass, method_name)
70       methods = klass.ancestors.map do |ancestor|
71         method = ancestor.send(extractor, method_name)
72         if method.owner == ancestor
73           source_location(method)
74         else
75           nil
76         end
77       end
78       methods.compact!      
79       methods
80     end
81
82     def defined_methods(klass)
83       methods = klass.methods(false).map{|m| klass.method(m)} +
84         klass.instance_methods(false).map{|m| klass.instance_method(m)}
85       methods.map!(&:source_location)
86       methods.compact!
87       methods
88     end
89   end
90 end
91
92 def where_is(klass, method = nil)
93   begin
94     Where.edit(
95       if method
96         begin
97           Where.is_instance_method(klass, method)
98         rescue NameError
99           Where.is_method(klass, method)
100         end
101       else
102         Where.is_class_primarily(klass)
103       end).first
104   end
105 end
106