method_lister 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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