mirrors 0.0.1

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +2 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +23 -0
  6. data/LICENSE.txt +59 -0
  7. data/README.md +1 -0
  8. data/asdfasdf.rb +53 -0
  9. data/bin/bundler +17 -0
  10. data/bin/byebug +17 -0
  11. data/bin/testunit +8 -0
  12. data/circle.yml +6 -0
  13. data/dev.yml +6 -0
  14. data/lib/mirrors.rb +150 -0
  15. data/lib/mirrors/class_mirror.rb +197 -0
  16. data/lib/mirrors/class_mixin.rb +11 -0
  17. data/lib/mirrors/field_mirror.rb +24 -0
  18. data/lib/mirrors/field_mirror/class_variable_mirror.rb +23 -0
  19. data/lib/mirrors/field_mirror/constant_mirror.rb +34 -0
  20. data/lib/mirrors/field_mirror/instance_variable_mirror.rb +23 -0
  21. data/lib/mirrors/hook.rb +33 -0
  22. data/lib/mirrors/index/indexer.rb +6 -0
  23. data/lib/mirrors/index/marker.rb +40 -0
  24. data/lib/mirrors/invoke.rb +29 -0
  25. data/lib/mirrors/method_mirror.rb +206 -0
  26. data/lib/mirrors/mirror.rb +37 -0
  27. data/lib/mirrors/object_mirror.rb +25 -0
  28. data/lib/mirrors/package_inference.rb +164 -0
  29. data/lib/mirrors/package_inference/class_to_file_resolver.rb +66 -0
  30. data/lib/mirrors/package_mirror.rb +33 -0
  31. data/lib/mirrors/visitors/disasm_visitor.rb +11 -0
  32. data/lib/mirrors/visitors/iseq_visitor.rb +84 -0
  33. data/lib/mirrors/visitors/references_visitor.rb +58 -0
  34. data/lib/mirrors/visitors/yasmdata.rb +212 -0
  35. data/lol.rb +35 -0
  36. data/mirrors.gemspec +19 -0
  37. data/test/fixtures/class.rb +29 -0
  38. data/test/fixtures/field.rb +9 -0
  39. data/test/fixtures/method.rb +15 -0
  40. data/test/fixtures/object.rb +5 -0
  41. data/test/fixtures/reflect.rb +14 -0
  42. data/test/mirrors/class_mirror_test.rb +87 -0
  43. data/test/mirrors/field_mirror_test.rb +125 -0
  44. data/test/mirrors/iseq_visitor_test.rb +56 -0
  45. data/test/mirrors/marker_test.rb +48 -0
  46. data/test/mirrors/method_mirror_test.rb +62 -0
  47. data/test/mirrors/object_mirror_test.rb +16 -0
  48. data/test/mirrors/package_inference_test.rb +31 -0
  49. data/test/mirrors/references_visitor_test.rb +30 -0
  50. data/test/mirrors_test.rb +38 -0
  51. data/test/test_helper.rb +12 -0
  52. metadata +137 -0
