object_shadow 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5ebcf54f524f3ab83fac7d05282f3510682a8e75b1c6f4744616aef84f2291f0
4
+ data.tar.gz: 6db42ef5f1cfff3b3927662cfddc774b779196521c023d3b224bf3b37a41e258
5
+ SHA512:
6
+ metadata.gz: 7350dd92d83503c51dc1d4a3172d8eda8713f2dcee47d41fdc324c289ab809ff93410cb49a18f5297218536f5bb0dfbb38ca8a1b42d603049ce724a9afdac7dc
7
+ data.tar.gz: 516a2e61c66368c4cfef098878e1493eccb536e5a26788b861013875be14bccdaedf415674d08c8adef07f5906d5810c378da830556f723c45bfeeada5659a16
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ /pkg
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ sudo: false
2
+ language: ruby
3
+
4
+ rvm:
5
+ - 2.6
6
+ - 2.5
7
+ - 2.4
8
+ - 2.3
9
+ - ruby-head
10
+ - jruby-9.2.6.0
11
+ - truffleruby
12
+
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: ruby-head
16
+ - rvm: truffleruby
17
+ - rvm: 2.3
18
+ # fast_finish: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## CHANGELOG
2
+
3
+ ### 1.0.0
4
+
5
+ * Initial release
6
+
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at opensource@janlelis.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "minitest"
6
+ gem "rake"
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019 Jan Lelis, mail@janlelis.de
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # Object#shadow [![[version]](https://badge.fury.io/rb/object.svg)](https://badge.fury.io/rb/object) [![[travis]](https://travis-ci.org/janlelis/object.svg)](https://travis-ci.org/janlelis/object)
2
+
3
+ Have you ever been [confused by some of Ruby's meta-programming methods](https://idiosyncratic-ruby.com/25-meta-methodology.html). If your answer is *Yes*, you have come to the right place:
4
+
5
+ ![Object and Shadow](/object_shadow.png)
6
+
7
+ With **shadow**, every Ruby object has a shadow which encapsulates a clean API to access the object's variables and methods.
8
+
9
+ Never again you will have to do the `x.methods - Object.methods` trick to get a meaningful method list.
10
+
11
+ ## Setup
12
+
13
+ Add to your `Gemfile`:
14
+
15
+ ```ruby
16
+ gem "object_shadow"
17
+ ```
18
+
19
+ ## Usage Example
20
+
21
+ ```ruby
22
+ class P
23
+ def a_public_parent_method
24
+ end
25
+ end
26
+
27
+ class C < P
28
+ def initialize
29
+ @ivar = 42
30
+ @another_variable = 43
31
+ end
32
+
33
+ attr_reader :another_variable
34
+
35
+ def a_public_method
36
+ end
37
+
38
+ protected
39
+
40
+ def a_protected_method
41
+ end
42
+
43
+ private
44
+
45
+ def a_private_method
46
+ end
47
+ end
48
+
49
+ object = C.new
50
+
51
+ def object.a_public_singleton_method
52
+ end
53
+ ```
54
+
55
+ ### # Get an Overview
56
+
57
+ ```ruby
58
+ object.shadow
59
+ # ObjectShadow of Object #47023274596520
60
+
61
+ ## Lookup Chain
62
+
63
+ #<Class:#<C:0x00005588eb283150>> → C → P → Object → …
64
+
65
+ ## 2 Instance Variables
66
+
67
+ [:ivar, :another_variable]
68
+
69
+ ## 4 Public Methods (Non-Class/Object)
70
+
71
+ [:a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable]
72
+
73
+ ## 1 Protected Method (Non-Class/Object)
74
+
75
+ [:a_protected_method]
76
+
77
+ ## 2 Private Methods (Non-Class/Object)
78
+
79
+ [:a_private_method, :initialize]
80
+
81
+ ```
82
+
83
+ ### # Read & Manipulate Instance Variables
84
+
85
+ ```ruby
86
+ object.shadow[:ivar] # => 42
87
+ object.shadow[:another_variable] = 23; object.another_variable # => 23
88
+ object.shadow.variables # => [:ivar, :another_variable]
89
+ object.shadow.to_h # => {:ivar=>42, :another_variable=>23}
90
+ object.shadow.remove(:ivar) # => 42 (and removed)
91
+ ```
92
+
93
+ ### # List Available Methods
94
+
95
+ ```ruby
96
+ # shadow features a single method called `methods` which takes some keyword arguments for further listing options
97
+ object.shadow.methods # => [:a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable]
98
+
99
+ # Use scope: option to toggle visibility (default is public)
100
+ object.shadow.methods(scope: :public) # => [:a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable]
101
+ object.shadow.methods(scope: :protected) # => [:a_protected_method]
102
+ object.shadow.methods(scope: :private) # => [:a_private_method, :initialize]
103
+ object.shadow.methods(scope: :all) # => [:a_private_method, :a_protected_method, :a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable, :initialize]
104
+
105
+ # Use inherit: option to allow or prevent traversal of the inheritance chain
106
+ object.shadow.methods(scope: :public, inherit: :singleton) # => [:a_public_singleton_method]
107
+ object.shadow.methods(scope: :public, inherit: :self) # => [:a_public_method, :a_public_singleton_method, :another_variable]
108
+ object.shadow.methods(scope: :public, inherit: :exclude_object) # => [:a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable]
109
+ object.shadow.methods(scope: :public, inherit: :all) # => [:!, :!=, :!~, :<=>, :==, :===, :=~, :__id__, :__send__, :a_public_method, :a_public_parent_method, :a_public_singleton_method, :another_variable, :class, :clone, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :methods, :nil?, :object_id, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :remove_instance_variable, :respond_to?, :send, :shadow, :singleton_class, :singleton_method, :singleton_methods, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :untaint, :untrust, :untrusted?, :yield_self]
110
+
111
+ # Use target: :instances or :class to jump between child and class method listings
112
+ C.shadow.methods == C.new.shadow.methods(target: :class) #=> true
113
+ C.shadow.methods(target: :instances) == C.new.shadow.methods #=> true
114
+ Enumerable.shadow.methods(target: :instances) # (lists Enumerables' methods)
115
+ ```
116
+
117
+ ## Documentation
118
+
119
+ ### Instance Variables
120
+
121
+ Shadow exposes instance variables in a Hash-like manner:
122
+
123
+ Method | Description
124
+ -----------|------------
125
+ `[]` | Retrieve instance variables. Takes a symbol without `@` to identify variable.
126
+ `[]=` | Sets instance variables. Takes a symbol without `@` to identify variable.
127
+ `remove` | Removes an instance variables. Takes a symbol without `@` to identify variable.
128
+ `variable?`| Checks if a variable with that name exists. Takes a symbol without `@` to identify variable.
129
+ `variables`| Returns the list of instance variables as symbols without `@`.
130
+ `to_h` | Returns a hash of instance variable names with `@`-less variables names as the keys.
131
+ `to_a` | Returns an array of all instance variable values.
132
+
133
+ ### Method Introspection
134
+
135
+ All method introspection methods get called on the shadow and take a `target:` keyword argument, which defaults to `:self`. It can take one of the following values:
136
+
137
+ Value of `target:` | Meaning
138
+ -------------------|--------
139
+ `:self` | Operate on the current object
140
+ `:class` | Operate on the current object's class (the class for instances, the singleton class for classes)
141
+ `:instances` | Operate on potential instances created by the object, which is a class (or module)
142
+
143
+ #### `methods(target: :self, scope: :public, inherit: :exclude_class)`
144
+
145
+ Returns a list of methods available to the object.
146
+
147
+ Only shows methods matching the given `scope:`, i.e. when you request all **public** methods, **protected** and **private** methods will not be included. You can also pass in `:all` to get methods of *all* scopes.
148
+
149
+ The `inherit:` option lets you choose how deep you want to dive into the inheritance chain:
150
+
151
+ Value of `inherit:` | Meaning
152
+ --------------------|--------
153
+ `:singleton` | Show only methods directly defined in the object's singleton class
154
+ `:self` | Show singleton methods and methods directly defined in the object's class, but do not traverse the inheritance chain
155
+ `:exclude_class` | Stop inheritance chain just before Class or Module. For non-modules it fallbacks to `:exclude_object`
156
+ `:exclude_object` | Stop inheritance chain just before Object
157
+ `:all` | Show methods from the whole inheritance chain
158
+
159
+ #### `method?(method_name, target: :self)`
160
+
161
+ Returns `true` if such a method can be found, `false` otherwise
162
+
163
+ #### `method_scope(method_name, target: :self)`
164
+
165
+ Returns the visibility scope of the method in question, one of `:public`, `:protected`, `:private`. If the method cannot be located, returns `nil`.
166
+
167
+ #### `method(method_name, target: :self, unbind: false, all: false)`
168
+
169
+ Returns the `Method` or `UnboundMethod` object of the method requested. Use `unbind: true` to force the return value to be an `UnboundMethod` object. Will always return `UnboundMethod`s if used in conjunction with `target: :instances`.
170
+
171
+ If you pass in `all: true`, it will return an array of all (unbound) method objects found in the inheritance chain for the given method name.
172
+
173
+ #### `method_lookup_chain(target: :self, inherit: :exclude_class)`
174
+
175
+ Shows the lookup chain for the target. See `methods()` for description of the `inherit:` option.
176
+
177
+ ## Q & A
178
+
179
+ ### Can I Access Hidden Instance Variables?
180
+
181
+ Some of Ruby's core classes use `@`-less instance variables, such as [Structs](https://ruby-doc.org/core/Struct.html). They cannot be accessed using shadow.
182
+
183
+ ### Does It Support Refinements?
184
+
185
+ [Currently not.](https://ruby-doc.org/core/doc/syntax/refinements_rdoc.html#label-Methods+Introspection)
186
+
187
+ ### Other Meta Programming?
188
+
189
+ Only some aspects of Ruby meta-programming are covered. However, **shadow** aims to cover all kinds of meta-programming. Maybe you have an idea about how to integrate `eval`, `method_missing`, and friends?
190
+
191
+ ## J-_-L
192
+
193
+ Copyright (C) 2019 Jan Lelis <https://janlelis.com>. Released under the MIT license.
194
+
195
+ PS: This gem would not exist if the [instance gem](https://rubyworks.github.io/instance/) did not come up with the idea.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ # # #
2
+ # Get gemspec info
3
+
4
+ gemspec_file = Dir["*.gemspec"].first
5
+ gemspec = eval File.read(gemspec_file), binding, gemspec_file
6
+ info = "#{gemspec.name} | #{gemspec.version} | " \
7
+ "#{gemspec.runtime_dependencies.size} dependencies | " \
8
+ "#{gemspec.files.size} files"
9
+
10
+ # # #
11
+ # Gem build and install task
12
+
13
+ desc info
14
+ task :gem do
15
+ puts info + "\n\n"
16
+ print " "; sh "gem build #{gemspec_file}"
17
+ FileUtils.mkdir_p "pkg"
18
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", "pkg"
19
+ puts; sh %{gem install --no-document pkg/#{gemspec.name}-#{gemspec.version}.gem}
20
+ end
21
+
22
+ # # #
23
+ # Start an IRB session with the gem loaded
24
+
25
+ desc "#{gemspec.name} | IRB"
26
+ task :irb do
27
+ sh "irb -I ./lib -r #{gemspec.name.gsub '-','/'}"
28
+ end
29
+
30
+ # # #
31
+ # Run specs
32
+
33
+ desc "#{gemspec.name} | Spec"
34
+ task :spec do
35
+ sh "for file in spec/*_spec.rb; do ruby $file; done"
36
+ end
37
+ task default: :spec
38
+
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "object_shadow/basic_object"
4
+ require_relative "object_shadow/version"
5
+ require_relative "object_shadow/object_method"
6
+
7
+ require_relative "object_shadow/wrap"
8
+ require_relative "object_shadow/instance_variables"
9
+ require_relative "object_shadow/method_introspection"
10
+ require_relative "object_shadow/info_inspect"
11
+
12
+ class ObjectShadow
13
+ include Wrap
14
+ include InstanceVariables
15
+ include MethodIntrospection
16
+ include InfoInspect
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ObjectShadow < BasicObject
4
+ def class
5
+ ::ObjectShadow
6
+ end
7
+
8
+ def respond_to?(what)
9
+ ::ObjectShadow.instance_methods.include?(what)
10
+ end
11
+
12
+ def instance_of?(other)
13
+ other == ::ObjectShadow
14
+ end
15
+
16
+ def is_a?(other)
17
+ other.ancestors.include? ::ObjectShadow
18
+ end
19
+
20
+ def singleton_class
21
+ class << self; self end
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ObjectShadow
4
+ module InfoInspect
5
+ def inspect
6
+ public_methods = methods(scope: :public)
7
+ protected_methods = methods(scope: :protected)
8
+ private_methods = methods(scope: :private)
9
+
10
+ inherit_till = object.instance_of?(Object) ? :all : Object
11
+ lookup_chain = method_lookup_chain(inherit: inherit_till).join(" → ") + " → …"
12
+
13
+ res = \
14
+ "# ObjectShadow of Object #%{object_id}\n\n" \
15
+ "## Lookup Chain\n\n%{method_lookup_chain}\n\n"
16
+
17
+ unless variables.empty?
18
+ res += "## %{variables_count} Instance Variable%{variables_plural}\n\n%{variables}\n\n" \
19
+ end
20
+
21
+ unless public_methods.empty?
22
+ res += "## %{public_methods_count} Public Method%{public_methods_plural} (Non-Class/Object)\n\n%{public_methods}\n\n" \
23
+ end
24
+
25
+ unless protected_methods.empty?
26
+ res += "## %{protected_methods_count} Protected Method%{protected_methods_plural} (Non-Class/Object)\n\n%{protected_methods}\n\n" \
27
+ end
28
+
29
+ unless private_methods.empty?
30
+ res += "## %{private_methods_count} Private Method%{private_methods_plural} (Non-Class/Object)\n\n%{private_methods}\n\n" \
31
+ end
32
+
33
+ res % {
34
+ object_id: object.object_id,
35
+ method_lookup_chain: InfoInspect.column100(lookup_chain),
36
+ variables_count: variables.size,
37
+ variables_plural: variables.size == 1 ? "" : "s",
38
+ variables: InfoInspect.column100(variables.inspect),
39
+ public_methods_count: public_methods.size,
40
+ public_methods_plural: public_methods.size == 1 ? "" : "s",
41
+ public_methods: InfoInspect.column100(public_methods.inspect),
42
+ protected_methods_count: protected_methods.size,
43
+ protected_methods_plural: protected_methods.size == 1 ? "" : "s",
44
+ protected_methods: InfoInspect.column100(protected_methods.inspect),
45
+ private_methods_count: private_methods.size,
46
+ private_methods_plural: private_methods.size == 1 ? "" : "s",
47
+ private_methods: InfoInspect.column100(private_methods.inspect),
48
+ }
49
+ end
50
+
51
+ class << self
52
+ def column100(input)
53
+ words = input.split(" ")
54
+ lines = [""]
55
+ words.each{ |word|
56
+ if lines[-1].size + word.size < 95 # 95 + 1 word space + 4 indent spaces = 95
57
+ lines[-1] = lines[-1] + word + " "
58
+ else
59
+ lines << word + " "
60
+ end
61
+ }
62
+
63
+ lines.map{ |line|
64
+ " #{line}"
65
+ }.join("\n")
66
+ end
67
+ end
68
+ end
69
+ end