attic 0.6.pre.RC1 → 1.0.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: 65eaa51d7ee74a1e97bef40a3cdb0c6cb8c78ca29e0cd8f8bb55e0cba641f664
4
+ data.tar.gz: fbc75d2a1063522b54edfc6d608a2f4599ac2b34c39a92aa400b138138f19cd8
5
5
  SHA512:
6
- metadata.gz: 920f145e1b3eb27151653a69be076fe3224c82e2781695209e2017ac4634708855dc0331ad254a9765bd185f25e566b1c6a712bc6118668c201ec0fa240239fc
7
- data.tar.gz: e0e4204bd76ae3f76c7c3a51166c278f69b2152db651a2ccd28ce8d559f281bfbf18e253713608fad2d9d488b96f52d3397070de7cc606842c3f47971db1276a
6
+ metadata.gz: 2a54f4d47b2aa70d9a8d6ee30578d5c06360e9e5c2ffd3c5dfa196086b40122dfee3d84b079cbabd9a24076262b8d8b4521d50a649fe111c3f8f3b80d20945da
7
+ data.tar.gz: b06ab0a4a824c1536046f4d36ee0a42ce0e5315e214ef398955cf45f7046c56937d93333102044a1a4607fbe4780dd176d0377569126320e81f623c220527c16
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,62 @@
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.2)
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-22
50
+
51
+ DEPENDENCIES
52
+ byebug
53
+ pry
54
+ pry-doc
55
+ rubocop
56
+ tryouts (= 2.2.0.pre.RC1)
57
+
58
+ RUBY VERSION
59
+ ruby 3.2.0p0
60
+
61
+ BUNDLED WITH
62
+ 2.4.12
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,146 @@
1
- # Attic - v0.6-RC1 (2021-07-01)
1
+ # Attic - v1.0-RC1 (2024-04-01)
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
+
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
4
13
 
5
- ## Example
6
14
 
7
15
  ```ruby
8
16
  require 'attic'
9
17
 
18
+ # Extend a class with Attic
10
19
  class String
11
20
  extend Attic
21
+
22
+ # Add an instance variable to the attic. All instances
23
+ # of this class will have this variable available.
12
24
  attic :timestamp
13
25
  end
14
26
 
15
- a = "anything"
16
- a.timestamp = "1980-11-18"
27
+ # Instantiate a new String object
28
+ a = "A lovely example of a string"
29
+
30
+ # Set and get the timestamp
31
+ a.timestamp = "1990-11-18"
32
+ a.timestamp # => "1990-11-18"
33
+
34
+ # The timestamp is not visible in the public interface
17
35
  a.instance_variables # => []
18
- a.timestamp # 1980-11-18
19
36
 
37
+ # But it is available in the attic
20
38
  a.attic_variables # => [:timestamp]
21
39
 
40
+ # If you prefer getting your hands dirty, you can also
41
+ # interact with the attic at a lower level.
22
42
  a.attic_variable_set :tags, [:a, :b, :c]
23
- a.attic_variable_get :tags # [:a, :b, :c]
43
+ a.attic_variable_get :tags # => [:a, :b, :c]
24
44
 
45
+ # Looking at the attic again shows that the timestamp
46
+ # is still there too.
25
47
  a.attic_variables # => [:timestamp, :tags]
26
48
  ```
27
49
 
28
- ## Some objects have no metaclasses
29
50
 
30
- Symbol objects do not have metaclasses so instance variables are hidden in the object itself.
51
+ ### **Objects without singleton classes
31
52
 
53
+ Symbol, Integer, Float, TrueClass, FalseClass, NilClass, and Integer are all objects that do not have singleton classes. TrueClass, FalseClass, and NilClass are all singletons themselves. Integer is a singleton of Integer.
32
54
 
33
- ## Installation
55
+ These objects do not have metaclasses so the attic is hidden in the object itself.
56
+
57
+ ### Explore in irb
58
+
59
+ ```shell
60
+ $ irb -r attic
61
+ ```
62
+
63
+ ---
34
64
 
