attic 0.6.pre.RC1 → 0.9.0.pre.rc2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cab1b37cf99fda2d7f47b898c998af908e2c8f4dcdde31ae66cd34d7c337a993
4
- data.tar.gz: 7d579f1320f3493b6639705d316aaf1197f77aa20894ecff84abd0a34a162124
3
+ metadata.gz: 5b8515c2812b3e4523d79d1fae98c348f041b4d6862d632e7a41ff4fb6555d7a
4
+ data.tar.gz: 29f3b9a07abb04e0d4611724a015ff8a0fcb704d7d8f3038d4ecd99ed6ae6768
5
5
  SHA512:
6
- metadata.gz: 920f145e1b3eb27151653a69be076fe3224c82e2781695209e2017ac4634708855dc0331ad254a9765bd185f25e566b1c6a712bc6118668c201ec0fa240239fc
7
- data.tar.gz: e0e4204bd76ae3f76c7c3a51166c278f69b2152db651a2ccd28ce8d559f281bfbf18e253713608fad2d9d488b96f52d3397070de7cc606842c3f47971db1276a
6
+ metadata.gz: 37911f6e23435f00b28f5d75643d2e250ee760c9ddee7049b6296dc3f5eaf6a00a48a9a6d3c484e9e9fd24e3feb15b57afaf7cc33d1e9456be80a8383840a360
7
+ data.tar.gz: bf9811b8008f3d292c0be8087202785ada8fda996ba352194d8d77652fc997ef0c2d9fe324f710493459f6aa79939472569d11bd68a25b6e4c7c31021b8c9c05
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ .bundle
3
+ .byebug*
4
+ .history
5
+ .devcontainer
6
+ .vscode
7
+ *.env
8
+ *.log
9
+ *.md
10
+ *.txt
11
+ !LICENSE.txt
12
+ .ruby-version
13
+ appendonlydir
14
+ Gemfile.lock
15
+ etc/config
16
+ log
17
+ tmp
18
+ vendor
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.1, 3.2
4
+ EnabledByDefault: false
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+
14
+ Layout/LineLength:
15
+ Enabled: true
16
+ Max: 120
17
+
18
+ Metrics/CyclomaticComplexity:
19
+ Enabled: false
20
+
21
+ Layout/HashAlignment:
22
+ Enabled: false
23
+
24
+ Lint/Void:
25
+ Enabled: false
26
+
27
+ Metrics/MethodLength:
28
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ ruby '>= 3.1'
6
+
7
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
8
+
9
+ # bundle install --without production
10
+ # bundle install --with development
11
+ # bundle install --without development
12
+ group "development" do
13
+ gem "byebug"
14
+ gem "pry"
15
+ gem "pry-doc"
16
+ gem "rubocop"
17
+ gem "tryouts", '2.2.0.pre.RC1'
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ ast (2.4.2)
5
+ byebug (11.1.3)
6
+ coderay (1.1.3)
7
+ drydock (0.6.9)
8
+ json (2.7.1)
9
+ language_server-protocol (3.17.0.3)
10
+ method_source (1.0.0)
11
+ parallel (1.24.0)
12
+ parser (3.3.0.5)
13
+ ast (~> 2.4.1)
14
+ racc
15
+ pry (0.14.2)
16
+ coderay (~> 1.1)
17
+ method_source (~> 1.0)
18
+ pry-doc (1.5.0)
19
+ pry (~> 0.11)
20
+ yard (~> 0.9.11)
21
+ racc (1.7.3)
22
+ rainbow (3.1.1)
23
+ regexp_parser (2.9.0)
24
+ rexml (3.2.6)
25
+ rubocop (1.62.1)
26
+ json (~> 2.3)
27
+ language_server-protocol (>= 3.17.0)
28
+ parallel (~> 1.10)
29
+ parser (>= 3.3.0.2)
30
+ rainbow (>= 2.2.2, < 4.0)
31
+ regexp_parser (>= 1.8, < 3.0)
32
+ rexml (>= 3.2.5, < 4.0)
33
+ rubocop-ast (>= 1.31.1, < 2.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 2.4.0, < 3.0)
36
+ rubocop-ast (1.31.2)
37
+ parser (>= 3.3.0.4)
38
+ ruby-progressbar (1.13.0)
39
+ storable (0.10.pre.RC1)
40
+ sysinfo (0.9.0.pre.RC1)
41
+ drydock (< 1.0)
42
+ storable (= 0.10.pre.RC1)
43
+ tryouts (2.2.0.pre.RC1)
44
+ sysinfo (= 0.9.0.pre.RC1)
45
+ unicode-display_width (2.5.0)
46
+ yard (0.9.36)
47
+
48
+ PLATFORMS
49
+ arm64-darwin-23
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ byebug
54
+ pry
55
+ pry-doc
56
+ rubocop
57
+ tryouts (= 2.2.0.pre.RC1)
58
+
59
+ RUBY VERSION
60
+ ruby 3.1.4p223
61
+
62
+ BUNDLED WITH
63
+ 2.5.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2009-2024 delano
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,54 +1,149 @@
1
- # Attic - v0.6-RC1 (2021-07-01)
1
+ # Attic - v1.0-RC1 (2023-03-15)
2
2
 
