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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +59 -0
- data/README.md +1 -0
- data/asdfasdf.rb +53 -0
- data/bin/bundler +17 -0
- data/bin/byebug +17 -0
- data/bin/testunit +8 -0
- data/circle.yml +6 -0
- data/dev.yml +6 -0
- data/lib/mirrors.rb +150 -0
- data/lib/mirrors/class_mirror.rb +197 -0
- data/lib/mirrors/class_mixin.rb +11 -0
- data/lib/mirrors/field_mirror.rb +24 -0
- data/lib/mirrors/field_mirror/class_variable_mirror.rb +23 -0
- data/lib/mirrors/field_mirror/constant_mirror.rb +34 -0
- data/lib/mirrors/field_mirror/instance_variable_mirror.rb +23 -0
- data/lib/mirrors/hook.rb +33 -0
- data/lib/mirrors/index/indexer.rb +6 -0
- data/lib/mirrors/index/marker.rb +40 -0
- data/lib/mirrors/invoke.rb +29 -0
- data/lib/mirrors/method_mirror.rb +206 -0
- data/lib/mirrors/mirror.rb +37 -0
- data/lib/mirrors/object_mirror.rb +25 -0
- data/lib/mirrors/package_inference.rb +164 -0
- data/lib/mirrors/package_inference/class_to_file_resolver.rb +66 -0
- data/lib/mirrors/package_mirror.rb +33 -0
- data/lib/mirrors/visitors/disasm_visitor.rb +11 -0
- data/lib/mirrors/visitors/iseq_visitor.rb +84 -0
- data/lib/mirrors/visitors/references_visitor.rb +58 -0
- data/lib/mirrors/visitors/yasmdata.rb +212 -0
- data/lol.rb +35 -0
- data/mirrors.gemspec +19 -0
- data/test/fixtures/class.rb +29 -0
- data/test/fixtures/field.rb +9 -0
- data/test/fixtures/method.rb +15 -0
- data/test/fixtures/object.rb +5 -0
- data/test/fixtures/reflect.rb +14 -0
- data/test/mirrors/class_mirror_test.rb +87 -0
- data/test/mirrors/field_mirror_test.rb +125 -0
- data/test/mirrors/iseq_visitor_test.rb +56 -0
- data/test/mirrors/marker_test.rb +48 -0
- data/test/mirrors/method_mirror_test.rb +62 -0
- data/test/mirrors/object_mirror_test.rb +16 -0
- data/test/mirrors/package_inference_test.rb +31 -0
- data/test/mirrors/references_visitor_test.rb +30 -0
- data/test/mirrors_test.rb +38 -0
- data/test/test_helper.rb +12 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0d3d02baa921913fcc9ac830bba816d1a9f47339
|
4
|
+
data.tar.gz: 71253ea57e8ae471532753da621496811112da9b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6922717b62cd336253f51223ff28b3c7be93471c15f7117d92e6b61796a562c613c027ac0185275aa6947c7631e24e7b586113850652ef80f13ff6420db86fa2
|
7
|
+
data.tar.gz: 4f3ffb84b4cada62a5f67b47be3484dbb832b62831c886a73554d12c0f6f9641e69466e51cb5abc0393b62ab16dd2e3a4ace12d14a855ebb63b836c87dcbe764
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mirrors (0.0.1)
|
5
|
+
method_source (~> 0.8)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
byebug (9.0.6)
|
11
|
+
method_source (0.8.2)
|
12
|
+
minitest (5.9.1)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
byebug (~> 9.0.6)
|
19
|
+
minitest (~> 5.0)
|
20
|
+
mirrors!
|
21
|
+
|
22
|
+
BUNDLED WITH
|
23
|
+
1.13.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
Copyright (c) 2016 Burke Libbey, Shopify
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
3. The names of the contributors may not be used
|
15
|
+
to endorse or promote products derived from this software without
|
16
|
+
specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
|
30
|
+
--- most code derived from github.com/timfel/rubymirrors ---
|
31
|
+
|
32
|
+
|
33
|
+
Copyright (c) Tim Felgentreff and individual contributors.
|
34
|
+
All rights reserved.
|
35
|
+
|
36
|
+
Redistribution and use in source and binary forms, with or without modification,
|
37
|
+
are permitted provided that the following conditions are met:
|
38
|
+
|
39
|
+
1. Redistributions of source code must retain the above copyright notice,
|
40
|
+
this list of conditions and the following disclaimer.
|
41
|
+
|
42
|
+
2. Redistributions in binary form must reproduce the above copyright
|
43
|
+
notice, this list of conditions and the following disclaimer in the
|
44
|
+
documentation and/or other materials provided with the distribution.
|
45
|
+
|
46
|
+
3. The names of the contributors may not be used
|
47
|
+
to endorse or promote products derived from this software without
|
48
|
+
specific prior written permission.
|
49
|
+
|
50
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
51
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
52
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
53
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
54
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
55
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
56
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
57
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
58
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
59
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Use `bin/frontend` and `bin/backend`.
|
data/asdfasdf.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
class Resolver
|
2
|
+
def resolve(klass)
|
3
|
+
@files ||= {}
|
4
|
+
|
5
|
+
klass.instance_methods(false).each do |name|
|
6
|
+
meth = klass.instance_method(name)
|
7
|
+
|
8
|
+
file = begin
|
9
|
+
meth.source_location[0]
|
10
|
+
rescue # ???
|
11
|
+
next
|
12
|
+
end
|
13
|
+
|
14
|
+
contents = (@files[file] ||= File.open(file, 'r') { |f| f.readpartial(4096) })
|
15
|
+
n = klass.name.sub(/.*::/, '') # last component of module name
|
16
|
+
return file if contents =~ /^\s+(class|module) ([\S]+::)?#{Regexp.quote(n)}\s/
|
17
|
+
end
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def infer_class_file_from_methods(klass)
|
23
|
+
if m = Resolver.new.resolve(klass)
|
24
|
+
return m
|
25
|
+
end
|
26
|
+
|
27
|
+
methods = klass
|
28
|
+
.instance_methods(false)
|
29
|
+
.map { |n| klass.instance_method(n) }
|
30
|
+
|
31
|
+
defined_directly_on_class = methods
|
32
|
+
.select do |meth|
|
33
|
+
# aliased methods can show up with instance_methods(false)
|
34
|
+
# but their source_location and owner point to the module they came from.
|
35
|
+
meth.owner == klass &&
|
36
|
+
meth.source =~ /\A\s+def #{Regexp.quote(meth.name)}/
|
37
|
+
# as a mostly-useful heuristic, we just eliminate everything that was
|
38
|
+
# defined using a template eval or define_method.
|
39
|
+
end
|
40
|
+
|
41
|
+
files = Hash.new(0)
|
42
|
+
|
43
|
+
defined_directly_on_class.each do |meth|
|
44
|
+
begin
|
45
|
+
files[meth.source_location[0]] += 1
|
46
|
+
rescue # which class?
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
file = files.max_by { |k, v| v }
|
52
|
+
file ? file[0] : nil
|
53
|
+
end
|
data/bin/bundler
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'bundler' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("bundler", "bundler")
|
data/bin/byebug
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'byebug' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("byebug", "byebug")
|
data/bin/testunit
ADDED
data/circle.yml
ADDED
data/dev.yml
ADDED
data/lib/mirrors.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'mirrors/mirror'
|
3
|
+
require 'mirrors/object_mirror'
|
4
|
+
require 'mirrors/class_mirror'
|
5
|
+
require 'mirrors/field_mirror'
|
6
|
+
require 'mirrors/method_mirror'
|
7
|
+
require 'mirrors/package_mirror'
|
8
|
+
require 'mirrors/package_inference'
|
9
|
+
require 'mirrors/class_mixin'
|
10
|
+
require 'mirrors/index/indexer'
|
11
|
+
|
12
|
+
module Mirrors
|
13
|
+
extend self
|
14
|
+
|
15
|
+
@class_mirrors = {}
|
16
|
+
@constant_mirrors = {}
|
17
|
+
@watches = {}
|
18
|
+
@logger = Logger.new(STDOUT)
|
19
|
+
|
20
|
+
def packages
|
21
|
+
packages = {}
|
22
|
+
# Object is the top-level.
|
23
|
+
Object.constants.each do |const|
|
24
|
+
pkg = PackageInference.infer_from_toplevel(const)
|
25
|
+
packages[pkg] = true
|
26
|
+
end
|
27
|
+
toplevel_packages = packages.keys.map { |pkg| pkg.sub(/:.*/, '') }.sort
|
28
|
+
package_mirrors(toplevel_packages)
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method can be used to query the system for known modules. It
|
32
|
+
# is not guaranteed that all possible modules are returned.
|
33
|
+
#
|
34
|
+
# @return [Array<ClassMirror>] a list of class mirrors
|
35
|
+
def modules
|
36
|
+
instances_of(Module).sort! { |a, b| a.name <=> b.name }
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method can be used to query the system for known classes. It
|
40
|
+
# is not guaranteed that all possible classes are returned.
|
41
|
+
#
|
42
|
+
# @return [Array<ClassMirror>] a list of class mirrors
|
43
|
+
def classes
|
44
|
+
instances_of(Class).sort! { |a, b| a.name <=> b.name }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Query the system for objects that are direct instances of the
|
48
|
+
# given class.
|
49
|
+
# @param [Class]
|
50
|
+
# @return [Array<ObjectMirror>] a list of appropriate mirrors for the requested objects
|
51
|
+
def instances_of(klass)
|
52
|
+
mirrors(ObjectSpace.each_object(klass).select { |obj| obj.class == klass })
|
53
|
+
end
|
54
|
+
|
55
|
+
# Ask the system to find the object with the given object id
|
56
|
+
# @param [Numeric] object id
|
57
|
+
# @return [ObjectMirror, NilClass] the object mirror or nil
|
58
|
+
def object_by_id(id)
|
59
|
+
obj = ObjectSpace._id2ref(id)
|
60
|
+
obj ? reflect(obj) : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# Query the system for implementors of a particular message
|
64
|
+
# @param [String] the message name
|
65
|
+
# @return [Array<MethodMirror>] the implementing methods
|
66
|
+
def implementations_of(str)
|
67
|
+
methods = ObjectSpace.each_object(Module).collect do |m|
|
68
|
+
ims = m.instance_methods(false).collect { |s| m.instance_method(s) }
|
69
|
+
cms = m.methods(false).collect { |s| m.method(s) }
|
70
|
+
ims + cms
|
71
|
+
end.flatten
|
72
|
+
|
73
|
+
mirrors(methods.select { |m| m.name.to_s == str.to_s })
|
74
|
+
end
|
75
|
+
|
76
|
+
def references_to(str)
|
77
|
+
filtered = {}
|
78
|
+
Mirrors.classes.each do |klass|
|
79
|
+
klass.methods.each do |m|
|
80
|
+
refs = m.references.select { |marker| marker.message.match(str) }
|
81
|
+
filtered[m] = refs unless refs.empty?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
filtered
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create a mirror for a given object in the system under
|
88
|
+
# observation. This is *the* factory method for all mirror
|
89
|
+
# instances, interning and cache invalidation will be added here.
|
90
|
+
#
|
91
|
+
# @param [Object]
|
92
|
+
# @return [Mirror]
|
93
|
+
def reflect(obj)
|
94
|
+
klass = basic_class(obj)
|
95
|
+
mirror =
|
96
|
+
if klass == FieldMirror::Field || klass == Symbol
|
97
|
+
case obj.name.to_s
|
98
|
+
when /^@@/
|
99
|
+
intern_field_mirror(ClassVariableMirror.new(obj))
|
100
|
+
when /^@/
|
101
|
+
# instance variables not interned as they are not guaranteed to be
|
102
|
+
# present in all instances
|
103
|
+
InstanceVariableMirror.new(obj)
|
104
|
+
else
|
105
|
+
intern_field_mirror(ConstantMirror.new(obj))
|
106
|
+
end
|
107
|
+
elsif klass == Method || klass == UnboundMethod
|
108
|
+
intern_method_mirror(MethodMirror.new(obj))
|
109
|
+
elsif klass == Class || klass == Module
|
110
|
+
intern_class_mirror(ClassMirror.new(obj))
|
111
|
+
else
|
112
|
+
# TODO: revisit if ObjectMirror delivers value
|
113
|
+
ObjectMirror.new(obj)
|
114
|
+
end
|
115
|
+
raise "badness" unless mirror.is_a?(Mirror)
|
116
|
+
mirror
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# find the class of obj
|
122
|
+
def basic_class(obj)
|
123
|
+
Kernel.instance_method(:class).bind(obj).call
|
124
|
+
end
|
125
|
+
|
126
|
+
# find the class name of obj
|
127
|
+
def basic_class_name(klass)
|
128
|
+
Class.instance_method(:name).bind(klass).call
|
129
|
+
end
|
130
|
+
|
131
|
+
def intern_class_mirror(mirror)
|
132
|
+
interned = @class_mirrors[mirror.name] ||= mirror
|
133
|
+
end
|
134
|
+
|
135
|
+
def intern_method_mirror(mirror)
|
136
|
+
mirror.defining_class.intern_method_mirror(mirror)
|
137
|
+
end
|
138
|
+
|
139
|
+
def intern_field_mirror(mirror)
|
140
|
+
mirror.defining_class.intern_field_mirror(mirror)
|
141
|
+
end
|
142
|
+
|
143
|
+
def mirrors(list)
|
144
|
+
list.map { |e| reflect(e) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def package_mirrors(list)
|
148
|
+
list.map { |e| PackageMirror.reflect(e) }
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Mirrors
|
2
|
+
# A specific mirror for a class, that includes all the capabilites
|
3
|
+
# and information we can gather about classes.
|
4
|
+
class ClassMirror < ObjectMirror
|
5
|
+
def initialize(obj)
|
6
|
+
super(obj)
|
7
|
+
@field_mirrors = {}
|
8
|
+
@method_mirrors = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_class
|
12
|
+
@subject.is_a?(Class)
|
13
|
+
end
|
14
|
+
|
15
|
+
def package
|
16
|
+
# TODO(burke)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fields
|
20
|
+
[constants, class_variables, class_instance_variables, instance_variables].flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
# The known class variables.
|
24
|
+
# @see #instance_variables
|
25
|
+
# @return [Array<FieldMirror>]
|
26
|
+
def class_variables
|
27
|
+
field_mirrors(@subject.class_variables)
|
28
|
+
end
|
29
|
+
|
30
|
+
# The known class variables.
|
31
|
+
# @see #instance_variables
|
32
|
+
# @return [Array<FieldMirror>]
|
33
|
+
def class_instance_variables
|
34
|
+
field_mirrors(@subject.instance_variables)
|
35
|
+
end
|
36
|
+
|
37
|
+
# The source files this class is defined and/or extended in.
|
38
|
+
#
|
39
|
+
# @return [Array<String,File>]
|
40
|
+
def source_files
|
41
|
+
locations = @subject.instance_methods(false).collect do |name|
|
42
|
+
method = @subject.instance_method(name)
|
43
|
+
sl = method.source_location
|
44
|
+
sl.first if sl
|
45
|
+
end
|
46
|
+
locations.compact.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
# The singleton class of this class
|
50
|
+
#
|
51
|
+
# @return [ClassMirror]
|
52
|
+
def singleton_class
|
53
|
+
Mirrors.reflect(@subject.singleton_class)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Predicate to determine whether the subject is a singleton class
|
57
|
+
#
|
58
|
+
# @return [true,false]
|
59
|
+
def singleton_class?
|
60
|
+
name.match(/^\#<Class:.*>$/)
|
61
|
+
end
|
62
|
+
|
63
|
+
# The mixins included in the ancestors of this class.
|
64
|
+
#
|
65
|
+
# @return [Array<ClassMirror>]
|
66
|
+
def mixins
|
67
|
+
mirrors(@subject.ancestors.reject { |m| m.is_a?(Class) })
|
68
|
+
end
|
69
|
+
|
70
|
+
# The direct superclass
|
71
|
+
#
|
72
|
+
# @return [ClassMirror]
|
73
|
+
def superclass
|
74
|
+
Mirrors.reflect(@subject.superclass)
|
75
|
+
end
|
76
|
+
|
77
|
+
# The known subclasses
|
78
|
+
#
|
79
|
+
# @return [Array<ClassMirror>]
|
80
|
+
def subclasses
|
81
|
+
mirrors(ObjectSpace.each_object(Class).select { |a| a.superclass == @subject })
|
82
|
+
end
|
83
|
+
|
84
|
+
# The list of ancestors
|
85
|
+
#
|
86
|
+
# @return [Array<ClassMirror>]
|
87
|
+
def ancestors
|
88
|
+
mirrors(@subject.ancestors)
|
89
|
+
end
|
90
|
+
|
91
|
+
# The constants defined within this class. This includes nested
|
92
|
+
# classes and modules, but also all other kinds of constants.
|
93
|
+
#
|
94
|
+
# @return [Array<FieldMirror>]
|
95
|
+
def constants
|
96
|
+
field_mirrors(@subject.constants)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Searches for the named constant in the mirrored namespace. May
|
100
|
+
# include a colon (::) separated constant path. This _may_ trigger
|
101
|
+
# an autoload!
|
102
|
+
#
|
103
|
+
# @return [ClassMirror, nil] the requested constant, or nil
|
104
|
+
def constant(str)
|
105
|
+
path = str.to_s.split("::")
|
106
|
+
c = path[0..-2].inject(@subject) { |klass, s| klass.const_get(s) }
|
107
|
+
field_mirror (c || @subject), path.last
|
108
|
+
rescue NameError => e
|
109
|
+
p e
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
# The full nesting.
|
114
|
+
#
|
115
|
+
# @return [Array<ClassMirror>]
|
116
|
+
def nesting
|
117
|
+
ary = []
|
118
|
+
@subject.name.split('::').inject(Object) do |klass, str|
|
119
|
+
ary << klass.const_get(str)
|
120
|
+
ary.last
|
121
|
+
end
|
122
|
+
ary.reverse
|
123
|
+
rescue NameError
|
124
|
+
[@subject]
|
125
|
+
end
|
126
|
+
|
127
|
+
# The classes nested within the subject. Should _not_ trigger
|
128
|
+
# autloads!
|
129
|
+
#
|
130
|
+
# @return [Array<ClassMirror>]
|
131
|
+
def nested_classes
|
132
|
+
nc = @subject.constants.collect do |c|
|
133
|
+
# do not trigger autoloads
|
134
|
+
if @subject.const_defined?(c) && !@subject.autoload?(c)
|
135
|
+
@subject.const_get(c)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
mirrors(nc.compact.select { |c| c.is_a?(Module) }.sort_by(&:name))
|
139
|
+
end
|
140
|
+
|
141
|
+
def nested_class_count
|
142
|
+
nested_classes.count
|
143
|
+
end
|
144
|
+
|
145
|
+
# The instance methods of this class. To get to the class methods,
|
146
|
+
# ask the #singleton_class for its methods.
|
147
|
+
#
|
148
|
+
# @return [Array<MethodMirror>]
|
149
|
+
def methods
|
150
|
+
pub_names = @subject.public_instance_methods(false)
|
151
|
+
prot_names = @subject.protected_instance_methods(false)
|
152
|
+
priv_names = @subject.private_instance_methods(false)
|
153
|
+
|
154
|
+
mirrors = []
|
155
|
+
pub_names.sort.each do |n|
|
156
|
+
mirrors << Mirrors.reflect(@subject.instance_method(n))
|
157
|
+
end
|
158
|
+
prot_names.sort.each do |n|
|
159
|
+
mirrors << Mirrors.reflect(@subject.instance_method(n))
|
160
|
+
end
|
161
|
+
priv_names.sort.each do |n|
|
162
|
+
mirrors << Mirrors.reflect(@subject.instance_method(n))
|
163
|
+
end
|
164
|
+
mirrors
|
165
|
+
end
|
166
|
+
|
167
|
+
# The instance method of this class or any of its superclasses
|
168
|
+
# that has the specified selector
|
169
|
+
#
|
170
|
+
# @return [MethodMirror, nil] the method or nil, if none was found
|
171
|
+
def method(name)
|
172
|
+
Mirrors.reflect @subject.instance_method(name)
|
173
|
+
end
|
174
|
+
|
175
|
+
# to work around overridden `name` methods
|
176
|
+
MODULE_INSPECT = Module.instance_method(:inspect)
|
177
|
+
def name
|
178
|
+
MODULE_INSPECT.bind(@subject).call
|
179
|
+
rescue
|
180
|
+
puts @subject.inspect
|
181
|
+
puts @subject.class
|
182
|
+
raise
|
183
|
+
end
|
184
|
+
|
185
|
+
def demodulized_name
|
186
|
+
name.split('::').last
|
187
|
+
end
|
188
|
+
|
189
|
+
def intern_method_mirror(mirror)
|
190
|
+
@method_mirrors[mirror.name] ||= mirror
|
191
|
+
end
|
192
|
+
|
193
|
+
def intern_field_mirror(mirror)
|
194
|
+
@field_mirrors[mirror.name] ||= mirror
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|