35
- Via Rubygems, one of:
65
+ ## Installation
36
66
 
37
67
  ```shell
38
68
  $ gem install attic
39
69
  ```
40
70
 
71
+ ```shell
72
+ $ bundle install attic
73
+ ```
74
+
41
75
  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]
76
+ * [attic-latest.tar.gz](https://github.com/delano/attic/tarball/latest)
77
+ * [attic-latest.zip](https://github.com/delano/attic/zipball/latest)
78
+
79
+
80
+ ## Proofs
44
81
 
82
+ Tested the following code in IRB for Ruby 2.6.8 and 3.0.2:
45
83
 
46
- ## Credits
84
+ ```ruby
85
+ rquire 'pp'
86
+
87
+ test_values = [:sym, 1, 1.01, Symbol, Integer, Float, String, TrueClass, FalseClass, NilClass, '', true, false, nil]
47
88
 
48
- * `gems@solutious.com` (@delano)
89
+ results = test_values.map do |value|
90
+ { value: value,
91
+ class: value.class,
92
+ attic: [value.attic?, value.attic? && value.attic.object_id] }
93
+ end
94
+ ```
95
+
96
+ which produced the same results for both.
97
+
98
+ ```ruby
99
+ attic> RUBY_VERSION
100
+ => "3.2.0"
101
+
102
+ pp results
103
+ [
104
+ {:value=>:sym, :class=>Symbol, :attic=>[false, false]},
105
+ {:value=>1, :class=>Integer, :attic=>[false, false]},
106
+ {:value=>1.01, :class=>Float, :attic=>[false, false]},
107
+ {:value=>Symbol, :class=>Class, :attic=>[true, 564680]},
108
+ {:value=>Integer, :class=>Class, :attic=>[true, 564700]},
109
+ {:value=>Float, :class=>Class, :attic=>[true, 564720]},
110
+ {:value=>String, :class=>Class, :attic=>[true, 564740]},
111
+ {:value=>TrueClass, :class=>Class, :attic=>[true, 564760]},
112
+ {:value=>FalseClass, :class=>Class, :attic=>[true, 564780]},
113
+ {:value=>NilClass, :class=>Class, :attic=>[true, 564800]},
114
+ {:value=>"", :class=>String, :attic=>[true, 602880]}
115
+ {:value=>true, :class=>TrueClass, :attic=>[true, 13840]},
116
+ {:value=>false, :class=>FalseClass, :attic=>[true, 13860]},
117
+ {:value=>nil, :class=>NilClass, :attic=>[true, 40]}
118
+ ]
119
+ ```
120
+ ```ruby
121
+ attic> RUBY_VERSION
122
+ => "2.6.8"
123
+
124
+ pp results
125
+ [
126
+ {:value=>:sym, :class=>Symbol, :attic=>[false, false]},
127
+ {:value=>1, :class=>Integer, :attic=>[false, false]},
128
+ {:value=>1.01, :class=>Float, :attic=>[false, false]},
129
+ {:value=>Symbol, :class=>Class, :attic=>[true, 2844115920]},
130
+ {:value=>Integer, :class=>Class, :attic=>[true, 2844089400]},
131
+ {:value=>Float, :class=>Class, :attic=>[true, 2844087700]},
132
+ {:value=>String, :class=>Class, :attic=>[true, 2844122580]},
133
+ {:value=>TrueClass, :class=>Class, :attic=>[true, 2844136260]},
134
+ {:value=>FalseClass, :class=>Class, :attic=>[true, 2844136000]},
135
+ {:value=>NilClass, :class=>Class, :attic=>[true, 2844139060]},
136
+ {:value=>"", :class=>String, :attic=>[true, 2845261220]},
137
+ {:value=>true, :class=>TrueClass, :attic=>[true, 2844136280]},
138
+ {:value=>false, :class=>FalseClass, :attic=>[true, 2844136020]},
139
+ {:value=>nil, :class=>NilClass, :attic=>[true, 2844139080]}
140
+ ]
141
+ ```
49
142
 
50
143
 
51
144
  ## License
52
145
 
53
146
  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 = "1.0.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 keep
25
+ # trying to access its singleton class over and over.
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, Integer].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
data/lib/attic.rb CHANGED
@@ -1,157 +1,174 @@
1
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
2
+ # Attic: A special place to store instance variables.
10
3
 
11
- unless defined?(::Object::NOMETACLASS)
12
- # An Array of classes which do not have metaclasses.
13
- NOMETACLASS = [Symbol, Integer]
14
- end
15
-
16
- def nometaclass?
17
- NOMETACLASS.member?(self)
18
- end
4
+ require_relative "attic/class_methods"
5
+ require_relative "attic/errors"
6
+ require_relative "attic/instance_methods"
19
7
 
20
- def metaclass?
21
- !NOMETACLASS.member?(self.class)
22
- end
8
+ # = Attic
9
+ #
10
+ # == Usage:
11
+ #
12
+ # require 'attic'
13
+ # class MyClass
14
+ # include Attic
15
+ # attic :name, :age
16
+ # end
17
+ #
18
+ # obj = MyClass.new
19
+ # obj.attic.nickname = 'My Classic Automobile'
20
+ # obj.attic.secret_age = 27
21
+ #
22
+ # obj.nickname #=> 'My Classic Automobile'
23
+ # obj.secret_age #=> 27
24
+ # obj.to_h #=> {}
25
+ #
26
+ # OR
27
+ #
28
+ # require 'attic'
29
+ # Attic.construct MyClass, :nickname, :secret_age
30
+ #
31
+ #
32
+ # == Description:
33
+ #
34
+ # Attic is a module that allows you to store instance variables
35
+ # in a dedicated singleton class. This is useful for storing
36
+ # instance variables that you don't want to be available to
37
+ # the public interface of your class. e.g. you want to store
38
+ # a value for the running instance but want to prevent it
39
+ # from being serialized.
40
+ #
41
+ # == Why?
42
+ #
43
+ # == Important Notes:
44
+ #
45
+ # Some objects just straight up are not capable of contructing
46
+ # an attic. `Symbols`, `Integers`, and `Floats` specifically do not
47
+ # have a dedicated singleton classes. These are what ruby
48
+ # internals refer to as "immediate values". They're special
49
+ # in that they are not objects in the traditional sense.
50
+ # They're just values (they're not even instances of a
51
+ # class 😮‍💨).
52
+ #
53
+ # When you call attic on an immediate value you get an error.
54
+ #
55
+ # :sym.attic #=> raises NoSingleton error
56
+ # 1.attic #=> Ditto
57
+ # 1.0.1.attic #=> Ditto again
58
+ #
59
+ #
60
+ # The other objects that do not have singleton classes are
61
+ # `true`, `false`, and `nil`. Calling attic on these don't
62
+ # raise an error but they simply return their class. This
63
+ # is because they are all instances of their same singleton
64
+ # class.
65
+ #
66
+ # true.attic #=> TrueClass
67
+ # false.attic #=> FalseClass
68
+ # nil.attic #=> NilClass
69
+ #
70
+ # Note: this means that every instance of nil
71
+ # returns the exact same singleton class. This is different
72
+ # from the behaviour of all other objects.
73
+ #
74
+ # nil.attic.object_id #=> 800
75
+ # nil.attic.object_id #=> 800
76
+ #
77
+ #
78
+ # NilClass, TrueClass, and FalseClass on the otherhand each
79
+ # have their own singleton class. Calling attic on these
80
+ # returns the singleton class for each of them. But again
81
+ # a singleton class for each of them but again they all
82
+ #
83
+ # TrueClass.attic #=> #<Class:TrueClass>
84
+ # FalseClass.attic #=> #<Class:FalseClass>
85
+ # NilClass.attic #=> #<Class:NilClass>
86
+ #
87
+ #
88
+ # For comparison, here's what happens with a String (each
89
+ # time attic is called on a new string you get a new
90
+ # singleton)
91
+ #
92
+ # "".attic #=> #<Class:#<String:0x0001234>>
93
+ # "".attic #=> #<Class:#<String:0x0005678>>
94
+ # "".attic.object_id #=> 1234
95
+ # "".attic.object_id #=> 5678
96
+ #
97
+ # nil.attic #=> NilClass
98
+ # nil.attic #=> NilClass
99
+ # nil.attic.object_id #=> 800
100
+ # nil.attic.object_id #=> 800
101
+ #
102
+ module Attic
103
+ VERSION = '0.9.0-RC1'.freeze unless defined?(VERSION)
23
104
 
24
- # A convenient method for getting the metaclass of the current object.
25
- # i.e.
105
+ # A convenince method at the class level for including
106
+ # ConstructMethods in the given object specifically.
26
107
  #
27
- # class << self; self; end;
108
+ # e.g.
28
109
  #
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 be stored in the class itself.
32
- def metaclass
33
- if self.metaclass?
34
- class << self
35
- self
36
- end
37
- else
38
- self
39
- end
40
- end
41
-
42
- def metaclassfly
43
- location = self.class
44
- attr_name = "@@_attic_#{self.object_id}"
45
- unless location.class_variable_defined? attr_name
46
- location.class_variable_set attr_name, Class.new
47
- end
48
- location.class_variable_get attr_name
49
- end
50
-
51
- # Execute a block +&blk+ within the metaclass of the current object.
52
- def meta_eval &blk
53
- metaclass.instance_eval blk
54
- end
55
-
56
- # Add an instance method called +name+ to metaclass for the current object.
57
- # This is useful because it will be available as a singleton method
58
- # to all subclasses too.
59
- def meta_def name, &blk
60
- meta_eval { define_method name, &blk }
61
- end
62
-
63
- # Add a class method called +name+ for the current object's class. This
64
- # isn't so special but it maintains consistency with meta_def.
65
- def class_def name, &blk
66
- class_eval { define_method name, &blk }
67
- end
68
-
69
-
70
- # A convenient method for getting the metaclass of the metaclass
71
- # i.e.
110
+ # Add Attic support to all objects available now and
111
+ # in the future:
112
+ #
113
+ # Attic.construct(Object)
72
114
  #
73
- # self.metaclass.metaclass
115
+ # which is equivalent to:
74
116
  #
75
- def metametaclass
76
- self.metaclass.metaclass
117
+ # class Object; include Attic::ClassMethods; end
118
+ #
119
+ def self.construct(obj)
120
+ obj.include Attic::ClassMethods
77
121
  end
78
122
 
79
- def metameta_eval &blk
80
- metametaclass.instance_eval blk
123
+ # Friendly exception to say we're not to be included
124
+ #
125
+ def self.included(obj)
126
+ raise RuntimeError, "Did you mean to `extend Attic`` in #{obj}"
81
127
  end
82
128
 
83
- def metameta_def name, &blk
84
- metameta_eval { define_method name, &blk }
85
- end
86
- end
129
+ def self.extended(obj)
130
+ # If the class has already been extended, we don't need
131
+ # to add the class methods again.
132
+ return if obj.ancestors.member? self
87
133
 
134
+ # Add the instance methods for accessing attic variables
135
+ obj.send :include, Attic::InstanceMethods
88
136
 
137
+ # If the object doesn't have a dedicated singleton class
138
+ # an exception will be raised so it can be caught and
139
+ # handled appropriately.
140
+ obj.attic.instance_variable_defined?("@attic_variables")
89
141
 
90
- # = Attic
91
- #
92
- # A place to store instance variables.
93
- #
94
- module Attic
95
- VERSION = '0.6-RC1' unless defined?(VERSION)
142
+ obj.attic.instance_variable_set("@attic_variables", [])
96
143
 
97
- module InstanceMethods
98
- def attic_variables
99
- self.class.attic_variables
100
- end
101
- alias_method :attic_vars, :attic_variables
102
- def attic_variable? n
103
- self.class.attic_variable? n
144
+ def obj.inherited(klass)
145
+ super
146
+ attic_vars = self.attic_variables.clone
147
+ klass.attic.instance_variable_set("@attic_variables", attic_vars)
104
148
  end
105
- def attic_variable_set(n,v)
106
- attic_variables << n unless attic_variable? n
107
- # binding.pry
108
- metaclassfly.instance_variable_set("@___attic_#{n}", v)
109
- end
110
- def attic_variable_get(n)
111
- metaclassfly.instance_variable_get("@___attic_#{n}")
112
- end
113
- def get_binding
114
- binding
115
- end
116
- end
117
-
118
- def self.included(o)
119
- raise "You probably meant to 'extend Attic' in #{o}"
120
- end
121
-
122
- def self.extended(o)
123
- # This class has already been extended.
124
- return if o.ancestors.member? Attic::InstanceMethods
125
-
126
- # Add the instance methods for accessing attic variables
127
- o.send :include, Attic::InstanceMethods
128
-
129
- o.metaclass.instance_variable_set("@attic_variables", [])
130
- o.class_eval do
131
- def self.inherited(o2)
132
- attic_vars = self.attic_variables.clone
133
- o2.metaclass.instance_variable_set("@attic_variables", attic_vars)
149
+ if method_defined? :instance_variables
150
+ instance_variables_orig = instance_method(:instance_variables)
151
+ define_method :instance_variables do
152
+ ret = _instance_variables_orig.bind(self).call.clone
153
+ ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
154
+ ret
134
155
  end
135
- if method_defined? :instance_variables
136
- old_instance_variables = instance_method(:instance_variables)
137
- define_method :instance_variables do
138
- ret = old_instance_variables.bind(self).call.clone
139
- ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
140
- ret
141
- end
142
- define_method :all_instance_variables do
143
- old_instance_variables.bind(self).call
144
- end
156
+ define_method :all_instance_variables do
157
+ _instance_variables_orig.bind(self).call
145
158
  end
146
159
  end
160
+
161
+ rescue TypeError => e
162
+ raise NoSingleton, obj, caller
147
163
  end
148
164
 
149
165
  # A class method for defining variables to store in the attic.
150
- # * +junk+ is a list of variables names. Accessor methods are
166
+ # * +names+ is a list of variables names. Accessor methods are
151
167
  # created for each variable name in the list.
152
168
  #
153
- # Returns the list of attic variable names or if not junk was
154
- # given, returns the metaclass.
169
+ # Returns an Array of all attic variables for the current
170
+ # class unless no arguments are given in which case it
171
+ # returns its singleton.
155
172
  #
156
173
  # e.g.
157
174
  #
@@ -162,24 +179,28 @@ module Attic
162
179
  # * <tt>String#timestamp</tt> for getting the value
163
180
  # * <tt>String#timestamp</tt> for setting the value
164
181
  #
165
- def attic *junk
166
- return metaclass if junk.empty?
167
- junk.each do |name|
182
+ def attic(*names)
183
+ return singleton_class if names.empty?
184
+
185
+ names.each do |name|
168
186
  next if attic_variable? name
187
+
169
188
  self.attic_variables << name
170
189
 
171
- unless method_defined? name
190
+ unless method_defined?(name)
172
191
  define_method(name) do
173
192
  attic_variable_get name
174
193
  end
175
194
  end
176
- unless method_defined? "#{name}="
195
+
196
+ unless method_defined?("#{name}=")
177
197
  define_method("#{name}=") do |val|
178
198
  attic_variable_set name, val
179
199
  end
180
200
  end
181
201
  end
182
- attic_vars
202
+
203
+ attic_variables # only after defining new attic vars
183
204
  end
184
205
 
185
206
  # Returns an Array of attic variables for the current class.
@@ -190,17 +211,27 @@ module Attic
190
211
  # String.attic_variables # => [:timestamp]
191
212
  #
192
213
  def attic_variables
193
- a = self.metaclass.instance_variable_get("@attic_variables")
194
- a ||= self.metaclass.instance_variable_set("@attic_variables", [])
214
+ a = attic.instance_variable_get('@attic_variables')
215
+ a ||= attic.instance_variable_set('@attic_variables', [])
195
216
  a
196
217
  end
197
- alias_method :attic_vars, :attic_variables
198
218
 
199
- def attic_variable?(n)
200
- attic_variables.member? n
219
+ def attic_variable?(name)
220
+ attic_variables.member? name
201
221
  end
202
-
203
222
  end
204
223
 
205
224
  # - Module#instance_method returns an UnboundMethod
206
225
  # - http://ruby-doc.org/core/classes/Module.html#M001659
226
+
227
+ # Add some candy when we're in irb
228
+ if defined?(IRB)
229
+ require 'irb/completion'
230
+ IRB.conf[:PROMPT][:ATTIC] = {
231
+ PROMPT_I: "attic> ",
232
+ PROMPT_S: "attic%l> ",
233
+ PROMPT_C: "attic* ",
234
+ RETURN: "=> %s\n\n"
235
+ }
236
+ IRB.conf[:PROMPT_MODE] = :ATTIC
237
+ end
@@ -0,0 +1,65 @@
1
+ require_relative '../lib/attic'
2
+
3
+ #
4
+ # Tests for the Object mixins that Attic relies on.
5
+ #
6
+
7
+ ## Has a valid NoMetaClass exception class
8
+ NoMetaClass < RuntimeError
9
+ #=> true
10
+
11
+ ## Has a pre-populated array of built-ins without a metaclass
12
+ begin
13
+ Object::NOMETACLASS
14
+ rescue NameError => e
15
+ e.class
16
+ end
17
+ #=> NameError
18
+
19
+ ## Has Object#metaclass method
20
+ Object.new.respond_to? :metaclass
21
+ #=> false
22
+
23
+ ## Has Object#singleton_class method
24
+ Object.new.respond_to? :singleton_class
25
+ #=> true
26
+
27
+ ## Object#singleton_class is a class
28
+ Object.new.singleton_class.class
29
+ #=> Class
30
+
31
+ ## Object#singleton_class is a class
32
+ Object.new.singleton_class.object_id.class
33
+ #=> Integer
34
+
35
+ ## Object#singleton_class is a class
36
+ a = Object.new
37
+ b = Object.new
38
+ a.singleton_class.object_id == b.singleton_class.object_id
39
+ #=> false
40
+
41
+ ## Object#singleton_class is an Object class
42
+ Object.new.singleton_class.superclass
43
+ #=> Object
44
+
45
+ ## Object#singleton_class is equivalent to `class << self; self; end;`
46
+ a = Object.new
47
+ a.singleton_class == (class << a; self; end)
48
+ #=> true
49
+
50
+ ## Integer doesn't have a singleton_class
51
+ Integer.singleton_class?
52
+ #=> false
53
+
54
+ ## Symbol doesn't have a singleton_class
55
+ Symbol.singleton_class?
56
+ #=> false
57
+
58
+ ## Object has a singleton_class
59
+ Object.singleton_class?
60
+ #=> false
61
+
62
+ ## Object#singleton_class is equivalent to Object#singleton_class
63
+ a = Object.new
64
+ a.singleton_class == a.singleton_class
65
+ #=> true
@@ -0,0 +1,51 @@
1
+ require_relative '../lib/attic'
2
+
3
+ #
4
+ # Tests for the Attic module
5
+ #
6
+
7
+ ## A class we define can extend Attic
8
+ class ::ExampleClass
9
+ extend Attic
10
+ def kind() :unlikely_value end
11
+ end
12
+ ExampleClass.methods.member?(:attic)
13
+ #=> true
14
+
15
+ ## Trying to include Attic raises an exception
16
+ begin
17
+ class ::ExampleClass
18
+ include Attic
19
+ end
20
+ rescue => e
21
+ e.class
22
+ end
23
+ #=> RuntimeError
24
+
25
+ ## Can define attic variables at class level
26
+ ExampleClass.attic :size
27
+ w = ExampleClass.new
28
+ w.respond_to? :size
29
+ #=> true
30
+
31
+ ## Accessing attic vars at the instance level fails
32
+ begin
33
+ w = ExampleClass.new
34
+ w.attic :size, 2
35
+ rescue => e
36
+ e.class
37
+ end
38
+ #=> NoMethodError
39
+
40
+ ## Can access attic vars the long way though
41
+ w = ExampleClass.new
42
+ w.attic_variable_set :size, 2
43
+ w.attic_variable_get :size
44
+ #=> 2
45
+
46
+ ## Won't clobber an existing method with the same name
47
+ ## NOTE: But also won't tell you it didn't define the method
48
+ ExampleClass.attic :kind
49
+ a = ExampleClass.new
50
+ a.kind
51
+ #=> :unlikely_value
@@ -0,0 +1,29 @@
1
+ require 'attic'
2
+ class ::Worker
3
+ extend Attic
4
+ attic :size
5
+ end
6
+
7
+
8
+ ## save an instance variable the long way
9
+ w = Worker.new
10
+ w.metametaclass.instance_variable_set '@mattress', 'S&F'
11
+ w.metametaclass.instance_variable_get '@mattress'
12
+ #=> 'S&F'
13
+
14
+ ## save an instance variable the short way
15
+ w = Worker.new
16
+ w.size = :california_king
17
+ w.size
18
+ #=> :california_king
19
+
20
+ ## new instances don't cross streams
21
+ w = Worker.new
22
+ w.size
23
+ #=> nil
24
+
25
+ ## instance variables are hidden
26
+ w = Worker.new
27
+ w.metametaclass.instance_variable_set '@mattress', 'S&F'
28
+ w.instance_variables
29
+ ## []
@@ -0,0 +1,32 @@
1
+ require 'attic'
2
+
3
+ ## String can extend Attic
4
+ String.extend Attic
5
+ String.respond_to? :attic
6
+ #=> true
7
+
8
+ ## save an instance variable the long way
9
+ s = ""
10
+ s.metametaclass.instance_variable_set '@mattress', 'S&F'
11
+ s.metametaclass.instance_variable_get '@mattress'
12
+ #=> 'S&F'
13
+
14
+ ## can create attributes
15
+ String.attic :goodies
16
+ #=> [:goodies]
17
+
18
+ ## save an instance variable the short way
19
+ s = ""
20
+ s.goodies = :california_king
21
+ p s.instance_variables
22
+ p s.attic_vars
23
+ s.goodies
24
+ #=> :california_king
25
+
26
+ ## String instances don't cross streams
27
+ String.extend Attic
28
+ String.attic :name
29
+ a = "any"
30
+ a.name = :roger
31
+ a.name == "".name
32
+ #=> false
@@ -0,0 +1,68 @@
1
+ require_relative "../lib/attic"
2
+
3
+ Attic.construct Symbol #, :name
4
+
5
+ ## has list of no metaclass classes
6
+ NoSingletonError::MEMBERS
7
+ #=> [Symbol, Integer]
8
+
9
+ # ## Symbol metaclass does not raise an exception
10
+ # begin
11
+ # :any.attic.class
12
+ # rescue NoSingletonError
13
+ # :failed
14
+ # end
15
+ # #=> :failed
16
+
17
+ ## Accessing Symbol metaclass raises an exception
18
+ begin
19
+ :any.attic.class
20
+ rescue NoSingletonError
21
+ :failed
22
+ end
23
+ #=> :failed
24
+
25
+ ## Symbol instances don't cross streams
26
+ Symbol.extend Attic
27
+ Symbol.attic :name
28
+ a, b = :symbol1, :symbol2
29
+ a.name = :roger
30
+ [a.name, b.name]
31
+ #=> [:roger, nil]
32
+
33
+ ## attic? method exists
34
+ Symbol.extend Attic
35
+ :any.respond_to? :attic?
36
+ #=> true
37
+
38
+ ## attic? method is false for a Symbol", false do
39
+ :any.attic?
40
+ #=> false
41
+
42
+ ## A Symbol's attic vars appear in `all_instance_variables` do
43
+ Symbol.extend Attic
44
+ Symbol.attic :_name
45
+ a, b = :symbol1, :symbol2
46
+ a._name = :roger
47
+ a.all_instance_variables
48
+ #=> [:@___attic_name]
49
+
50
+ ## An Integer's attic vars appear in `all_instance_variables` do
51
+ Integer.extend Attic
52
+ Integer.attic :_name
53
+ a, b = 1, 2
54
+ a._name = :roger
55
+ a.all_instance_variables
56
+ #=> [:@___attic_name]
57
+
58
+ ## A Symbol's attic vars do not appear in `instance_variables` do
59
+ Symbol.extend Attic
60
+ Symbol.attic :name
61
+ a, b = :symbol1, :symbol2
62
+ a.name = :roger
63
+ a.instance_variables
64
+ #=> []
65
+
66
+ ## knows attic variables, [:name] do
67
+ Symbol.attic_variables
68
+ #=> [:name]
@@ -0,0 +1,17 @@
1
+ require 'attic'
2
+
3
+ class ::Worker
4
+ extend Attic
5
+ end
6
+
7
+ ## can set value", 100 do
8
+ a = Worker.new
9
+ a.attic_variable_set :space, 100
10
+ a.attic_variable_get :space
11
+ #=> 100
12
+
13
+ ## doesn't create accessor methods", false do
14
+ a = Worker.new
15
+ a.attic_variable_set :space, 100
16
+ a.respond_to? :space
17
+ #=> false
metadata CHANGED
@@ -1,53 +1,104 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.pre.RC1
4
+ version: 1.0.0.pre.RC2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
8
- autorequire:
9
- bindir: bin
8
+ autorequire:
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-22 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 13.0.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 13.0.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
13
55
  description: 'Attic: a place to hide metadata about the class or variable itself (e.g.
14
56
  SHA hash summaries).'