3
- A place to hide private instance variables in your Ruby objects.
3
+ A place to hide private instance variables in your Ruby objects: in the attic.
4
4
 
5
- ## Example
5
+ ## Description
6
+
7
+ Attic is a Ruby library that provides a place to hide private instance variables in your Ruby objects: in the attic.
8
+
9
+ Like, _why though_? Well sometimes you want to hide thing from the public interface of your object. You could use the `@@` prefix to place them at the class level but then you can't use them on a per-instance basis without creating a new data structure to store them. Attic does this for you in a transparent way using the Ruby singleton classes that are already there**.
10
+
11
+
12
+ ### Example
6
13
 
7
14
  ```ruby
8
15
  require 'attic'
9
16
 
17
+ # Extend a class with Attic
10
18
  class String
11
19
  extend Attic
20
+
21
+ # Add an instance variable to the attic. All instances
22
+ # of this class will have this variable available.
12
23
  attic :timestamp
13
24
  end
14
25
 
15
- a = "anything"
16
- a.timestamp = "1980-11-18"
26
+ # Instantiate a new String object
27
+ a = "A lovely example of a string"
28
+
29
+ # Set and get the timestamp
30
+ a.timestamp = "1990-11-18"
31
+ a.timestamp # => "1990-11-18"
32
+
33
+ # The timestamp is not visible in the public interface
17
34
  a.instance_variables # => []
18
- a.timestamp # 1980-11-18
19
35
 
36
+ # But it is available in the attic
20
37
  a.attic_variables # => [:timestamp]
21
38
 
39
+ # If you prefer getting your hands dirty, you can also
40
+ # interact with the attic at a lower level.
22
41
  a.attic_variable_set :tags, [:a, :b, :c]
23
- a.attic_variable_get :tags # [:a, :b, :c]
42
+ a.attic_variable_get :tags # => [:a, :b, :c]
24
43
 
44
+ # Looking at the attic again shows that the timestamp
45
+ # is still there too.
25
46
  a.attic_variables # => [:timestamp, :tags]
26
47
  ```
27
48
 
28
- ## Some objects have no metaclasses
29
49
 
30
- Symbol objects do not have metaclasses so instance variables are hidden in the object itself.
50
+ ### **Objects without singleton classes
31
51
 
52
+ Symbol, Integer, Float, TrueClass, FalseClass, NilClass, and Fixnum are all objects that do not have singleton classes. TrueClass, FalseClass, and NilClass are all singletons themselves. Fixnum is a singleton of Integer.
32
53
 
33
- ## Installation
54
+ These objects do not have metaclasses so the attic is hidden in the object itself.
34
55
 
35
- Via Rubygems, one of:
56
+ ### Explore in irb
57
+
58
+ ```shell
59
+ $ irb -r attic
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Installation
36
65
 
37
66
  ```shell
38
67
  $ gem install attic