@@ -0,0 +1,11 @@
1
+ # mixin, convenience operator for method reflection
2
+ # inspired by Smalltalk
3
+ class Class
4
+ def >>(symbol)
5
+ Mirrors.reflect(method(symbol))
6
+ end
7
+
8
+ def reflect
9
+ Mirrors.reflect(self)
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Mirrors
2
+ # A class to reflect on instance, class, and class instance variables,
3
+ # as well as constants.
4
+ class FieldMirror < Mirror
5
+ Field = Struct.new(:object, :name)
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(obj)
10
+ super
11
+ @object = obj.object
12
+ @name = obj.name.to_s
13
+ end
14
+
15
+ # @return [ClassMirror] The class this method was originally defined in
16
+ def defining_class
17
+ Mirrors.reflect(@object)
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'mirrors/field_mirror/class_variable_mirror'
23
+ require 'mirrors/field_mirror/instance_variable_mirror'
24
+ require 'mirrors/field_mirror/constant_mirror'
@@ -0,0 +1,23 @@
1
+ module Mirrors
2
+ class ClassVariableMirror < FieldMirror
3
+ def value
4
+ Mirrors.reflect(@object.class_variable_get(@name))
5
+ end
6
+
7
+ def value=(o)
8
+ @object.class_variable_set(@name, o)
9
+ end
10
+
11
+ def public?
12
+ false
13
+ end
14
+
15
+ def protected?
16
+ false
17
+ end
18
+
19
+ def private?
20
+ true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ module Mirrors
2
+ class ConstantMirror < FieldMirror
3
+ def value
4
+ if path = @object.autoload?(@name)
5
+ unless $LOADED_FEATURES.include?(path) ||
6
+ $LOADED_FEATURES.include?(File.expand_path(path))
7
+ # Do not trigger autoload
8
+ return nil
9
+ end
10
+ end
11
+ Mirrors.reflect @object.const_get(@name)
12
+ end
13
+
14
+ def value=(o)
15
+ @object.const_set(@name, o)
16
+ end
17
+
18
+ def public?
19
+ true
20
+ end
21
+
22
+ def protected?
23
+ false
24
+ end
25
+
26
+ def private?
27
+ false
28
+ end
29
+
30
+ def delete
31
+ @object.send(:remove_const, @name)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ module Mirrors
2
+ class InstanceVariableMirror < FieldMirror
3
+ def value
4
+ Mirrors.reflect(@object.instance_variable_get(@name))
5
+ end
6
+
7
+ def value=(o)
8
+ @object.instance_variable_set(@name, o)
9
+ end
10
+
11
+ def public?
12
+ false
13
+ end
14
+
15
+ def protected?
16
+ false
17
+ end
18
+
19
+ def private?
20
+ true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'mirrors/invoke'
2
+
3
+ module Mirrors
4
+ CLASS_DEFINITION_POINTS = {}
5
+
6
+ NoGemfile = Class.new(StandardError)
7
+
8
+ class << self
9
+ attr_accessor :project_root
10
+ end
11
+ end
12
+
13
+ begin
14
+ unless Mirrors.project_root
15
+ appline = caller.detect { |line| line !~ /:in `require'/ }
16
+ f = File.expand_path(appline.sub(/:\d.*/, ''))
17
+ Mirrors.project_root = loop do
18
+ f = File.dirname(f)
19
+ raise Mirrors::NoGemfile if f == '/'
20
+ break f if File.exist?("#{f}/Gemfile")
21
+ end
22
+ end
23
+ end
24
+
25
+ class Class
26
+ alias_method :__lg_orig_inherited, :inherited
27
+ def inherited(subclass)
28
+ file = caller[0].sub(/:\d+:in.*/, '')
29
+ key = Mirrors.module_instance_invoke(subclass, :inspect)
30
+ Mirrors::CLASS_DEFINITION_POINTS[key] = file
31
+ __lg_orig_inherited(subclass)
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Mirrors
2
+ module Index
3
+ class Indexer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ module Mirrors
2
+ class Marker
3
+ TYPE_TASK = 'mirrors.marker.task'.freeze
4
+ TYPE_PROBLEM = 'mirrors.marker.problem'.freeze
5
+ TYPE_TEXT = 'mirrors.marker.text'.freeze
6
+ TYPE_CLASS_REFERENCE = 'mirrors.marker.text.class_reference'.freeze
7
+ TYPE_METHOD_REFERENCE = 'mirrors.marker.text.method_reference'.freeze
8
+ TYPE_FIELD_REFERENCE = 'mirrors.marker.text.field_reference'.freeze
9
+
10
+ NO_LINE = -1
11
+ NO_COLUMN = -1
12
+
13
+ attr_reader :message, :type, :file, :line, :start_column, :end_column
14
+
15
+ def initialize(type: TYPE_TASK, message: '', file: nil, line: NO_LINE, start_column: NO_COLUMN, end_column: NO_COLUMN)
16
+ @type = type
17
+ @message = message
18
+ @file = file
19
+ @line = line
20
+ @start_column = start_column
21
+ @end_column = end_column
22
+ end
23
+
24
+ def ==(other)
25
+ type == other.type && message == other.message && line == other.line && start_column == other.start_column && end_column == other.end_column
26
+ end
27
+
28
+ def eql?(other)
29
+ self == other
30
+ end
31
+
32
+ def hash
33
+ [@type, @message, @line, @start_column, @end_column].hash
34
+ end
35
+
36
+ def hashy
37
+ [@type, @message, @line, @start_column, @end_column]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ module Mirrors
2
+ # Methods here follow the pattern of:
3
+ # <target>_<instance or singleton>_<invoke or method>
4
+ #
5
+ # * target is the owner of the method
6
+ # * instance or singleton indicates whether we want an instance method or a
7
+ # singleton method from the target
8
+ # * invoke calls the method on a receiver, which must be compatible with the
9
+ # method. method returns the method to bind to whatever receiver you want.
10
+
11
+ @unbound_module_instance_methods = {}
12
+ @unbound_class_singleton_methods = {}
13
+
14
+ def self.module_instance_invoke(receiver, msg)
15
+ module_instance_method(msg).bind(receiver).call
16
+ end
17
+
18
+ def self.module_instance_method(msg)
19
+ @unbound_module_instance_methods[msg] ||= Module.instance_method(msg)
20
+ end
21
+
22
+ def self.class_singleton_invoke(receiver, msg)
23
+ class_singleton_method(msg).bind(receiver).call
24
+ end
25
+
26
+ def self.class_singleton_method(msg)
27
+ @unbound_class_singleton_methods[msg] ||= Class.method(msg).unbind
28
+ end
29
+ end
@@ -0,0 +1,206 @@
1
+ require 'method_source'
2
+ require 'base64'
3
+ require 'ripper'
4
+ require 'pp'
5
+ require 'mirrors/visitors/iseq_visitor'
6
+ require 'mirrors/visitors/references_visitor'
7
+
8
+ module Mirrors
9
+ # A MethodMirror should reflect on methods, but in a more general
10
+ # sense than the Method and UnboundMethod classes in Ruby are able
11
+ # to offer.
12
+ #
13
+ # In actual execution, a method is pretty much every chunk of code,
14
+ # even loading a file triggers a process not unlike compiling a
15
+ # method (if only for the side-effects). Method mirrors should allow
16
+ # access to the runtime objects, but also to their static
17
+ # representations (bytecode, source, ...), their debugging
18
+ # information and statistical information
19
+ class MethodMirror < Mirror
20
+ # @return [String, nil] The filename, if available
21
+ def file
22
+ sl = source_location
23
+ sl ? sl.first : nil
24
+ end
25
+
26
+ # @return [Fixnum, nil] The source line, if available
27
+ def line
28
+ sl = source_location
29
+ sl && sl.last ? sl.last - 1 : nil
30
+ end
31
+
32
+ # @return [String] The method name
33
+ def selector
34
+ @subject.name.to_s
35
+ end
36
+
37
+ # @return [ClassMirror] The class this method was originally defined in
38
+ def defining_class
39
+ Mirrors.reflect @subject.send(:owner)
40
+ end
41
+
42
+ # Return the value the block argument, or nil
43
+ #
44
+ # @return [String, nil]
45
+ def block_argument
46
+ args(:block).first
47
+ end
48
+
49
+ # Returns a field mirror with name and possibly value of the splat
50
+ # argument, or nil, if there is none to this method.
51
+ #
52
+ # @return [String, nil]
53
+ def splat_argument
54
+ args(:rest).first
55
+ end
56
+
57
+ # Returns names and values of the optional arguments.
58
+ #
59
+ # @return [Array<String>, nil]
60
+ def optional_arguments
61
+ args(:opt)
62
+ end
63
+
64
+ # Returns the name and possibly values of the required arguments
65
+ #
66
+ # @return [Array<String>]
67
+ def required_arguments
68
+ args(:req)
69
+ end
70
+
71
+ # Queries the method for it's arguments and returns a list of
72
+ # mirrors that hold name and value information.
73
+ #
74
+ # @return [Array<String>]
75
+ def arguments
76
+ @subject.send(:parameters).map { |_, a| a.to_s }
77
+ end
78
+
79
+ # Is the method :public, :private, or :protected?
80
+ #
81
+ # @return [String]
82
+ def visibility
83
+ return :public if visibility?(:public)
84
+ return :private if visibility?(:private)
85
+ :protected
86
+ end
87
+
88
+ def protected?
89
+ visibility?(:protected)
90
+ end
91
+
92
+ def public?
93
+ visibility?(:public)
94
+ end
95
+
96
+ def private?
97
+ visibility?(:private)
98
+ end
99
+
100
+ def super_method
101
+ owner = @subject.send(:owner)
102
+
103
+ if owner.is_a?(Class)
104
+ meth = Mirrors
105
+ .class_singleton_method(:allocate)
106
+ .bind(instance)
107
+ .super_method
108
+ .unbind
109
+ else
110
+ meth = @subject.bind(owner).super_method.unbind
111
+ end
112
+
113
+ meth ? Mirrors.reflect(meth) : nil
114
+ end
115
+
116
+ # @return [String,nil] The source code of this method
117
+ def source
118
+ @source ||= unindent(@subject.send(:source))
119
+ rescue MethodSource::SourceNotFoundError
120
+ nil
121
+ end
122
+
123
+ # @return [String,nil] The pre-definition comment of this method
124
+ def comment
125
+ @subject.send(:comment)
126
+ rescue MethodSource::SourceNotFoundError
127
+ nil
128
+ end
129
+
130
+ # Returns the instruction sequence for the method (cached)
131
+ def iseq
132
+ @iseq ||= RubyVM::InstructionSequence.of(@subject)
133
+ end
134
+
135
+ # Returns the disassembled code if available.
136
+ #
137
+ # @return [String, nil] human-readable bytedcode dump
138
+ def bytecode
139
+ @bytecode ||= iseq.disasm if iseq
140
+ @bytecode
141
+ end
142
+
143
+ # Returns the parse tree if available
144
+ #
145
+ # @return [String, nil] prettified AST
146
+ def sexp
147
+ src = source
148
+ src ? Ripper.sexp(src).pretty_inspect : nil
149
+ end
150
+
151
+ # Returns the compiled code if available.
152
+ #
153
+ # @return [RubyVM::InstructionSequence, nil] native code
154
+ def native_code
155
+ RubyVM::InstructionSequence.of(@subject)
156
+ end
157
+
158
+ def name
159
+ @subject.name
160
+ end
161
+
162
+ def references
163
+ visitor = Mirrors::ReferencesVisitor.new
164
+ visitor.call(self)
165
+ visitor.markers
166
+ end
167
+
168
+ private
169
+
170
+ def visibility?(type)
171
+ list = @subject.send(:owner).send("#{type}_instance_methods")
172
+ list.any? { |m| m.to_s == selector }
173
+ end
174
+
175
+ def args(type)
176
+ args = []
177
+ @subject.send(:parameters).select { |t, n| args << n.to_s if t == type }
178
+ args
179
+ end
180
+
181
+ def source_location
182
+ @subject.send(:source_location)
183
+ end
184
+
185
+ def unindent(str)
186
+ lines = str.split("\n")
187
+ return str if lines.empty?
188
+
189
+ indents = lines.map do |line|
190
+ if line =~ /\S/
191
+ line.start_with?(" ") ? line.match(/^ +/).offset(0)[1] : 0
192
+ end
193
+ end
194
+ indents.compact!
195
+
196
+ if indents.empty?
197
+ # No lines had any non-whitespace characters.
198
+ return ([""] * lines.size).join "\n"
199
+ end
200
+
201
+ min_indent = indents.min
202
+ return str if min_indent.zero?
203
+ lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join("\n")
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,37 @@
1
+ module Mirrors
2
+ # The basic mirror
3
+ class Mirror
4
+ def initialize(obj)
5
+ @subject = obj
6
+ end
7
+
8
+ def subject_id
9
+ @subject.__id__.to_s
10
+ end
11
+
12
+ # A generic representation of the object under observation.
13
+ def name
14
+ if @subject.is_a?(String) || @subject.is_a?(Symbol)
15
+ @subject
16
+ else
17
+ @subject.inspect
18
+ end
19
+ end
20
+
21
+ # The equivalent to #==/#eql? for comparison of mirrors against objects
22
+ def mirrors?(other)
23
+ @subject == other
24
+ end
25
+
26
+ # Accessor to the reflected object
27
+ def reflectee
28
+ @subject
29
+ end
30
+
31
+ private
32
+
33
+ def mirrors(list)
34
+ list.collect { |e| Mirrors.reflect(e) }
35
+ end
36
+ end
37
+ end