system_navigation 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0877e736a201bde13a94bb2fd54462777c30878
4
+ data.tar.gz: 0f072306d97e8b2d3f2697c3601e279bc5b6ffdd
5
+ SHA512:
6
+ metadata.gz: 4d9106cdc061404ae1ab11859ce526994ed3e478d4f21fc200304af7cf56bff513bb4c6b2fb1485ddb6963048d7eaa722e257920269a50f971eee7c92a30fc4a
7
+ data.tar.gz: 41cb455b27e76c9bb4b54d5197d25a17f7823dc5f661ddebd3cdc5a15dc513834afb347279dda38f16f5da338bc709d30d174bf5166ab23a151c08f0c8085d16
@@ -0,0 +1,6 @@
1
+ System Navigation changelog
2
+ ===========================
3
+
4
+ ### v0.1.0 (June 11, 2015)
5
+
6
+ * Initial release (sorta, not ready yet)
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2015 Kyrylo Silin
2
+
3
+ This software is provided 'as-is', without any express or implied
4
+ warranty. In no event will the authors be held liable for any damages
5
+ arising from the use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+
19
+ 3. This notice may not be removed or altered from any source distribution.
@@ -0,0 +1,70 @@
1
+ SystemNavigation
2
+ ==
3
+
4
+ * Repository: [https://github.com/kyrylo/system_navigation/](https://github.com/kyrylo/system_navigation/)
5
+
6
+ Description
7
+ -----------
8
+
9
+ SystemNavigation is a Ruby library that provides support for the navigation and
10
+ introspection of Ruby programs. It is inspired by the eponymous class in
11
+ Pharo/Squeak.
12
+
13
+ Sneak peek:
14
+
15
+ ```ruby
16
+ module M
17
+ def increment
18
+ @num + 1
19
+ end
20
+ end
21
+
22
+ class A
23
+ include M
24
+
25
+ attr_reader :num
26
+
27
+ def initialize(num)
28
+ @num = num
29
+ end
30
+ end
31
+
32
+ sn = SystemNavigation.default
33
+
34
+ sn.all_accesses(to: :@num, from: A)
35
+ #=> [#<UnboundMethod: A#num>, #<UnboundMethod: A(M)#increment>, #<UnboundMethod: A#initialize>]
36
+ ```
37
+
38
+ And many more...
39
+
40
+ Installation
41
+ ------------
42
+
43
+ All you need is to install the gem.
44
+
45
+ gem install system_navigation
46
+
47
+ Synopsis
48
+ ---
49
+
50
+ ### Precaution
51
+
52
+ The library is very young and its API is far from stable. Some behaviours
53
+ might be unexpected or bugged. Feel free to file issues if you feel like the
54
+ result you expect isn't what you actually get.
55
+
56
+ ### API
57
+
58
+ See the [API.md](/docs/API.md) file.
59
+
60
+ Limitations
61
+ -----------
62
+
63
+ Supports *only* CRuby.
64
+
65
+ * CRuby 2.2.2 and higher
66
+
67
+ License
68
+ -------
69
+
70
+ The project uses Zlib License. See LICENCE.txt file for more information.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,374 @@
1
+ require 'fast_method_source'
2
+
3
+ require 'forwardable'
4
+ require 'strscan'
5
+ require 'ripper'
6
+
7
+ require_relative 'system_navigation/array_refinement'
8
+ require_relative 'system_navigation/module_refinement'
9
+ require_relative 'system_navigation/ruby_environment'
10
+ require_relative 'system_navigation/instruction_stream'
11
+ require_relative 'system_navigation/expression_tree'
12
+ require_relative 'system_navigation/method_query'
13
+ require_relative 'system_navigation/compiled_method'
14
+ require_relative 'system_navigation/method_hash'
15
+ require_relative 'system_navigation/ancestor_method_finder'
16
+ require_relative 'system_navigation/instruction_stream/decoder'
17
+ require_relative 'system_navigation/instruction_stream/instruction'
18
+ require_relative 'system_navigation/instruction_stream/instruction/attr_instruction'
19
+
20
+ class SystemNavigation
21
+ # The VERSION file must be in the root directory of the library.
22
+ VERSION_FILE = File.expand_path('../../VERSION', __FILE__)
23
+
24
+ VERSION = File.exist?(VERSION_FILE) ?
25
+ File.read(VERSION_FILE).chomp : '(could not find VERSION file)'
26
+
27
+ using ModuleRefinement
28
+
29
+ extend Forwardable
30
+ def_delegators :@environment,
31
+ :all_behaviors, :all_classes, :all_classes_and_modules,
32
+ :all_modules, :all_objects
33
+
34
+ def self.default
35
+ self.new
36
+ end
37
+
38
+ def initialize
39
+ @environment = SystemNavigation::RubyEnvironment.new
40
+ end
41
+
42
+ ##
43
+ # Query methods for instance variables in descending (subclasses) and
44
+ # ascending (superclasses) fashion.
45
+ #
46
+ # @example Global
47
+ # class A
48
+ # def initialize
49
+ # @foo = 1
50
+ # end
51
+ # end
52
+ #
53
+ # class B
54
+ # attr_reader :foo
55
+ # end
56
+ #
57
+ # sn.all_accesses(to: :@foo)
58
+ # #=> [#<UnboundMethod: A#initialize>, #<UnboundMethod: B#foo>]
59
+ #
60
+ # @example Local
61
+ # class A
62
+ # def initialize
63
+ # @foo = 1
64
+ # end
65
+ # end
66
+ #
67
+ # class B
68
+ # attr_reader :foo
69
+ # end
70
+ #
71
+ # sn.all_accesses(to: :@foo, from: B)
72
+ # #=> [#<UnboundMethod: B#foo>]
73
+ #
74
+ # @example Only get invokations
75
+ # class A
76
+ # def initialize
77
+ # @foo = 1
78
+ # end
79
+ # end
80
+ #
81
+ # class B
82
+ # attr_reader :foo
83
+ # end
84
+ #
85
+ # sn.all_accesses(to: :@foo, only_get: true)
86
+ # #=> [#<UnboundMethod: B#foo>]
87
+ #
88
+ # @param to [Symbol] The name of the instance variable to search for
89
+ # @param from [Class] The class that limits the scope of the query. Optional.
90
+ # If omitted, performs the query starting from the top of the object
91
+ # hierarchy (BasicObject)
92
+ # @param only_get [Boolean] Limits the scope of the query only to methods that
93
+ # write into the +ivar+. Optional. Mutually exclusive with +only_set+
94
+ # @param only_set [Boolean] Limits the scope of the query only to methods that
95
+ # read from the +ivar+. Optional. Mutually exclusive with +only_get+
96
+ # @return [Array<UnboundMethod>] methods that access the +ivar+ according to
97
+ # the given scope
98
+ # @note This is a very costly operation, if you don't provide the +from+
99
+ # argument
100
+ def all_accesses(to:, from: nil, only_get: nil, only_set: nil)
101
+ if only_set && only_get
102
+ fail ArgumentError, 'both only_get and only_set were provided'
103
+ end
104
+
105
+ if from && !from.instance_of?(Class)
106
+ fail TypeError, "from must be a Class (#{from.class} given)"
107
+ end
108
+
109
+ (from || BasicObject).with_all_sub_and_superclasses.flat_map do |klass|
110
+ klass.select_methods_that_access(to, only_get, only_set)
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Query methods for literals they call.
116
+ #
117
+ # @example Global
118
+ # class A
119
+ # def foo
120
+ # :hello
121
+ # end
122
+ # end
123
+ #
124
+ # class B
125
+ # def bar
126
+ # :hello
127
+ # end
128
+ # end
129
+ #
130
+ # sn.all_calls(on: :hello)
131
+ # #=> [#<UnboundMethod: A#foo>, #<UnboundMethod: B#bar>]
132
+ #
133
+ # @example Local
134
+ # class A
135
+ # def foo
136
+ # :hello
137
+ # end
138
+ # end
139
+ #
140
+ # class B
141
+ # def bar
142
+ # :hello
143
+ # end
144
+ # end
145
+ #
146
+ # sn.all_calls(on: :hello, from: A)
147
+ # #=> [#<UnboundMethod: A#foo>]
148
+ #
149
+ # @example Gem
150
+ # sn.all_calls(on: :singleton, gem: 'system_navigation')
151
+ # #=> [...]
152
+ #
153
+ # @param on [Boolean, Integer, Float, String, Symbol, Array, Hash, Range,
154
+ # Regexp] The literal to search for
155
+ # @param from [Class, Module] The behaviour that limits the scope of the
156
+ # query. If it's present, the search will be performed from top to bottom
157
+ # (only subclasses). Optional
158
+ # @param gem [String] Limits the scope of the query only to methods
159
+ # that are defined in the RubyGem +gem+ classes and modules. Optional.
160
+ # @return [Array<UnboundMethod>] methods that call the given +literal+
161
+ # @note This is a very costly operation, if you don't provide the +from+
162
+ # argument
163
+ # @note The list of supported literals can be found here:
164
+ # http://ruby-doc.org/core-2.2.2/doc/syntax/literals_rdoc.html
165
+ def all_calls(on:, from: nil, gem: nil)
166
+ if from && gem
167
+ fail ArgumentError, 'both from and gem were provided'
168
+ end
169
+
170
+ subject = if from
171
+ from.with_all_subclasses
172
+ elsif gem
173
+ self.all_classes_and_modules_in_gem_named(gem)
174
+ else
175
+ self.all_classes_and_modules
176
+ end
177
+
178
+ subject.flat_map { |behavior| behavior.select_methods_that_refer_to(on) }
179
+ end
180
+
181
+ ##
182
+ # Query classes for the methods they implement.
183
+ #
184
+ # @example
185
+ # sn.all_classes_implementing(:~)
186
+ # #=> [Regexp, Bignum, Fixnum]
187
+ #
188
+ # @!macro [new] selector.param
189
+ # @param selector [Symbol] the name of the method to be searched for
190
+ # @return [Array<Class>] classes that implement +selector+
191
+ def all_classes_implementing(selector)
192
+ self.all_classes.select { |klass| klass.includes_selector?(selector) }
193
+ end
194
+
195
+ ##
196
+ # Query modules for the methods they implement.
197
+ #
198
+ # @example
199
+ # sn.all_classes_implementing(:select)
200
+ # #=> [Enumerable, Kernel, #<Module:0x007f56daf92918>]
201
+ #
202
+ # @!macro selector.param
203
+ # @return [Array<Class>] modules that implement +selector+
204
+ def all_modules_implementing(selector)
205
+ self.all_modules.select { |mod| mod.includes_selector?(selector) }
206
+ end
207
+
208
+ ##
209
+ # Query classes and modules for the methods they implement.
210
+ #
211
+ # @example
212
+ # sn.all_implementors_of(:select)
213
+ # #=> [Enumerator::Lazy, IO, ..., #<Class:Kernel>]
214
+ #
215
+ # @!macro selector.param
216
+ # @return [Array<Class, Module>] classes and modules that implement +selector+
217
+ def all_implementors_of(selector)
218
+ self.all_classes_and_modules.select do |klass|
219
+ klass.includes_selector?(selector)
220
+ end
221
+ end
222
+
223
+ ##
224
+ # Query gems for classes they implement.
225
+ #
226
+ # @example
227
+ # sn.all_classes_in_gem_named('system_navigation')
228
+ # #=> [SystemNavigation::AncestorMethodFinder, ..., SystemNavigation]
229
+ #
230
+ # @!macro [new] gem.param
231
+ # @param gem [String] The name of the gem. Case sensitive
232
+ # @return [Array<Class>] classes that were defined by +gem+
233
+ def all_classes_in_gem_named(gem)
234
+ self.all_classes.select { |klass| klass.belongs_to?(gem) }
235
+ end
236
+
237
+ ##
238
+ # Query gems for modules they implement.
239
+ #
240
+ # @example
241
+ # sn.all_modules_in_gem_named('pry-theme')
242
+ # #=> [PryTheme::Theme::DefaultAttrs, ..., PryTheme]
243
+ #
244
+ # @!macro [new] gem.param
245
+ # @return [Array<Class>] modules that were defined by +gem+
246
+ def all_modules_in_gem_named(gem)
247
+ self.all_modules.select { |mod| mod.belongs_to?(gem) }
248
+ end
249
+
250
+ ##
251
+ # Query gems for classes and modules they implement.
252
+ #
253
+ # @example
254
+ # sn.all_classes_and_modules_in_gem_named('pry-theme')
255
+ # #=> [PryTheme::Preview, ..., PryTheme::Color256]
256
+ #
257
+ # @!macro [new] gem.param
258
+ # @return [Array<Class, Module>] classes and modules that were defined by
259
+ # +gem+
260
+ def all_classes_and_modules_in_gem_named(gem)
261
+ self.all_classes_and_modules.select { |klassmod| klassmod.belongs_to?(gem) }
262
+ end
263
+
264
+ ##
265
+ # Get all methods defined in current Ruby process.
266
+ #
267
+ # @example
268
+ # sn.all_methods
269
+ # #=> [#<UnboundMethod: Gem::Dependency#name>, ...]
270
+ #
271
+ # @return [Array<UnboundMethod>] all methods that exist
272
+ def all_methods
273
+ self.all_classes_and_modules.map do |klassmod|
274
+ klassmod.own_methods.as_array
275
+ end.flatten
276
+ end
277
+
278
+ ##
279
+ # Search for a string in all classes and modules including their comments and
280
+ # names.
281
+ #
282
+ # @example
283
+ # class A
284
+ # def foo
285
+ # :hello_hi
286
+ # end
287
+ # end
288
+ #
289
+ # class B
290
+ # def bar
291
+ # 'hello_hi'
292
+ # end
293
+ # end
294
+ #
295
+ # module M
296
+ # # hello_hi
297
+ # def baz
298
+ # end
299
+ # end
300
+ #
301
+ #
302
+ # sn.all_methods_with_source(string: 'hello_hi')
303
+ # #=> [#<UnboundMethod: B#bar>, #<UnboundMethod: A#foo>, #<UnboundMethod: M#foo>]
304
+ #
305
+ # @param string [String] The string to be searched for
306
+ # @param match_case [Boolean] Whether to match case or not. Optional
307
+ # @return [Array<UnboundMethod>] methods that matched +string+
308
+ # @note This is a very costly operation
309
+ def all_methods_with_source(string:, match_case: true)
310
+ return [] if string.empty?
311
+
312
+ self.all_classes_and_modules.flat_map do |klassmod|
313
+ klassmod.select_matching_methods(string, match_case)
314
+ end
315
+ end
316
+
317
+ ##
318
+ # Get all methods implemented in C.
319
+ #
320
+ # @example
321
+ # sn.all_c_methods
322
+ # #=> [#<UnboundMethod: #<Class:Etc>#getlogin>, ...]
323
+ #
324
+ # @return [Array<UnboundMethod>] all methods that were implemented in C
325
+ def all_c_methods
326
+ self.all_classes_and_modules.flat_map do |klassmod|
327
+ klassmod.select_c_methods
328
+ end
329
+ end
330
+
331
+ ##
332
+ # Get all methods implemented in Ruby.
333
+ #
334
+ # @example
335
+ # sn.all_rb_methods
336
+ # #=> [#<UnboundMethod: Gem::Dependency#name>, ...]
337
+ #
338
+ # @return [Array<UnboundMethod>] all methods that were implemented in Ruby
339
+ def all_rb_methods
340
+ self.all_classes_and_modules.flat_map do |klassmod|
341
+ klassmod.select_rb_methods
342
+ end
343
+ end
344
+
345
+ ##
346
+ # Get all methods that implement +message+.
347
+ #
348
+ # @example
349
+ # sn.all_senders_of(:puts)
350
+ # #=> []
351
+ #
352
+ # @param message [Symbol] The name of the method you're interested in
353
+ # @return [Array<UnboundMethod>] all methods that send +message
354
+ def all_senders_of(message)
355
+ self.all_classes_and_modules.flat_map do |klassmod|
356
+ klassmod.select_senders_of(message)
357
+ end
358
+ end
359
+
360
+ ##
361
+ # Get all messages that all methods send.
362
+ #
363
+ # @example
364
+ # sn.all_sent_messages
365
+ # #=> [:name, :hash, ..., :type]
366
+ #
367
+ # @return [Array<Symbol>] all unique messages
368
+ # @note This is a very costly operation
369
+ def all_sent_messages
370
+ self.all_classes_and_modules.flat_map do |klassmod|
371
+ klassmod.all_messages.as_array
372
+ end.uniq
373
+ end
374
+ end