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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENCE.txt +19 -0
- data/README.md +70 -0
- data/VERSION +1 -0
- data/lib/system_navigation.rb +374 -0
- data/lib/system_navigation/ancestor_method_finder.rb +44 -0
- data/lib/system_navigation/array_refinement.rb +21 -0
- data/lib/system_navigation/compiled_method.rb +94 -0
- data/lib/system_navigation/expression_tree.rb +95 -0
- data/lib/system_navigation/instruction_stream.rb +33 -0
- data/lib/system_navigation/instruction_stream/decoder.rb +91 -0
- data/lib/system_navigation/instruction_stream/instruction.rb +180 -0
- data/lib/system_navigation/instruction_stream/instruction/attr_instruction.rb +90 -0
- data/lib/system_navigation/method_hash.rb +49 -0
- data/lib/system_navigation/method_query.rb +98 -0
- data/lib/system_navigation/module_refinement.rb +226 -0
- data/lib/system_navigation/ruby_environment.rb +75 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
data/LICENCE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|