method_extensions 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/method_extensions/method/source_with_doc.rb +180 -178
- data/lib/method_extensions/method/super.rb +206 -205
- data/lib/method_extensions.rb +2 -1
- data/method_extensions.gemspec +1 -1
- metadata +3 -5
@@ -5,211 +5,213 @@ rescue LoadError
|
|
5
5
|
ripper_available = false
|
6
6
|
end
|
7
7
|
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# Returns comment preceding the method definition by parsing the file
|
35
|
-
# returned by `Method#source_location`
|
36
|
-
#
|
37
|
-
# Sample IRB session:
|
38
|
-
#
|
39
|
-
# ruby-1.9.2-head > require 'fileutils'
|
40
|
-
#
|
41
|
-
# ruby-1.9.2-head > puts FileUtils.method(:mkdir).doc
|
42
|
-
# #
|
43
|
-
# # Options: mode noop verbose
|
44
|
-
# #
|
45
|
-
# # Creates one or more directories.
|
46
|
-
# #
|
47
|
-
# # FileUtils.mkdir 'test'
|
48
|
-
# # FileUtils.mkdir %w( tmp data )
|
49
|
-
# # FileUtils.mkdir 'notexist', :noop => true # Does not really create.
|
50
|
-
# # FileUtils.mkdir 'tmp', :mode => 0700
|
51
|
-
# #
|
52
|
-
def doc
|
53
|
-
MethodDocRipper.doc_from_source_location(source_location)
|
54
|
-
end
|
8
|
+
module MethodExtensions
|
9
|
+
module MethodSourceWithDoc
|
10
|
+
# Returns method source by parsing the file returned by `Method#source_location`.
|
11
|
+
#
|
12
|
+
# If method definition cannot be found `ArgumentError` exception is raised
|
13
|
+
# (this includes methods defined `attr_accessor`, `module_eval` etc.).
|
14
|
+
#
|
15
|
+
# Sample IRB session:
|
16
|
+
#
|
17
|
+
# ruby-1.9.2-head > require 'fileutils'
|
18
|
+
#
|
19
|
+
# ruby-1.9.2-head > puts FileUtils.method(:mkdir).source
|
20
|
+
# def mkdir(list, options = {})
|
21
|
+
# fu_check_options options, OPT_TABLE['mkdir']
|
22
|
+
# list = fu_list(list)
|
23
|
+
# fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
|
24
|
+
# return if options[:noop]
|
25
|
+
#
|
26
|
+
# list.each do |dir|
|
27
|
+
# fu_mkdir dir, options[:mode]
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# => nil
|
31
|
+
def source
|
32
|
+
MethodSourceRipper.source_from_source_location(source_location)
|
33
|
+
end
|
55
34
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
# # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
|
78
|
-
# def scoped(options = {}, &block)
|
79
|
-
# if options.present?
|
80
|
-
# relation = scoped.apply_finder_options(options)
|
81
|
-
# block_given? ? relation.extending(Module.new(&block)) : relation
|
82
|
-
# else
|
83
|
-
# current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
|
84
|
-
# end
|
85
|
-
# end
|
86
|
-
#
|
87
|
-
# ruby-1.9.2-head > ActiveRecord::Base.method(:unscoped).source_with_doc
|
88
|
-
# => def unscoped
|
89
|
-
# @unscoped ||= Relation.new(self, arel_table)
|
90
|
-
# finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# ruby-1.9.2-head > ActiveRecord::Relation.instance_method(:find).source_with_doc
|
94
|
-
# => # Find operates with four different retrieval approaches:
|
95
|
-
# ...
|
96
|
-
# def find(*args, &block)
|
97
|
-
# return to_a.find(&block) if block_given?
|
98
|
-
#
|
99
|
-
# options = args.extract_options!
|
100
|
-
#
|
101
|
-
# if options.present?
|
102
|
-
# ...
|
103
|
-
def source_with_doc
|
104
|
-
return unless source_location
|
105
|
-
|
106
|
-
[doc.to_s.chomp, source_unindent(source)].compact.reject(&:empty?).join("\n")
|
107
|
-
end
|
35
|
+
# Returns comment preceding the method definition by parsing the file
|
36
|
+
# returned by `Method#source_location`
|
37
|
+
#
|
38
|
+
# Sample IRB session:
|
39
|
+
#
|
40
|
+
# ruby-1.9.2-head > require 'fileutils'
|
41
|
+
#
|
42
|
+
# ruby-1.9.2-head > puts FileUtils.method(:mkdir).doc
|
43
|
+
# #
|
44
|
+
# # Options: mode noop verbose
|
45
|
+
# #
|
46
|
+
# # Creates one or more directories.
|
47
|
+
# #
|
48
|
+
# # FileUtils.mkdir 'test'
|
49
|
+
# # FileUtils.mkdir %w( tmp data )
|
50
|
+
# # FileUtils.mkdir 'notexist', :noop => true # Does not really create.
|
51
|
+
# # FileUtils.mkdir 'tmp', :mode => 0700
|
52
|
+
# #
|
53
|
+
def doc
|
54
|
+
MethodDocRipper.doc_from_source_location(source_location)
|
55
|
+
end
|
108
56
|
|
109
|
-
|
110
|
-
|
111
|
-
|
57
|
+
# ruby-1.9.2-head > irb_context.inspect_mode = false # turn off inspect mode so that we can view sources
|
58
|
+
#
|
59
|
+
# ruby-1.9.2-head > ActiveRecord::Base.method(:find).source_with_doc
|
60
|
+
# ArgumentError: failed to find method definition around the lines:
|
61
|
+
# delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
|
62
|
+
# delegate :find_each, :find_in_batches, :to => :scoped
|
63
|
+
#
|
64
|
+
# ruby-1.9.2-head > ActiveRecord::Base.method(:scoped).source_with_doc
|
65
|
+
# # Returns an anonymous scope.
|
66
|
+
# #
|
67
|
+
# # posts = Post.scoped
|
68
|
+
# # posts.size # Fires "select count(*) from posts" and returns the count
|
69
|
+
# # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
70
|
+
# #
|
71
|
+
# # fruits = Fruit.scoped
|
72
|
+
# # fruits = fruits.where(:colour => 'red') if options[:red_only]
|
73
|
+
# # fruits = fruits.limit(10) if limited?
|
74
|
+
# #
|
75
|
+
# # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
|
76
|
+
# # intermediate values (scopes) around as first-class objects is convenient.
|
77
|
+
# #
|
78
|
+
# # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
|
79
|
+
# def scoped(options = {}, &block)
|
80
|
+
# if options.present?
|
81
|
+
# relation = scoped.apply_finder_options(options)
|
82
|
+
# block_given? ? relation.extending(Module.new(&block)) : relation
|
83
|
+
# else
|
84
|
+
# current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# ruby-1.9.2-head > ActiveRecord::Base.method(:unscoped).source_with_doc
|
89
|
+
# => def unscoped
|
90
|
+
# @unscoped ||= Relation.new(self, arel_table)
|
91
|
+
# finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# ruby-1.9.2-head > ActiveRecord::Relation.instance_method(:find).source_with_doc
|
95
|
+
# => # Find operates with four different retrieval approaches:
|
96
|
+
# ...
|
97
|
+
# def find(*args, &block)
|
98
|
+
# return to_a.find(&block) if block_given?
|
99
|
+
#
|
100
|
+
# options = args.extract_options!
|
101
|
+
#
|
102
|
+
# if options.present?
|
103
|
+
# ...
|
104
|
+
def source_with_doc
|
105
|
+
return unless source_location
|
112
106
|
|
113
|
-
|
107
|
+
[doc.to_s.chomp, source_unindent(source)].compact.reject(&:empty?).join("\n")
|
108
|
+
end
|
114
109
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
indent_level = indented_lines.
|
119
|
-
reject { |line| line.strip.empty? }. # exclude empty lines from indent level calculation
|
120
|
-
map { |line| line[/^(\s*)/, 1].size }. # map to indent level of every line
|
121
|
-
min
|
122
|
-
[lines[0], *indented_lines.map { |line| line[indent_level .. -1] }].join("\n")
|
123
|
-
end
|
124
|
-
|
125
|
-
class ::Method
|
126
|
-
include MethodSourceWithDoc
|
127
|
-
end
|
110
|
+
def full_inspect
|
111
|
+
"#{ inspect }\n#{ source_location }\n#{ source_with_doc }"
|
112
|
+
end
|
128
113
|
|
129
|
-
|
130
|
-
include MethodSourceWithDoc
|
131
|
-
end
|
114
|
+
private
|
132
115
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
116
|
+
def source_unindent(src)
|
117
|
+
lines = src.split("\n")
|
118
|
+
indented_lines = lines[1 .. -1] # first line doesn't have proper indentation
|
119
|
+
indent_level = indented_lines.
|
120
|
+
reject { |line| line.strip.empty? }. # exclude empty lines from indent level calculation
|
121
|
+
map { |line| line[/^(\s*)/, 1].size }. # map to indent level of every line
|
122
|
+
min
|
123
|
+
[lines[0], *indented_lines.map { |line| line[indent_level .. -1] }].join("\n")
|
137
124
|
end
|
138
125
|
|
139
|
-
|
140
|
-
|
141
|
-
@src_lines = IO.read(filename).split("\n")
|
142
|
-
@method_definition_lineno = method_definition_lineno
|
126
|
+
class ::Method
|
127
|
+
include MethodSourceWithDoc
|
143
128
|
end
|
144
129
|
|
145
|
-
|
146
|
-
|
147
|
-
if @method_source
|
148
|
-
@method_source
|
149
|
-
else
|
150
|
-
raise ArgumentError.new("failed to find method definition around the lines:\n" <<
|
151
|
-
definition_lines.join("\n"))
|
152
|
-
end
|
130
|
+
class ::UnboundMethod
|
131
|
+
include MethodSourceWithDoc
|
153
132
|
end
|
154
133
|
|
155
|
-
|
156
|
-
|
157
|
-
|
134
|
+
class MethodSourceRipper < Ripper
|
135
|
+
def self.source_from_source_location(source_location)
|
136
|
+
return unless source_location
|
137
|
+
new(*source_location).method_source
|
138
|
+
end
|
158
139
|
|
159
|
-
|
160
|
-
|
161
|
-
|
140
|
+
def initialize(filename, method_definition_lineno)
|
141
|
+
super(IO.read(filename), filename)
|
142
|
+
@src_lines = IO.read(filename).split("\n")
|
143
|
+
@method_definition_lineno = method_definition_lineno
|
162
144
|
end
|
163
|
-
end
|
164
145
|
|
165
|
-
|
166
|
-
|
167
|
-
|
146
|
+
def method_source
|
147
|
+
parse
|
148
|
+
if @method_source
|
149
|
+
@method_source
|
150
|
+
else
|
151
|
+
raise ArgumentError.new("failed to find method definition around the lines:\n" <<
|
152
|
+
definition_lines.join("\n"))
|
153
|
+
end
|
154
|
+
end
|
168
155
|
|
169
|
-
|
156
|
+
def definition_lines
|
157
|
+
@src_lines[@method_definition_lineno - 1 .. @method_definition_lineno + 1]
|
158
|
+
end
|
170
159
|
|
171
|
-
|
172
|
-
|
160
|
+
Ripper::SCANNER_EVENTS.each do |meth|
|
161
|
+
define_method("on_#{ meth }") do |*args|
|
162
|
+
[lineno, column]
|
163
|
+
end
|
164
|
+
end
|
173
165
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
end
|
166
|
+
def on_def(name, params, body)
|
167
|
+
from_lineno, from_column = name
|
168
|
+
return unless @method_definition_lineno == from_lineno
|
178
169
|
|
179
|
-
|
180
|
-
def self.doc_from_source_location(source_location)
|
181
|
-
return unless source_location
|
182
|
-
new(*source_location).method_doc
|
183
|
-
end
|
170
|
+
to_lineno, to_column = lineno, column
|
184
171
|
|
185
|
-
|
186
|
-
|
187
|
-
@method_definition_lineno = method_definition_lineno
|
188
|
-
@last_comment_block = nil
|
189
|
-
end
|
172
|
+
@method_source = @src_lines[from_lineno - 1 .. to_lineno - 1].join("\n").strip
|
173
|
+
end
|
190
174
|
|
191
|
-
|
192
|
-
|
193
|
-
|
175
|
+
def on_defs(target, period, name, params, body)
|
176
|
+
on_def(target, params, body)
|
177
|
+
end
|
194
178
|
end
|
195
179
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
end
|
180
|
+
class MethodDocRipper < Ripper
|
181
|
+
def self.doc_from_source_location(source_location)
|
182
|
+
return unless source_location
|
183
|
+
new(*source_location).method_doc
|
184
|
+
end
|
202
185
|
|
186
|
+
def initialize(filename, method_definition_lineno)
|
187
|
+
super(IO.read(filename), filename)
|
188
|
+
@method_definition_lineno = method_definition_lineno
|
203
189
|
@last_comment_block = nil
|
204
190
|
end
|
205
|
-
end
|
206
191
|
|
207
|
-
|
208
|
-
|
209
|
-
|
192
|
+
def method_doc
|
193
|
+
parse
|
194
|
+
@method_doc
|
195
|
+
end
|
196
|
+
|
197
|
+
Ripper::SCANNER_EVENTS.each do |meth|
|
198
|
+
define_method("on_#{ meth }") do |token|
|
199
|
+
if @last_comment_block &&
|
200
|
+
lineno == @method_definition_lineno
|
201
|
+
@method_doc = @last_comment_block.join.gsub(/^\s*/, "")
|
202
|
+
end
|
203
|
+
|
204
|
+
@last_comment_block = nil
|
205
|
+
end
|
206
|
+
end
|
210
207
|
|
211
|
-
|
212
|
-
|
208
|
+
def on_comment(token)
|
209
|
+
(@last_comment_block ||= []) << token
|
210
|
+
end
|
211
|
+
|
212
|
+
def on_sp(token)
|
213
|
+
@last_comment_block << token if @last_comment_block
|
214
|
+
end
|
213
215
|
end
|
214
216
|
end
|
215
|
-
end if ripper_available
|
217
|
+
end if ripper_available
|
@@ -1,273 +1,274 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@methods_all
|
30
|
-
|
31
|
-
|
1
|
+
module MethodExtensions
|
2
|
+
module MethodSuper
|
3
|
+
# Returns method which will be called if given Method/UnboundMethod would
|
4
|
+
# call `super`.
|
5
|
+
# Implementation is incomplete with regard to modules included in singleton
|
6
|
+
# class (`class C; extend M; end`), but such modules usually don't use `super`
|
7
|
+
# anyway.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# class Base
|
12
|
+
# def meth; end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class Derived < Base
|
16
|
+
# def meth; end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# ruby-1.9.2-head > Derived.instance_method(:meth)
|
20
|
+
# => #<UnboundMethod: Derived#meth>
|
21
|
+
#
|
22
|
+
# ruby-1.9.2-head > Derived.instance_method(:meth).super
|
23
|
+
# => #<UnboundMethod: Base#meth>
|
24
|
+
def super
|
25
|
+
raise ArgumentError, "method doesn't have required @context_for_super instance variable set" unless @context_for_super
|
26
|
+
|
27
|
+
klass, level, name = @context_for_super.values_at(:klass, :level, :name)
|
28
|
+
|
29
|
+
unless @methods_all
|
30
|
+
@methods_all = MethodSuper.methods_all(klass)
|
31
|
+
|
32
|
+
# on first call ignore first found method
|
33
|
+
superclass_index = MethodSuper.superclass_index(@methods_all,
|
34
|
+
level,
|
35
|
+
name)
|
36
|
+
@methods_all = @methods_all[superclass_index + 1 .. -1]
|
37
|
+
|
38
|
+
end
|
39
|
+
|
32
40
|
superclass_index = MethodSuper.superclass_index(@methods_all,
|
33
41
|
level,
|
34
42
|
name)
|
35
|
-
@methods_all = @methods_all[superclass_index + 1 .. -1]
|
36
|
-
|
37
|
-
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
name)
|
44
|
+
superclass = @methods_all[superclass_index].keys.first
|
45
|
+
rest_methods_all = @methods_all[superclass_index + 1 .. -1]
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
super_method = if level == :class && superclass.class == Class
|
48
|
+
superclass.method(name)
|
49
|
+
elsif level == :instance ||
|
50
|
+
(level == :class && superclass.class == Module)
|
51
|
+
superclass.instance_method(name)
|
52
|
+
end
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
(level == :class && superclass.class == Module)
|
50
|
-
superclass.instance_method(name)
|
54
|
+
super_method.instance_variable_set(:@context_for_super, @context_for_super)
|
55
|
+
super_method.instance_variable_set(:@methods_all, rest_methods_all)
|
56
|
+
super_method
|
51
57
|
end
|
52
58
|
|
53
|
-
|
54
|
-
super_method.instance_variable_set(:@methods_all, rest_methods_all)
|
55
|
-
super_method
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
+
private
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
def self.superclass_index(methods_all, level, name)
|
62
|
+
methods_all.index do |ancestor_with_methods|
|
63
|
+
ancestor, methods =
|
64
|
+
ancestor_with_methods.keys.first, ancestor_with_methods.values.first
|
65
|
+
methods[level] && methods[level].any? do |level, methods|
|
66
|
+
methods.include?(name)
|
67
|
+
end
|
66
68
|
end
|
67
69
|
end
|
68
|
-
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
class Methods
|
75
|
-
def initialize(klass_or_module, options = {})
|
76
|
-
@klass_or_module = klass_or_module
|
77
|
-
@ancestor_name_formatter = options.fetch(:ancestor_name_formatter,
|
78
|
-
default_ancestor_name_formatter)
|
79
|
-
@exclude_trite = options.fetch(:exclude_trite, true)
|
71
|
+
def self.methods_all(klass)
|
72
|
+
MethodSuper::Methods.new(klass, :ancestor_name_formatter => proc { |ancestor, _| ancestor }).all
|
80
73
|
end
|
81
74
|
|
82
|
-
|
83
|
-
|
84
|
-
|
75
|
+
class Methods
|
76
|
+
def initialize(klass_or_module, options = {})
|
77
|
+
@klass_or_module = klass_or_module
|
78
|
+
@ancestor_name_formatter = options.fetch(:ancestor_name_formatter,
|
79
|
+
default_ancestor_name_formatter)
|
80
|
+
@exclude_trite = options.fetch(:exclude_trite, true)
|
81
|
+
end
|
82
|
+
|
83
|
+
def all
|
84
|
+
@all ||= find_all
|
85
|
+
end
|
85
86
|
|
86
|
-
|
87
|
+
VISIBILITIES = [ :public, :protected, :private ].freeze
|
87
88
|
|
88
|
-
|
89
|
+
protected
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
def default_ancestor_name_formatter
|
92
|
+
proc do |ancestor, singleton|
|
93
|
+
ancestor_name(ancestor, singleton)
|
94
|
+
end
|
93
95
|
end
|
94
|
-
end
|
95
96
|
|
96
|
-
|
97
|
-
|
97
|
+
def find_all
|
98
|
+
ancestors = [] # flattened ancestors (both normal and singleton)
|
98
99
|
|
99
|
-
|
100
|
-
|
100
|
+
(@klass_or_module.ancestors - trite_ancestors).each do |ancestor|
|
101
|
+
ancestor_singleton = ancestor.singleton_class
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
103
|
+
# Modules don't inherit class methods from included modules
|
104
|
+
unless @klass_or_module.instance_of?(Module) && ancestor != @klass_or_module
|
105
|
+
class_methods = collect_instance_methods(ancestor_singleton)
|
106
|
+
end
|
106
107
|
|
107
|
-
|
108
|
+
instance_methods = collect_instance_methods(ancestor)
|
108
109
|
|
109
|
-
|
110
|
-
|
110
|
+
append_ancestor_entry(ancestors, @ancestor_name_formatter[ancestor, false],
|
111
|
+
class_methods, instance_methods)
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
(singleton_ancestors(ancestor) || []).each do |singleton_ancestor|
|
114
|
+
class_methods = collect_instance_methods(singleton_ancestor)
|
115
|
+
append_ancestor_entry(ancestors, @ancestor_name_formatter[singleton_ancestor, true],
|
116
|
+
class_methods)
|
117
|
+
end
|
116
118
|
end
|
117
|
-
end
|
118
119
|
|
119
|
-
|
120
|
-
|
120
|
+
ancestors
|
121
|
+
end
|
121
122
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
123
|
+
# singleton ancestors which ancestor introduced
|
124
|
+
def singleton_ancestors(ancestor)
|
125
|
+
@singleton_ancestors ||= all_singleton_ancestors
|
126
|
+
@singleton_ancestors[ancestor]
|
127
|
+
end
|
127
128
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
129
|
+
def all_singleton_ancestors
|
130
|
+
all = {}
|
131
|
+
seen = []
|
132
|
+
(@klass_or_module.ancestors - trite_ancestors).reverse.each do |ancestor|
|
133
|
+
singleton_ancestors = ancestor.singleton_class.ancestors - trite_singleton_ancestors
|
134
|
+
introduces = singleton_ancestors - seen
|
135
|
+
all[ancestor] = introduces unless introduces.empty?
|
136
|
+
seen.concat singleton_ancestors
|
137
|
+
end
|
138
|
+
all
|
136
139
|
end
|
137
|
-
all
|
138
|
-
end
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def ancestor_name(ancestor, singleton)
|
142
|
+
"#{ singleton ? "S" : ""}[#{ ancestor.is_a?(Class) ? "C" : "M" }] #{ ancestor.name || ancestor.to_s }"
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
145
|
+
# ancestor is included only when contributes some methods
|
146
|
+
def append_ancestor_entry(ancestors, ancestor, class_methods, instance_methods = nil)
|
147
|
+
if class_methods || instance_methods
|
148
|
+
ancestor_entry = {}
|
149
|
+
ancestor_entry[:class] = class_methods if class_methods
|
150
|
+
ancestor_entry[:instance] = instance_methods if instance_methods
|
151
|
+
ancestors << {ancestor => ancestor_entry}
|
152
|
+
end
|
151
153
|
end
|
152
|
-
end
|
153
154
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
155
|
+
# Returns hash { :public => [...public methods...],
|
156
|
+
# :protected => [...private methods...],
|
157
|
+
# :private => [...private methods...] }
|
158
|
+
# keys with empty values are excluded,
|
159
|
+
# when no methods are found - returns nil
|
160
|
+
def collect_instance_methods(klass)
|
161
|
+
methods_with_visibility = VISIBILITIES.map do |visibility|
|
162
|
+
methods = klass.send("#{ visibility }_instance_methods", false)
|
163
|
+
[visibility, methods] unless methods.empty?
|
164
|
+
end.compact
|
165
|
+
Hash[methods_with_visibility] unless methods_with_visibility.empty?
|
166
|
+
end
|
166
167
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
def trite_singleton_ancestors
|
169
|
+
return [] unless @exclude_trite
|
170
|
+
@trite_singleton_ancestors ||= Class.new.singleton_class.ancestors
|
171
|
+
end
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
173
|
+
def trite_ancestors
|
174
|
+
return [] unless @exclude_trite
|
175
|
+
@trite_ancestors ||= Class.new.ancestors
|
176
|
+
end
|
175
177
|
end
|
176
178
|
end
|
177
|
-
end
|
178
|
-
|
179
|
-
class Method
|
180
|
-
include MethodSuper
|
181
|
-
end
|
182
179
|
|
183
|
-
class
|
184
|
-
|
185
|
-
end
|
186
|
-
|
187
|
-
class Module
|
188
|
-
def instance_method_with_ancestors_for_super(name)
|
189
|
-
method = instance_method_without_ancestors_for_super(name)
|
190
|
-
method.instance_variable_set(:@context_for_super,
|
191
|
-
:klass => self,
|
192
|
-
:level => :instance,
|
193
|
-
:name => name)
|
194
|
-
|
195
|
-
method
|
180
|
+
class ::Method
|
181
|
+
include MethodSuper
|
196
182
|
end
|
197
183
|
|
198
|
-
|
199
|
-
|
200
|
-
alias_method :instance_method_without_ancestors_for_super, :instance_method
|
201
|
-
alias_method :instance_method, :instance_method_with_ancestors_for_super
|
184
|
+
class ::UnboundMethod
|
185
|
+
include MethodSuper
|
202
186
|
end
|
203
|
-
end
|
204
|
-
|
205
|
-
module Kernel
|
206
|
-
def method_with_ancestors_for_super(name)
|
207
|
-
method = method_without_ancestors_for_super(name)
|
208
187
|
|
209
|
-
|
188
|
+
class ::Module
|
189
|
+
def instance_method_with_ancestors_for_super(name)
|
190
|
+
method = instance_method_without_ancestors_for_super(name)
|
210
191
|
method.instance_variable_set(:@context_for_super,
|
211
192
|
:klass => self,
|
212
|
-
:level => :class,
|
213
|
-
:name => name)
|
214
|
-
else
|
215
|
-
method.instance_variable_set(:@context_for_super,
|
216
|
-
:klass => self.class,
|
217
193
|
:level => :instance,
|
218
194
|
:name => name)
|
195
|
+
|
196
|
+
method
|
219
197
|
end
|
220
|
-
|
221
|
-
method
|
222
|
-
end
|
223
198
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
199
|
+
unless method_defined?(:instance_method_without_ancestors_for_super) ||
|
200
|
+
private_method_defined?(:instance_method_without_ancestors_for_super)
|
201
|
+
alias_method :instance_method_without_ancestors_for_super, :instance_method
|
202
|
+
alias_method :instance_method, :instance_method_with_ancestors_for_super
|
203
|
+
end
|
228
204
|
end
|
229
|
-
end
|
230
205
|
|
231
|
-
module
|
232
|
-
|
233
|
-
|
234
|
-
|
206
|
+
module ::Kernel
|
207
|
+
def method_with_ancestors_for_super(name)
|
208
|
+
method = method_without_ancestors_for_super(name)
|
209
|
+
|
210
|
+
if respond_to?(:ancestors)
|
211
|
+
method.instance_variable_set(:@context_for_super,
|
212
|
+
:klass => self,
|
213
|
+
:level => :class,
|
214
|
+
:name => name)
|
215
|
+
else
|
216
|
+
method.instance_variable_set(:@context_for_super,
|
217
|
+
:klass => self.class,
|
218
|
+
:level => :instance,
|
219
|
+
:name => name)
|
220
|
+
end
|
221
|
+
|
222
|
+
method
|
223
|
+
end
|
235
224
|
|
236
|
-
|
237
|
-
|
225
|
+
unless method_defined?(:method_without_ancestors_for_super) ||
|
226
|
+
private_method_defined?(:method_without_ancestors_for_super)
|
227
|
+
alias_method :method_without_ancestors_for_super, :method
|
228
|
+
alias_method :method, :method_with_ancestors_for_super
|
229
|
+
end
|
238
230
|
end
|
239
231
|
end
|
240
232
|
|
241
|
-
|
242
|
-
|
243
|
-
|
233
|
+
if $PROGRAM_NAME == __FILE__
|
234
|
+
require "rspec/core"
|
235
|
+
require "rspec/expectations"
|
236
|
+
require "rspec/matchers"
|
244
237
|
|
245
|
-
|
238
|
+
module BaseIncludedModule
|
239
|
+
def module_meth
|
240
|
+
end
|
246
241
|
end
|
247
242
|
|
248
|
-
|
243
|
+
module BaseExtendedModule
|
244
|
+
def module_meth
|
245
|
+
end
|
249
246
|
end
|
250
|
-
end
|
251
247
|
|
252
|
-
class
|
253
|
-
|
254
|
-
|
248
|
+
class BaseClass
|
249
|
+
include BaseIncludedModule
|
250
|
+
extend BaseExtendedModule
|
255
251
|
|
256
|
-
|
257
|
-
|
252
|
+
def self.singleton_meth
|
253
|
+
end
|
258
254
|
|
259
|
-
|
255
|
+
def meth
|
256
|
+
end
|
260
257
|
end
|
261
258
|
|
262
|
-
|
263
|
-
|
264
|
-
end
|
259
|
+
class DerivedClass < BaseClass
|
260
|
+
def self.singleton_meth
|
261
|
+
end
|
265
262
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
263
|
+
def self.module_meth
|
264
|
+
end
|
265
|
+
|
266
|
+
def meth
|
267
|
+
end
|
270
268
|
|
269
|
+
def module_meth
|
270
|
+
end
|
271
|
+
end
|
271
272
|
|
272
273
|
describe Method do
|
273
274
|
describe "#super" do
|
data/lib/method_extensions.rb
CHANGED
data/method_extensions.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.unshift lib unless $:.include?(lib)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "method_extensions"
|
6
|
-
s.version = "0.0.
|
6
|
+
s.version = "0.0.2"
|
7
7
|
s.authors = ["Evgeniy Dolzhenko"]
|
8
8
|
s.email = ["dolzenko@gmail.com"]
|
9
9
|
s.homepage = "http://github.com/dolzenko/method_extensions"
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Evgeniy Dolzhenko
|
@@ -42,7 +42,6 @@ rdoc_options: []
|
|
42
42
|
require_paths:
|
43
43
|
- lib
|
44
44
|
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
45
|
requirements:
|
47
46
|
- - ">="
|
48
47
|
- !ruby/object:Gem::Version
|
@@ -50,7 +49,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
49
|
- 0
|
51
50
|
version: "0"
|
52
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
52
|
requirements:
|
55
53
|
- - ">="
|
56
54
|
- !ruby/object:Gem::Version
|
@@ -60,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
58
|
requirements: []
|
61
59
|
|
62
60
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.3.
|
61
|
+
rubygems_version: 1.3.6
|
64
62
|
signing_key:
|
65
63
|
specification_version: 3
|
66
64
|
summary: Method object extensions for better code navigation
|