39
68
  ```
40
69
 
70
+ ```shell
71
+ $ bundle install attic
72
+ ```
73
+
41
74
  or via download:
42
- * attic-latest.tar.gz[http://github.com/delano/attic/tarball/latest]
43
- * attic-latest.zip[http://github.com/delano/attic/zipball/latest]
75
+ * [attic-latest.tar.gz](https://github.com/delano/attic/tarball/latest)
76
+ * [attic-latest.zip](https://github.com/delano/attic/zipball/latest)
77
+
78
+
79
+ ## Proofs
80
+
81
+ Tested the following code in IRB for Ruby 2.6.8 and 3.0.2:
82
+
83
+ ```ruby
84
+ rquire 'pp'
85
+
86
+ test_values = [:sym, 1, 1.01, Symbol, Integer, Float, String, TrueClass, FalseClass, NilClass, '', true, false, nil]
87
+
88
+ results = test_values.map do |value|
89
+ { value: value,
90
+ class: value.class,
91
+ attic: [value.attic?, value.attic? && value.attic.object_id] }
92
+ end
93
+ ```
44
94
 
95
+ which produced the same results for both.
96
+
97
+ ```ruby
98
+ attic> RUBY_VERSION
99
+ => "3.2.0"
100
+
101
+ pp results
102
+ [
103
+ {:value=>:sym, :class=>Symbol, :attic=>[false, false]},
104
+ {:value=>1, :class=>Integer, :attic=>[false, false]},
105
+ {:value=>1.01, :class=>Float, :attic=>[false, false]},
106
+ {:value=>Symbol, :class=>Class, :attic=>[true, 564680]},
107
+ {:value=>Integer, :class=>Class, :attic=>[true, 564700]},
108
+ {:value=>Float, :class=>Class, :attic=>[true, 564720]},
109
+ {:value=>String, :class=>Class, :attic=>[true, 564740]},
110
+ {:value=>TrueClass, :class=>Class, :attic=>[true, 564760]},
111
+ {:value=>FalseClass, :class=>Class, :attic=>[true, 564780]},
112
+ {:value=>NilClass, :class=>Class, :attic=>[true, 564800]},
113
+ {:value=>"", :class=>String, :attic=>[true, 602880]}
114
+ {:value=>true, :class=>TrueClass, :attic=>[true, 13840]},
115
+ {:value=>false, :class=>FalseClass, :attic=>[true, 13860]},
116
+ {:value=>nil, :class=>NilClass, :attic=>[true, 40]}
117
+ ]
118
+ ```
119
+ ```ruby
120
+ attic> RUBY_VERSION
121
+ => "2.6.8"
122
+
123
+ pp results
124
+ [
125
+ {:value=>:sym, :class=>Symbol, :attic=>[false, false]},
126
+ {:value=>1, :class=>Integer, :attic=>[false, false]},
127
+ {:value=>1.01, :class=>Float, :attic=>[false, false]},
128
+ {:value=>Symbol, :class=>Class, :attic=>[true, 2844115920]},
129
+ {:value=>Integer, :class=>Class, :attic=>[true, 2844089400]},
130
+ {:value=>Float, :class=>Class, :attic=>[true, 2844087700]},
131
+ {:value=>String, :class=>Class, :attic=>[true, 2844122580]},
132
+ {:value=>TrueClass, :class=>Class, :attic=>[true, 2844136260]},
133
+ {:value=>FalseClass, :class=>Class, :attic=>[true, 2844136000]},
134
+ {:value=>NilClass, :class=>Class, :attic=>[true, 2844139060]},
135
+ {:value=>"", :class=>String, :attic=>[true, 2845261220]},
136
+ {:value=>true, :class=>TrueClass, :attic=>[true, 2844136280]},
137
+ {:value=>false, :class=>FalseClass, :attic=>[true, 2844136020]},
138
+ {:value=>nil, :class=>NilClass, :attic=>[true, 2844139080]}
139
+ ]
140
+ ```
45
141
 
46
142
  ## Credits
47
143
 
48
- * `gems@solutious.com` (@delano)
144
+ * (@delano) Delano Mandelbaum
49
145
 
50
146
 
51
147
  ## License
52
148
 
53
149
  MIT
54
-
data/attic.gemspec CHANGED
@@ -1,20 +1,23 @@
1
- @spec = Gem::Specification.new do |s|
2
- s.name = "attic"
3
- s.version = "0.6-RC1"
4
- s.summary = "When in doubt, store it in the attic"
5
- s.description = "Attic: a place to hide metadata about the class or variable itself (e.g. SHA hash summaries)."
6
- s.author = "Delano Mandelbaum"
7
- s.email = "gems@solutious.com"
8
- s.homepage = "http://github.com/delano/attic"
9
- s.executables = %w[]
10
- s.require_paths = %w[lib]
11
- s.extra_rdoc_files = %w[README.md]
12
- s.licenses = ["MIT"] # https://spdx.org/licenses/MIT-Modern-Variant.html
13
- s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.md"]
14
- s.files = %w(
15
- README.md
16
- Rakefile
17
- attic.gemspec
18
- lib/attic.rb
19
- )
1
+ Gem::Specification.new do |s|
2
+ s.name = "attic"
3
+ s.version = "0.9.0-rc2"
4
+ s.summary = "When in doubt, store it in the attic"
5
+ s.description = "Attic: a place to hide metadata about the class or variable itself (e.g. SHA hash summaries)."
6
+ s.authors = ["Delano Mandelbaum"]
7
+ s.email = "gems@solutious.com"
8
+ s.homepage = "https://github.com/delano/attic"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
12
+ s.bindir = "exe"
13
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
14
+ s.require_paths = ["lib"]
15
+
16
+ s.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
17
+
18
+ s.add_dependency "rake", ">= 13.0.6"
19
+
20
+ # Add development dependencies
21
+ s.add_development_dependency "rspec", "~> 3.0"
22
+ s.add_development_dependency "rubocop", "~> 1.0"
20
23
  end
@@ -0,0 +1,66 @@
1
+ #
2
+ # = Attic::ClassMethods
3
+ #
4
+ module Attic
5
+ # Adds a few methods for accessing the metaclass of an
6
+ # object. We do this with great caution since the Object
7
+ # class is as global as it gets in Ruby.
8
+ module ClassMethods
9
+ # A list of all the attic variables defined for this class.
10
+ attr_reader :attic_variables
11
+
12
+ # A quick way to check if the current object already has a
13
+ # dedicated singleton class. We want to know this because
14
+ # this is where our attic variables will be stored.
15
+ def attic?
16
+ return false if NoSingletonError.member? self
17
+
18
+ # NOTE: Calling this on an object for the first time lazily
19
+ # creates a singleton class for itself. Another way of doing
20
+ # the same thing is to attempt defining a singleton method
21
+ # for the object. In either case, objects that cannot have
22
+ # cannot have a dedicated singleton class (e.g. nil, true,
23
+ # false) will raise a TypeError. We rescue this and add the
24
+ # object to the NoSingletonError list so we don't have to
25
+ # keep trying to access its singleton class.
26
+ !singleton_class.nil?
27
+
28
+ rescue TypeError
29
+ # Remember for next time.
30
+ NoSingletonError.add_member self
31
+ false
32
+ end
33
+
34
+ def attic(name=nil)
35
+ return singleton_class if name.nil?
36
+
37
+ name = name.normalize
38
+
39
+ self.attic_variables << name unless attic_variable? name
40
+
41
+ safe_name = "@_attic_#{name}"
42
+ instance_variable_set(safe_name, name)
43
+ end
44
+
45
+ def attic_variable?(name)
46
+ attic_variables.include? name.normalize
47
+ end
48
+
49
+ def attic_variable_set(name, val)
50
+ unless attic_variable? name
51
+ attic_variables << name.normalize
52
+ end
53
+ attic.instance_variable_set("@_attic_#{name}", val)
54
+ end
55
+
56
+ def attic_variable_get(name)
57
+ instance_variable_get("@_attic_#{name}")
58
+ end
59
+
60
+ protected
61
+
62
+ def normalize(name)
63
+ name.to_s.gsub(/\@[\?\!\=]$/, '_').to_sym
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,70 @@
1
+
2
+ class NoMetaClass < RuntimeError
3
+ end
4
+
5
+ # = Object
6
+ #
7
+ # These methods are copied directly from _why's metaid.rb.
8
+ # See: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
9
+ class Object
10
+
11
+ unless defined?(::Object::NOMETACLASS)
12
+ # An Array of classes which do not have metaclasses.
13
+ NOMETACLASS = [Symbol, Fixnum].freeze
14
+ end
15
+
16
+ def nometaclass?
17
+ NOMETACLASS.member?(self)
18
+ end
19
+
20
+ def metaclass?
21
+ !NOMETACLASS.member?(self.class)
22
+ end
23
+
24
+ # A convenient method for getting the metaclass of the current object.
25
+ # i.e.
26
+ #
27
+ # class << self; self; end;
28
+ #
29
+ # NOTE: Some Ruby class do not have meta classes (see: NOMETACLASS).
30
+ # For these classes, this method returns the class itself. That means
31
+ # the instance variables will stored in the class itself.
32
+ def metaclass
33
+ if !self.metaclass?
34
+ raise NoMetaClass, self
35
+ else
36
+ class << self; self; end;
37
+ end
38
+ end
39
+
40
+ # Execute a block +&blk+ within the metaclass of the current object.
41
+ def meta_eval &blk; metaclass.instance_eval &blk; end
42
+
43
+ # Add an instance method called +name+ to metaclass for the current object.
44
+ # This is useful because it will be available as a singleton method
45
+ # to all subclasses too.
46
+ def meta_def name, &blk
47
+ meta_eval { define_method name, &blk }
48
+ end
49
+
50
+ # Add a class method called +name+ for the current object's class. This
51
+ # isn't so special but it maintains consistency with meta_def.
52
+ def class_def name, &blk
53
+ class_eval { define_method name, &blk }
54
+ end
55
+
56
+
57
+ # A convenient method for getting the metaclass of the metaclass
58
+ # i.e.
59
+ #
60
+ # self.metaclass.metaclass
61
+ #
62
+ def metametaclass; self.metaclass.metaclass; end
63
+
64
+ def metameta_eval &blk; metametaclass.instance_eval &blk; end
65
+
66
+ def metameta_def name, &blk
67
+ metameta_eval { define_method name, &blk }
68
+ end
69
+
70
+ end
@@ -0,0 +1,52 @@
1
+
2
+ # Attic: A special place to store instance variables.
3
+
4
+ class NoMetaClass < RuntimeError
5
+ end
6
+
7
+ # = NoSingletonError
8
+ #
9
+ # This error is raised when an attempt is made to access the
10
+ # attic of an object which does not have a singleton class.
11
+ #
12
+ # This is a TypeError because it is not an exceptional
13
+ # condition. It is simply a condition that is not supported
14
+ # by the Attic module.
15
+ #
16
+ # == Usage
17
+ #
18
+ # require 'attic'
19
+ # class MyClass
20
+ # include Attic
21
+ # attic :name, :age
22
+ # end
23
+ #
24
+ class NoSingletonError < TypeError
25
+ unless defined?(MEMBERS)
26
+ # A Set of classes which do not have singleton classes
27
+ # (i.e. meta classes). This is used to prevent an exception
28
+ # the first time an attic is accessed. It's populated
29
+ # dynamically at start time by simply checking whether
30
+ # the object has a singleton. This only needs to be
31
+ # done once per class.
32
+ #
33
+ # We use a set here to avoid having to deal with duplicate
34
+ # values. Realistically there are only a few classes that
35
+ # do not have singleton classes. We could hard code them
36
+ # here which is not lost on us.
37
+ #
38
+ MEMBERS = Set.new
39
+ end
40
+
41
+ # Check if the given object is a member of the NoSingleton
42
+ # members list. This checks for the object itself and all
43
+ # of its ancestors. See the docs for `Enumerable#===` for
44
+ # more details.
45
+ def self.member?(obj)
46
+ MEMBERS === obj
47
+ end
48
+
49
+ def self.add_member(obj)
50
+ MEMBERS.merge [self]
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+
3
+ # = Attic::InstanceMethods
4
+ #
5
+ module Attic
6
+ # Adds a few methods for object instances to access the
7
+ # attic variables of their class.
8
+ module InstanceMethods
9
+
10
+ def attic
11
+ raise NoSingleton, self, caller unless attic?
12
+
13
+ singleton_class
14
+
15
+ rescue TypeError
16
+ NoSingleton.add_member self
17
+ end
18
+
19
+ def attic_variables
20
+ self.class.attic_variables
21
+ end
22
+
23
+ def attic_variable?(name)
24
+ self.class.attic_variable? name
25
+ end
26
+
27
+ def attic_variable_set(name, val)
28
+ attic_variables << name unless attic_variable? name
29
+ attic.instance_variable_set("@___attic_#{name}", val)
30
+ end
31
+
32
+ def attic_variable_get(name)
33
+ attic.instance_variable_get("@___attic_#{name}")
34
+ end
35
+ end
36
+ end