15
57
  email: gems@solutious.com
16
58
  executables: []
17
59
  extensions: []
18
- extra_rdoc_files:
19
- - README.md
60
+ extra_rdoc_files: []
20
61
  files:
62
+ - ".gitignore"
63
+ - ".rubocop.yml"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE.txt
21
67
  - README.md
22
68
  - Rakefile
23
69
  - attic.gemspec
24
70
  - lib/attic.rb
25
- homepage: http://github.com/delano/attic
71
+ - lib/attic/class_methods.rb
72
+ - lib/attic/core_ext.rb
73
+ - lib/attic/errors.rb
74
+ - lib/attic/instance_methods.rb
75
+ - try/01_mixins_tryouts.rb
76
+ - try/10_attic_tryouts.rb
77
+ - try/20_accessing_tryouts.rb
78
+ - try/25_string_tryouts.rb
79
+ - try/30_nometaclass_tryouts.rb
80
+ - try/40_explicit_accessor_tryouts.rb
81
+ homepage: https://github.com/delano/attic
26
82
  licenses:
27
83
  - MIT
28
84
  metadata: {}
29
- post_install_message:
30
- rdoc_options:
31
- - "--line-numbers"
32
- - "--title"
33
- - When in doubt, store it in the attic
34
- - "--main"
35
- - README.md
85
+ post_install_message:
86
+ rdoc_options: []
36
87
  require_paths:
37
88
  - lib
38
89
  required_ruby_version: !ruby/object:Gem::Requirement
39
90
  requirements:
40
91
  - - ">="
41
92
  - !ruby/object:Gem::Version
42
- version: '0'
93
+ version: 2.6.0
43
94
  required_rubygems_version: !ruby/object:Gem::Requirement
44
95
  requirements:
45
96
  - - ">"
46
97
  - !ruby/object:Gem::Version
47
98
  version: 1.3.1
48
99
  requirements: []
49
- rubygems_version: 3.2.21
50
- signing_key:
100
+ rubygems_version: 3.4.12
101
+ signing_key:
51
102
  specification_version: 4
52
103
  summary: When in doubt, store it in the attic
53
104
  test_files: []