method_lister 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,208 @@
1
+ About
2
+ =====
3
+
4
+ Method Lister is used to query objects and discover which ancestor implements
5
+ which methods. It's quite common to have a lot of mixins and several classes
6
+ in an object's class hierarchy, especially in a Rails application. To help
7
+ with this Method Lister adds the ability to find out in which classes/modules
8
+ the methods on an object are implemented.
9
+
10
+ Method Lister adds 3 methods to all objects in the system: `ls`, `grep`, and
11
+ `which`. Since these names are sometimes taken you can also use `mls`,
12
+ `mgrep`, and `mwhich`.
13
+
14
+ Method Lister is intended to be used from IRB or during debugging.
15
+
16
+ Install
17
+ =======
18
+
19
+ Get the gem:
20
+
21
+ # Add GitHub as a Gem Source (only have to do this once)
22
+ gem sources -a http://gems.github.com
23
+
24
+ # Install the gem
25
+ sudo gem install matthew-method_lister
26
+
27
+ # Otherwise, build the gem and install it
28
+ rake gem
29
+ sudo gem install pkg/*.gem
30
+
31
+ Open up `~/.irbrc` and add these lines:
32
+
33
+ require 'rubygems'
34
+ require 'method_lister'
35
+
36
+ Usage
37
+ =====
38
+
39
+ `ls` or `mls`
40
+ -------------
41
+
42
+ The `ls` command will list all methods an object responds to, organized by the
43
+ module or class which provides the implementation. For example (results may
44
+ vary, depending on what you have loaded):
45
+
46
+ >> [].ls
47
+ ========== Module Kernel ==========
48
+ PUBLIC: == === =~ __id__ __send__ class clone display dup eql? equal?
49
+ extend freeze frozen? gem grep hash id inspect instance_eval instance_of?
50
+ instance_variable_defined? instance_variable_get instance_variable_set
51
+ instance_variables is_a? kind_of? ls method methods mgrep mls mwhich nil?
52
+ object_id pretty_inspect private_methods protected_methods public_methods
53
+ require respond_to? send singleton_methods taint tainted? to_a to_s type
54
+ untaint which
55
+
56
+ PRIVATE: Array Float Integer String URI ` abort at_exit autoload autoload?
57
+ binding block_given? callcc caller catch chomp chomp! chop chop! eval exec
58
+ exit exit! fail fork format gem_original_require getc gets
59
+ global_variables gsub gsub! initialize_copy iterator? lambda load
60
+ local_variables loop method_missing open p pp print printf proc putc puts
61
+ raise rand readline readlines remove_instance_variable scan select
62
+ set_trace_func singleton_method_added singleton_method_removed
63
+ singleton_method_undefined sleep split sprintf srand sub sub! syscall
64
+ system test throw trace_var trap untrace_var warn
65
+
66
+ ========== Module PP::ObjectMixin ==========
67
+ PUBLIC: pretty_print pretty_print_cycle pretty_print_inspect
68
+ pretty_print_instance_variables
69
+
70
+ ========== Class Object ==========
71
+ PRIVATE: initialize irb_binding timeout
72
+
73
+ ========== Module Enumerable ==========
74
+ PUBLIC: all? any? collect detect each_with_index entries find find_all
75
+ grep include? inject map max member? min partition reject select sort
76
+ sort_by to_a zip
77
+
78
+ ========== Class Array ==========
79
+ PUBLIC: & * + - << <=> == [] []= assoc at clear collect collect! compact
80
+ compact! concat delete delete_at delete_if each each_index empty? eql?
81
+ fetch fill first flatten flatten! frozen? hash include? index indexes
82
+ indices insert inspect join last length map map! nitems pack pop
83
+ pretty_print pretty_print_cycle push rassoc reject reject! replace reverse
84
+ reverse! reverse_each rindex select shift size slice slice! sort sort!
85
+ to_a to_ary to_s transpose uniq uniq! unshift values_at zip |
86
+
87
+ PRIVATE: initialize initialize_copy
88
+
89
+ You can show only the public methods by passing in "true":
90
+
91
+ >> [].ls true
92
+ ========== Module Kernel ==========
93
+ PUBLIC: == === =~ __id__ __send__ class clone display dup eql? equal?
94
+ extend freeze frozen? gem grep hash id inspect instance_eval instance_of?
95
+ instance_variable_defined? instance_variable_get instance_variable_set
96
+ instance_variables is_a? kind_of? ls method methods mgrep mls mwhich nil?
97
+ object_id pretty_inspect private_methods protected_methods public_methods
98
+ require respond_to? send singleton_methods taint tainted? to_a to_s type
99
+ untaint which
100
+
101
+ ========== Module PP::ObjectMixin ==========
102
+ PUBLIC: pretty_print pretty_print_cycle pretty_print_inspect
103
+ pretty_print_instance_variables
104
+
105
+ ========== Module Enumerable ==========
106
+ PUBLIC: all? any? collect detect each_with_index entries find find_all
107
+ grep include? inject map max member? min partition reject select sort
108
+ sort_by to_a zip
109
+
110
+ ========== Class Array ==========
111
+ PUBLIC: & * + - << <=> == [] []= assoc at clear collect collect! compact
112
+ compact! concat delete delete_at delete_if each each_index empty? eql?
113
+ fetch fill first flatten flatten! frozen? hash include? index indexes
114
+ indices insert inspect join last length map map! nitems pack pop
115
+ pretty_print pretty_print_cycle push rassoc reject reject! replace reverse
116
+ reverse! reverse_each rindex select shift size slice slice! sort sort!
117
+ to_a to_ary to_s transpose uniq uniq! unshift values_at zip |
118
+
119
+ `grep` or `mgrep`
120
+ -----------------
121
+
122
+ The `grep` command takes a regular expression and only returns methods which
123
+ match the given regex. In this example we'll use `mgrep` since on Array
124
+ objects `grep` is already taken:
125
+
126
+ >> [].mgrep /f/
127
+ ========== Module Kernel ==========
128
+ PUBLIC: freeze frozen? instance_of? instance_variable_defined? kind_of?
129
+
130
+ PRIVATE: fail fork format method_missing printf set_trace_func
131
+ singleton_method_undefined sprintf
132
+
133
+ ========== Module Enumerable ==========
134
+ PUBLIC: find find_all
135
+
136
+ ========== Class Array ==========
137
+ PUBLIC: delete_if fetch fill first flatten flatten! frozen? shift unshift
138
+
139
+ Similar to `ls` you can pass in an extra argument of "true" to see only the
140
+ public methods:
141
+
142
+ >> [].mgrep /f/, true
143
+ ========== Module Kernel ==========
144
+ PUBLIC: freeze frozen? instance_of? instance_variable_defined? kind_of?
145
+
146
+ ========== Module Enumerable ==========
147
+ PUBLIC: find find_all
148
+
149
+ ========== Class Array ==========
150
+ PUBLIC: delete_if fetch fill first flatten flatten! frozen? shift unshift
151
+
152
+ Note that `method_missing` is always considered a match, since it could always
153
+ potentially execute.
154
+
155
+ `which` or `mwhich`
156
+ -------------------
157
+
158
+ The `which` command is for finding which classes or modules implement the
159
+ method you're seeking. You can pass the method name in as a string or symbol.
160
+
161
+ >> [].which :to_a
162
+ ========== Module Kernel ==========
163
+ PUBLIC: to_a
164
+
165
+ ========== Module Enumerable ==========
166
+ PUBLIC: to_a
167
+
168
+ ========== Class Array ==========
169
+ PUBLIC: to_a
170
+
171
+ Logically the `which` command is the same as `grep(/^your_method$/)` and so
172
+ the same comments apply about `method_missing` and the optional parameter to
173
+ see only public methods.
174
+
175
+ Known Bugs
176
+ ==========
177
+
178
+ If a singleton method overrides some method from an ancestor then the method
179
+ will be reported on the ancestor only and not both the ancestor and the
180
+ eigenclass. For example:
181
+
182
+ >> class Foo; def doit; end; end
183
+ => nil
184
+
185
+ >> f = Foo.new
186
+ => #<Foo:0x3395a0>
187
+
188
+ >> class << f; def doit; end; end
189
+ => nil
190
+
191
+ >> f.mgrep /doit/
192
+ ========== Module Kernel ==========
193
+ PRIVATE: method_missing
194
+
195
+ ========== Class Foo ==========
196
+ PUBLIC: doit
197
+
198
+ This was done on purpose to support listing singleton methods on cloned
199
+ objects. I couldn't support both features since the reflection methods for
200
+ eigenclasses are buggy.
201
+
202
+ License
203
+ =======
204
+
205
+ Copyright 2008, 2009, Matthew O'Connor All rights reserved.
206
+
207
+ This program is free software; you can redistribute it and/or modify it under
208
+ the same terms as Ruby 1.8.7 itself.
@@ -0,0 +1,77 @@
1
+ module MethodLister
2
+ class ColorDisplay < SimpleDisplay
3
+ def initialize
4
+ @ansi = AnsiEscape.new
5
+ end
6
+
7
+ private
8
+
9
+ def location_description(result)
10
+ color(super, :yellow_fg, :bold)
11
+ end
12
+
13
+ def color_method_overloaded_from_kernel(source, method)
14
+ if source != Kernel and Kernel.instance_methods.member? method
15
+ color(method, :green_fg)
16
+ end
17
+ end
18
+
19
+ def color_method_missing(source, method)
20
+ color(method, :red_fg) if method == "method_missing"
21
+ end
22
+
23
+ def color_method_array_primative(source, method)
24
+ exempt_sources = [Kernel, Fixnum, Hash, String, Enumerable, Array]
25
+ unless exempt_sources.member? source
26
+ primatives = %w{[] []= each <<}
27
+ color(method, :magenta_fg) if primatives.member? method
28
+ end
29
+ end
30
+
31
+ def process_method(result, method)
32
+ color_method(result.object, method)
33
+ end
34
+
35
+ def color_method(source, method)
36
+ coloring_methods.each do |coloring_method|
37
+ colored_method = self.send(coloring_method, source, method)
38
+ return colored_method if colored_method
39
+ end
40
+ method
41
+ end
42
+
43
+ def coloring_methods
44
+ private_methods.select {|method| method =~ /^color_method_/}
45
+ end
46
+
47
+ def color(string, *colors)
48
+ output_is_to_tty? ? @ansi.color_string(string, *colors) : string
49
+ end
50
+
51
+ def output_is_to_tty?
52
+ $stdout.tty?
53
+ end
54
+ end
55
+
56
+ class AnsiEscape
57
+ Colors = {
58
+ :none => 0, :black_fg => 30, :red_fg => 31, :green_fg => 32,
59
+ :yellow_fg => 33, :blue_fg => 34, :magenta_fg => 35, :cyan_fg => 36,
60
+ :white_fg => 37, :black_bg => 40, :red_bg => 41, :green_bg => 42,
61
+ :yellow_bg => 43, :blue_bg => 44, :magenta_bg => 45, :cyan_bg => 46,
62
+ :white_bg => 47, :bold => 1, :underline => 4, :blink => 5,
63
+ :reverse => 7, :invisible => 8
64
+ }
65
+
66
+ def color_string(string, *colors)
67
+ color_code(*colors) + string + color_code(:none)
68
+ end
69
+
70
+ private
71
+
72
+ def color_code(*colors)
73
+ ansi_codes = colors.map {|c| Colors[c]}.compact.join(";")
74
+ "\e[#{ansi_codes}m" unless ansi_codes.empty?
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,50 @@
1
+ module MethodLister
2
+ class FindResult
3
+ attr_reader :object
4
+ VISIBILITIES = [:public, :protected, :private]
5
+
6
+ def initialize(object, options={})
7
+ @object = object
8
+ @methods = Hash.new
9
+ VISIBILITIES.each do |visibility|
10
+ @methods[visibility] = options[visibility] || Array.new
11
+ end
12
+ end
13
+
14
+ def methods(visibility)
15
+ if visibility == :all
16
+ VISIBILITIES.inject(Array.new) { |result, viz| result + methods(viz) }
17
+ elsif VISIBILITIES.include? visibility
18
+ @methods[visibility]
19
+ else
20
+ raise ArgumentError, "Unknown visibility #{visibility.inspect}"
21
+ end.sort
22
+ end
23
+
24
+ def has_methods?(visibility=:all)
25
+ !methods(visibility).empty?
26
+ end
27
+
28
+ def narrow_to_methods_matching!(rx)
29
+ VISIBILITIES.each do |visibility|
30
+ @methods[visibility] = @methods[visibility].select do |method|
31
+ method =~ rx || method == "method_missing"
32
+ end
33
+ end
34
+ self
35
+ end
36
+
37
+ def ==(other)
38
+ object.eql?(other.object) &&
39
+ methods(:all).sort == other.methods(:all).sort
40
+ end
41
+
42
+ def inspect
43
+ repr = "object=#{object.inspect}\n"
44
+ VISIBILITIES.each do |visibility|
45
+ repr += "#{visibility}=#{methods(visibility).sort.inspect}\n"
46
+ end
47
+ repr
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,113 @@
1
+ module MethodLister
2
+ class Finder
3
+ def find_all(object)
4
+ @results, @seen = Array.new, Hash.new
5
+ record_methods_for_eigenclass(object)
6
+ search_class_hierarchy(object.class)
7
+ @results
8
+ end
9
+
10
+ def ls(object)
11
+ find_all(object).select { |results| results.has_methods? }
12
+ end
13
+
14
+ def grep(rx, object)
15
+ ls(object).map do |result|
16
+ result.narrow_to_methods_matching!(rx)
17
+ end.select { |result| result.has_methods? }
18
+ end
19
+
20
+ def which(method, object)
21
+ grep(/^#{Regexp.escape(method.to_s)}$/, object)
22
+ end
23
+
24
+ private
25
+
26
+ def search_class_hierarchy(klass)
27
+ while klass
28
+ scan :class, klass
29
+ klass = klass.superclass
30
+ end
31
+ end
32
+
33
+ def scan(type, klass_or_module)
34
+ unless @seen.has_key? klass_or_module
35
+ @seen[klass_or_module] = true
36
+ record_methods_for klass_or_module
37
+ scan_modules(type, klass_or_module)
38
+ end
39
+ end
40
+
41
+ def scan_modules(type, klass_or_module)
42
+ modules_for(type, klass_or_module).each do |a_module|
43
+ scan :module, a_module
44
+ end
45
+ end
46
+
47
+ def record_methods_for(klass_or_module)
48
+ record_result(
49
+ klass_or_module,
50
+ :public => klass_or_module.public_instance_methods(false),
51
+ :protected => klass_or_module.protected_instance_methods(false),
52
+ :private => klass_or_module.private_instance_methods(false)
53
+ )
54
+ end
55
+
56
+ def record_result(*args)
57
+ @results << FindResult.new(*args)
58
+ end
59
+
60
+ def modules_for(obj_type, klass_or_module)
61
+ case obj_type
62
+ when :module
63
+ klass_or_module.included_modules
64
+ when :class
65
+ if superclass = klass_or_module.superclass
66
+ klass_or_module.included_modules - superclass.included_modules
67
+ else
68
+ klass_or_module.included_modules
69
+ end
70
+ when :eigenclass
71
+ eigenclass = get_eigenclass(klass_or_module)
72
+ eigenclass.included_modules - klass_or_module.class.included_modules
73
+ end
74
+ end
75
+
76
+ # This method is awfully complicated and does not give accurate answers
77
+ # because the reflection methods for eigenclasses do not work correctly.
78
+ # The singleton_methods(false) call will return all public and protected
79
+ # singleton methods, but not the private ones. And the
80
+ # *_instance_methods(false) calls will include the methods defined on the
81
+ # class in addition to singleton methods. c.f the following test scenarios:
82
+ # mixed_visibility_methods.rb and cloned_eigenclass.rb
83
+ def record_methods_for_eigenclass(object)
84
+ @seen[object] = true
85
+ return unless eigenclass = get_eigenclass(object)
86
+
87
+ our_public_methods = eigenclass.public_instance_methods(false)
88
+ our_protected_methods = eigenclass.protected_instance_methods(false)
89
+ our_private_methods = eigenclass.private_instance_methods(false)
90
+
91
+ ancestor_methods = Hash.new
92
+ [:public, :protected, :private].each do |method_type|
93
+ ancestor_methods[method_type] = eigenclass.ancestors.map do |ancestor|
94
+ ancestor.send("#{method_type}_instance_methods", false)
95
+ end.flatten.uniq
96
+ end
97
+
98
+ record_result(object,
99
+ :public => our_public_methods - ancestor_methods[:public],
100
+ :protected => our_protected_methods - ancestor_methods[:protected],
101
+ :private => our_private_methods - ancestor_methods[:private]
102
+ )
103
+
104
+ scan_modules(:eigenclass, object)
105
+ end
106
+
107
+ def get_eigenclass(object)
108
+ class << object; self; end
109
+ rescue TypeError
110
+ nil
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,24 @@
1
+ module Kernel
2
+ def mls(show_public_only=false,
3
+ displayer=MethodLister::ColorDisplay.new,
4
+ finder=MethodLister::Finder.new)
5
+ displayer.display finder.ls(self), show_public_only
6
+ end
7
+ alias :ls :mls
8
+
9
+ def mgrep(regex,
10
+ show_public_only=false,
11
+ displayer=MethodLister::ColorDisplay.new,
12
+ finder=MethodLister::Finder.new)
13
+ displayer.display finder.grep(regex, self), show_public_only
14
+ end
15
+ alias :grep :mgrep
16
+
17
+ def mwhich(method,
18
+ show_public_only=false,
19
+ displayer=MethodLister::ColorDisplay.new,
20
+ finder=MethodLister::Finder.new)
21
+ displayer.display finder.which(method, self), show_public_only
22
+ end
23
+ alias :which :mwhich
24
+ end
@@ -0,0 +1,55 @@
1
+ module MethodLister
2
+ class SimpleDisplay
3
+ def display(findings, show_public_only=false)
4
+ findings.reverse.each do |result|
5
+ list = method_list(result, show_public_only)
6
+ if !list.empty?
7
+ puts header(result)
8
+ puts list
9
+ puts seperator(result)
10
+ end
11
+ end
12
+ nil
13
+ end
14
+
15
+ private
16
+
17
+ def header(result)
18
+ "========== #{location_description(result)} =========="
19
+ end
20
+
21
+ def method_list(result, show_public_only)
22
+ FindResult::VISIBILITIES.map do |visibility|
23
+ (visibility == :public || !show_public_only) ?
24
+ method_set(result, visibility) :
25
+ nil
26
+ end.compact.join("\n\n")
27
+ end
28
+
29
+ def method_set(result, visibility)
30
+ if result.has_methods?(visibility)
31
+ "#{visibility.to_s.upcase}: " +
32
+ result.methods(visibility).map {|method|
33
+ process_method(result, method)
34
+ }.join(" ")
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ def process_method(result, method)
41
+ method
42
+ end
43
+
44
+ def seperator(result)
45
+ ""
46
+ end
47
+
48
+ def location_description(result)
49
+ object = result.object
50
+ return "Class #{object}" if object.kind_of? Class
51
+ return "Module #{object}" if object.kind_of? Module
52
+ return "Eigenclass"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'method_lister/find_result'
4
+ require 'method_lister/finder'
5
+ require 'method_lister/simple_display'
6
+ require 'method_lister/color_display'
7
+ require 'method_lister/ruby_ext'
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+
3
+ describe MethodLister::ColorDisplay do
4
+ describe "#display" do
5
+ before do
6
+ @results = [
7
+ result(Object.new, :public => ["foo"]),
8
+ result(Array, :public => ["bar"] + Array.public_instance_methods(false)),
9
+ result(Kernel, :public => ["baz"] + Kernel.public_instance_methods(false)),
10
+ ]
11
+
12
+ @displayer = MethodLister::ColorDisplay.new
13
+
14
+ @output = ""
15
+ stub(@displayer).puts do |*args|
16
+ @output += args.map {|arg| arg.to_s}.join("") + "\n"
17
+ end
18
+ end
19
+
20
+ it "attempts to write out the relevant information" do
21
+ @displayer.display @results
22
+ @output.should =~ /Module Kernel.*.*Class Array.*bar.*Eigenclass.*foo/m
23
+ end
24
+
25
+ it "does not add ANSI codes if output is not a tty" do
26
+ mock(@displayer).output_is_to_tty? { false }.times(any_times)
27
+ @displayer.display @results
28
+ @output.should_not =~ /\e\[0m;/m
29
+ end
30
+
31
+ it "does add ANSI codes if output is a tty" do
32
+ mock(@displayer).output_is_to_tty? { true }.times(any_times)
33
+ @displayer.display @results
34
+ @output.should =~ /\e\[0m/m
35
+ end
36
+ end
37
+ end