method_extensions 0.0.1 → 0.0.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/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
|