equitable 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e534220f01acc864d7c2207df2357e64e1629bf52ed12f1819710cd6aebfffa2
4
+ data.tar.gz: '079708acf5302fffd47cf41ad4a546fc71e84b73d16229c0001faa8fb4b1c66b'
5
+ SHA512:
6
+ metadata.gz: 22271dd2594bcaa8083313171f5aa3cd190131a9f3b8632360e25069ea490cec7578e4dfd03ae558a63e2f5d34af9034b0280229543853da2295a0d54a73f256
7
+ data.tar.gz: 9017cba0ec0cd5aef2b9f04e4277df95cef3c714abf5d4d4f200ee4f5048bda0de58108d999d93a2c2fb8dee26534bbd7826926df1bcddbc430ecfee26d54521
data/.mutant.yml ADDED
@@ -0,0 +1,23 @@
1
+ usage: opensource
2
+ integration:
3
+ name: minitest
4
+ includes:
5
+ - lib
6
+ - test
7
+ requires:
8
+ - equitable
9
+ - test_helper
10
+ - equitable_test
11
+ matcher:
12
+ subjects:
13
+ - "Equitable*"
14
+ mutation:
15
+ timeout: 10.0
16
+ ignore_patterns:
17
+ # module_eval's __LINE__ + 1 argument only affects debugging metadata
18
+ - send{selector=module_eval}
19
+ coverage_criteria:
20
+ test_result: true
21
+ process_abort: true
22
+ environment_variables:
23
+ MUTANT: "true"
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ inherit_gem:
2
+ standard: config/base.yml
3
+ standard-performance: config/base.yml
4
+
5
+ plugins:
6
+ - rubocop-minitest
7
+ - rubocop-performance
8
+ - rubocop-rake
9
+
10
+ AllCops:
11
+ TargetRubyVersion: 3.3
12
+ NewCops: enable
13
+
14
+ Style/StringLiterals:
15
+ EnforcedStyle: double_quotes
16
+
17
+ Style/StringLiteralsInInterpolation:
18
+ EnforcedStyle: double_quotes
19
+
20
+ Style/Documentation:
21
+ Enabled: true
22
+
23
+ Minitest/MultipleAssertions:
24
+ Max: 4
25
+
26
+ Naming/MethodParameterName:
27
+ Exclude:
28
+ - "test/**/*"
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --markup markdown
2
+ --no-private
3
+ --protected
4
+ lib/**/*.rb
data/.yardstick.yml ADDED
@@ -0,0 +1,34 @@
1
+ ---
2
+ threshold: 100
3
+ require_exact_threshold: false
4
+ rules:
5
+ ApiTag::Presence:
6
+ enabled: true
7
+ exclude: []
8
+ ApiTag::Inclusion:
9
+ enabled: true
10
+ exclude: []
11
+ ApiTag::ProtectedMethod:
12
+ enabled: true
13
+ exclude: []
14
+ ApiTag::PrivateMethod:
15
+ enabled: true
16
+ exclude: []
17
+ ExampleTag:
18
+ enabled: false
19
+ exclude: []
20
+ ReturnTag:
21
+ enabled: true
22
+ exclude: []
23
+ Summary::Presence:
24
+ enabled: true
25
+ exclude: []
26
+ Summary::Length:
27
+ enabled: true
28
+ exclude: []
29
+ Summary::Delimiter:
30
+ enabled: true
31
+ exclude: []
32
+ Summary::SingleLine:
33
+ enabled: true
34
+ exclude: []
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Erik Berlin
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 ADDED
@@ -0,0 +1,296 @@
1
+ # Equitable
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/equitable)](https://rubygems.org/gems/equitable)
4
+ [![Test](https://github.com/sferik/equitable/actions/workflows/test.yml/badge.svg)](https://github.com/sferik/equitable/actions/workflows/test.yml)
5
+ [![Quality](https://github.com/sferik/equitable/actions/workflows/quality.yml/badge.svg)](https://github.com/sferik/equitable/actions/workflows/quality.yml)
6
+ [![Documentation](https://github.com/sferik/equitable/actions/workflows/yard.yml/badge.svg)](https://github.com/sferik/equitable/actions/workflows/yard.yml)
7
+ [![Mutation Testing](https://github.com/sferik/equitable/actions/workflows/mutant.yml/badge.svg)](https://github.com/sferik/equitable/actions/workflows/mutant.yml)
8
+
9
+ Equitable provides equality, equivalence, hashing, pattern matching, and
10
+ inspection methods for Ruby objects based on explicitly specified attributes.
11
+
12
+ Unlike approaches that automatically use all `attr_reader` attributes,
13
+ Equitable requires explicit specification of which attributes affect equality,
14
+ giving you full control over comparison behavior.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem "equitable"
22
+ ```
23
+
24
+ Or install it directly:
25
+
26
+ ```bash
27
+ gem install equitable
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```ruby
33
+ class Point
34
+ include Equitable.new(:x, :y)
35
+
36
+ attr_reader :x, :y
37
+
38
+ def initialize(x, y)
39
+ @x = x
40
+ @y = y
41
+ end
42
+ end
43
+
44
+ p1 = Point.new(1, 2)
45
+ p2 = Point.new(1, 2)
46
+
47
+ p1 == p2 # => true
48
+ p1.eql?(p2) # => true
49
+ p1.hash == p2.hash # => true
50
+ ```
51
+
52
+ ## Features
53
+
54
+ ### Selective Attribute Comparison
55
+
56
+ Only the attributes you specify are used for equality. Other instance variables
57
+ are ignored:
58
+
59
+ > [!TIP]
60
+ > This is useful when you have attributes that shouldn't affect equality, like
61
+ > timestamps, cached values, or display names.
62
+
63
+ ```ruby
64
+ class GeoLocation
65
+ include Equitable.new(:latitude, :longitude)
66
+
67
+ attr_reader :latitude, :longitude, :name
68
+
69
+ def initialize(latitude, longitude, name = nil)
70
+ @latitude = latitude
71
+ @longitude = longitude
72
+ @name = name
73
+ end
74
+ end
75
+
76
+ home = GeoLocation.new(37.7786, -122.4407, "Home")
77
+ work = GeoLocation.new(37.7786, -122.4407, "Work")
78
+
79
+ home == work # => true (name is not part of equality)
80
+ ```
81
+
82
+ ### Equality vs Equivalence
83
+
84
+ Equitable provides two comparison methods with different semantics:
85
+
86
+ #### `==` (Equality)
87
+
88
+ Returns `true` if the other object is an instance of the same class **or a
89
+ subclass**, and all specified attributes are equal using `==`:
90
+
91
+ ```ruby
92
+ class ColoredPoint < Point
93
+ attr_reader :color
94
+
95
+ def initialize(x, y, color)
96
+ super(x, y)
97
+ @color = color
98
+ end
99
+ end
100
+
101
+ point = Point.new(1, 2)
102
+ colored = ColoredPoint.new(1, 2, "red")
103
+
104
+ point == colored # => true (ColoredPoint is a subclass of Point)
105
+ colored == point # => false (Point is not a subclass of ColoredPoint)
106
+ ```
107
+
108
+ > [!IMPORTANT]
109
+ > In Ruby, the `==` operator is asymmetric when comparing across class
110
+ > hierarchies. A parent class instance can equal a subclass instance, but not
111
+ > vice versa.
112
+
113
+ #### `eql?` (Equivalence)
114
+
115
+ Returns `true` only if both objects are instances of the **exact same class**,
116
+ and all specified attributes are equal using `eql?`:
117
+
118
+ ```ruby
119
+ point = Point.new(1, 2)
120
+ colored = ColoredPoint.new(1, 2, "red")
121
+
122
+ point.eql?(colored) # => false (different classes)
123
+ colored.eql?(point) # => false (different classes)
124
+
125
+ point.eql?(Point.new(1, 2)) # => true (same class, same values)
126
+ ```
127
+
128
+ ### Hashing
129
+
130
+ Objects that are `eql?` will have the same hash code, making them safe for use
131
+ as Hash keys and in Sets:
132
+
133
+ > [!NOTE]
134
+ > Ruby's `Hash` and `Set` use `eql?` and `hash` together. Equitable ensures
135
+ > these methods stay consistent—objects that are `eql?` always have matching
136
+ > hash codes.
137
+
138
+ ```ruby
139
+ require "set"
140
+
141
+ p1 = Point.new(1, 2)
142
+ p2 = Point.new(1, 2)
143
+
144
+ # As Hash keys
145
+ locations = {}
146
+ locations[p1] = "first"
147
+ locations[p2] = "second"
148
+ locations.size # => 1 (p1 and p2 are the same key)
149
+
150
+ # In Sets
151
+ set = Set.new
152
+ set << p1
153
+ set << p2
154
+ set.size # => 1
155
+ ```
156
+
157
+ ### Pattern Matching
158
+
159
+ Equitable provides full support for Ruby's pattern matching syntax.
160
+
161
+ > [!TIP]
162
+ > Use array patterns `[x, y]` for positional matching when attribute order
163
+ > matters. Use hash patterns `{x:, y:}` for named matching when you want
164
+ > clarity or only need specific attributes.
165
+
166
+ #### Array Patterns
167
+
168
+ Use `deconstruct` for array-style pattern matching:
169
+
170
+ ```ruby
171
+ point = Point.new(3, 4)
172
+
173
+ case point
174
+ in [0, 0]
175
+ puts "origin"
176
+ in [x, 0]
177
+ puts "on x-axis at #{x}"
178
+ in [0, y]
179
+ puts "on y-axis at #{y}"
180
+ in [x, y]
181
+ puts "at (#{x}, #{y})"
182
+ end
183
+ # => "at (3, 4)"
184
+ ```
185
+
186
+ #### Hash Patterns
187
+
188
+ Use `deconstruct_keys` for hash-style pattern matching:
189
+
190
+ ```ruby
191
+ point = Point.new(3, 4)
192
+
193
+ case point
194
+ in { x: 0, y: 0 }
195
+ puts "origin"
196
+ in { x:, y: } if x == y
197
+ puts "on diagonal at #{x}"
198
+ in { x:, y: }
199
+ puts "at (#{x}, #{y})"
200
+ end
201
+ # => "at (3, 4)"
202
+ ```
203
+
204
+ #### Class Patterns
205
+
206
+ Combine with class checks:
207
+
208
+ ```ruby
209
+ case point
210
+ in Point(x: 0, y: 0)
211
+ puts "origin point"
212
+ in Point(x:, y:)
213
+ puts "point at (#{x}, #{y})"
214
+ end
215
+ ```
216
+
217
+ ### Clean Inspect Output
218
+
219
+ Equitable customizes `inspect` to show only the attributes used for equality:
220
+
221
+ ```ruby
222
+ class User
223
+ include Equitable.new(:id)
224
+
225
+ attr_reader :id, :name, :email, :created_at
226
+
227
+ def initialize(id, name, email)
228
+ @id = id
229
+ @name = name
230
+ @email = email
231
+ @created_at = Time.now
232
+ end
233
+ end
234
+
235
+ user = User.new(42, "Alice", "alice@example.com")
236
+ user.inspect
237
+ # => "#<User:0x00007f... @id=42>"
238
+ # Note: name, email, and created_at are not shown
239
+ ```
240
+
241
+ > [!NOTE]
242
+ > When debugging, remember that `inspect` only shows equality attributes. Use
243
+ > `instance_variables` to see all instance variables if needed.
244
+
245
+ ### Clean Ancestor Chain
246
+
247
+ The included module has a descriptive name in the ancestor chain:
248
+
249
+ ```ruby
250
+ Point.ancestors
251
+ # => [Point, Equitable(x, y), Object, Kernel, BasicObject]
252
+ ```
253
+
254
+ ## Nested Equitable Objects
255
+
256
+ Equitable objects can be nested and will compare correctly:
257
+
258
+ ```ruby
259
+ class Line
260
+ include Equitable.new(:start_point, :end_point)
261
+
262
+ attr_reader :start_point, :end_point
263
+
264
+ def initialize(start_point, end_point)
265
+ @start_point = start_point
266
+ @end_point = end_point
267
+ end
268
+ end
269
+
270
+ line1 = Line.new(Point.new(0, 0), Point.new(1, 1))
271
+ line2 = Line.new(Point.new(0, 0), Point.new(1, 1))
272
+
273
+ line1 == line2 # => true
274
+ ```
275
+
276
+ ## Error Handling
277
+
278
+ > [!CAUTION]
279
+ > Equitable validates arguments at include time. Errors will be raised
280
+ > immediately if you pass invalid arguments.
281
+
282
+ Equitable validates its arguments:
283
+
284
+ ```ruby
285
+ # At least one attribute is required
286
+ Equitable.new()
287
+ # => ArgumentError: at least one attribute is required
288
+
289
+ # Attributes must be Symbols
290
+ Equitable.new("name")
291
+ # => ArgumentError: attribute must be a Symbol, got String
292
+ ```
293
+
294
+ ## License
295
+
296
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "standard/rake"
6
+ require "rubocop/rake_task"
7
+ require "yard"
8
+ require "yardstick/rake/verify"
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "test"
12
+ t.libs << "lib"
13
+ t.test_files = FileList["test/**/*_test.rb"]
14
+ t.warning = true
15
+ end
16
+
17
+ RuboCop::RakeTask.new(:rubocop) do |task|
18
+ task.options = %w[--display-cop-names]
19
+ end
20
+
21
+ YARD::Rake::YardocTask.new(:yard) do |t|
22
+ t.files = ["lib/**/*.rb"]
23
+ t.options = ["--no-private", "--markup", "markdown"]
24
+ end
25
+
26
+ Yardstick::Rake::Verify.new(:yardstick) do |verify|
27
+ verify.threshold = 100
28
+ verify.require_exact_threshold = false
29
+ end
30
+
31
+ desc "Run Steep type checker"
32
+ task :steep do
33
+ sh "steep check"
34
+ end
35
+
36
+ desc "Run mutation testing"
37
+ task :mutant do
38
+ ENV["MUTANT"] = "true"
39
+ sh "bundle exec mutant run"
40
+ end
41
+
42
+ desc "Run all quality checks"
43
+ task quality: %i[rubocop steep yardstick]
44
+
45
+ desc "Run all checks (tests, quality, mutation)"
46
+ task default: %i[test quality mutant]
data/Steepfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ D = Steep::Diagnostic
4
+
5
+ target :lib do
6
+ signature "sig"
7
+ check "lib"
8
+
9
+ configure_code_diagnostics(D::Ruby.strict)
10
+ end
data/lib/equitable.rb ADDED
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Equitable provides equality, equivalence, hashing, pattern matching, and
4
+ # inspection methods for Ruby objects based on specified attributes.
5
+ #
6
+ # @example Basic usage
7
+ # class GeoLocation
8
+ # include Equitable.new(:latitude, :longitude)
9
+ #
10
+ # attr_reader :latitude, :longitude, :name
11
+ #
12
+ # def initialize(latitude, longitude, name = nil)
13
+ # @latitude = latitude
14
+ # @longitude = longitude
15
+ # @name = name
16
+ # end
17
+ # end
18
+ #
19
+ # loc1 = GeoLocation.new(1.0, 2.0, "Home")
20
+ # loc2 = GeoLocation.new(1.0, 2.0, "Work")
21
+ # loc1 == loc2 # => true (name is not part of equality)
22
+ #
23
+ # @example Pattern matching
24
+ # case location
25
+ # in GeoLocation(latitude:, longitude:) then "#{latitude}, #{longitude}"
26
+ # in [lat, lon] then "coords: #{lat}, #{lon}"
27
+ # end
28
+ #
29
+ # @api public
30
+ module Equitable
31
+ # The current version of the Equitable gem
32
+ VERSION = "1.0.0"
33
+
34
+ # Creates a module providing equality methods based on the given attributes
35
+ #
36
+ # @example Basic usage
37
+ # class Point
38
+ # include Equitable.new(:x, :y)
39
+ # attr_reader :x, :y
40
+ # end
41
+ #
42
+ # @param keys [Array<Symbol>] attribute names to use for equality
43
+ # @return [Module] a module to include in your class
44
+ # @raise [ArgumentError] if keys is empty or contains non-Symbols
45
+ #
46
+ # @api public
47
+ def self.new(*keys)
48
+ validate_keys!(keys)
49
+ build_module(keys.freeze)
50
+ end
51
+
52
+ # Validates that keys are non-empty and all Symbols
53
+ #
54
+ # @param keys [Array<Object>] the keys to validate
55
+ # @return [void]
56
+ # @raise [ArgumentError] if keys is empty or contains non-Symbols
57
+ #
58
+ # @api private
59
+ def self.validate_keys!(keys)
60
+ raise ArgumentError, "at least one attribute is required" if keys.empty?
61
+
62
+ invalid = keys.find { |key| !key.is_a?(Symbol) }
63
+ raise ArgumentError, "attribute must be a Symbol, got #{invalid.class}" if invalid
64
+ end
65
+ private_class_method :validate_keys!
66
+
67
+ # Instance methods mixed into classes that include an Equitable module
68
+ #
69
+ # @api private
70
+ module InstanceMethods
71
+ # Equality comparison allowing subclasses
72
+ #
73
+ # @param other [Object] object to compare
74
+ # @return [Boolean] true if other is_a? same class with equal attributes
75
+ def ==(other)
76
+ other.is_a?(self.class) && equitable_keys.all? { |key| public_send(key) == other.public_send(key) }
77
+ end
78
+
79
+ # Strict equality requiring exact class match
80
+ #
81
+ # @param other [Object] object to compare
82
+ # @return [Boolean] true if other is exact same class with eql? attributes
83
+ def eql?(other)
84
+ other.instance_of?(self.class) && equitable_keys.all? { |key| public_send(key).eql?(other.public_send(key)) }
85
+ end
86
+
87
+ # Hash code based on class and attribute values
88
+ #
89
+ # @return [Integer] hash code
90
+ def hash
91
+ [self.class, *deconstruct].hash
92
+ end
93
+
94
+ # Array deconstruction for pattern matching
95
+ #
96
+ # @return [Array] attribute values in order
97
+ def deconstruct
98
+ equitable_keys.map { |key| public_send(key) }
99
+ end
100
+
101
+ # Hash deconstruction for pattern matching
102
+ #
103
+ # @param requested [Array<Symbol>, nil] keys to include, or nil for all
104
+ # @return [Hash{Symbol => Object}] requested attribute key-value pairs
105
+ def deconstruct_keys(requested)
106
+ subset = requested.nil? ? equitable_keys : equitable_keys & requested
107
+ subset.to_h { |key| [key, public_send(key)] }
108
+ end
109
+
110
+ # String representation showing only equitable attributes
111
+ #
112
+ # @return [String] inspect output
113
+ def inspect
114
+ attrs = equitable_keys.map { |key| "@#{key}=#{public_send(key).inspect}" }.join(", ")
115
+ Object.instance_method(:to_s).bind_call(self).sub(/>\z/, " #{attrs}>")
116
+ end
117
+
118
+ # Pretty print output using PP's object formatting
119
+ #
120
+ # @param q [PP] pretty printer
121
+ # @return [void]
122
+ def pretty_print(q)
123
+ q.pp_object(self)
124
+ end
125
+
126
+ # Instance variables to display in pretty print output
127
+ #
128
+ # @return [Array<Symbol>] instance variable names
129
+ def pretty_print_instance_variables
130
+ equitable_keys.map { |key| :"@#{key}" }
131
+ end
132
+ end
133
+
134
+ # Builds the module with equality methods for the given keys
135
+ #
136
+ # @param keys [Array<Symbol>] attribute names (frozen)
137
+ # @return [Module] the configured module
138
+ #
139
+ # @api private
140
+ def self.build_module(keys)
141
+ Module.new do
142
+ include InstanceMethods
143
+
144
+ set_temporary_name("Equitable(#{keys.join(", ")})")
145
+
146
+ define_method(:equitable_keys) { keys }
147
+ private :equitable_keys
148
+ end
149
+ end
150
+ private_class_method :build_module
151
+ end
data/sig/equitable.rbs ADDED
@@ -0,0 +1,57 @@
1
+ # Type signatures for the Equitable gem
2
+ #
3
+ # Equitable provides equality, equivalence, hashing, and inspection methods
4
+ # for Ruby objects based on specified attributes.
5
+
6
+ module Equitable
7
+ # The current version of the Equitable gem.
8
+ VERSION: String
9
+
10
+ # Define equality methods for the given attributes.
11
+ #
12
+ # Creates a module that, when included, adds equality methods
13
+ # based on the specified attributes.
14
+ def self.new: (*Symbol keys) -> Module
15
+
16
+ # Private method to validate keys
17
+ def self.validate_keys!: (Array[untyped] keys) -> void
18
+
19
+ # Private method to build the module
20
+ def self.build_module: (Array[Symbol] keys) -> Module
21
+
22
+ # Instance methods mixed into classes that include an Equitable module.
23
+ # Assumes equitable_keys is provided by the dynamic module.
24
+ module InstanceMethods : _Equitable
25
+ end
26
+ end
27
+
28
+ # Interface for classes that include an Equitable module
29
+ interface _Equitable
30
+ # Returns the attribute keys used for equality comparison (private).
31
+ def equitable_keys: () -> Array[Symbol]
32
+
33
+ # Equivalence check - allows subclass instances.
34
+ def ==: (untyped other) -> bool
35
+
36
+ # Strict equality check - requires exact same class.
37
+ def eql?: (untyped other) -> bool
38
+
39
+ # Generate a hash code based on class and attribute values.
40
+ def hash: () -> Integer
41
+
42
+ # Array deconstruction for pattern matching.
43
+ def deconstruct: () -> Array[untyped]
44
+
45
+ # Hash deconstruction for pattern matching.
46
+ def deconstruct_keys: (Array[Symbol]? requested) -> Hash[Symbol, untyped]
47
+
48
+ # Custom inspect output showing equitable attributes.
49
+ def inspect: () -> String
50
+
51
+ # Pretty print output using PP's object formatting.
52
+ def pretty_print: (untyped q) -> void
53
+
54
+ # Returns instance variable names for PP (pretty print).
55
+ def pretty_print_instance_variables: () -> Array[Symbol]
56
+ end
57
+
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: equitable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Berlin
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |
13
+ Equitable provides a simple way to define equality (==), equivalence (eql?),
14
+ and hashing (hash) methods for Ruby objects based on specified attributes.
15
+ Includes pattern matching support and clean inspect output across Ruby versions.
16
+ email:
17
+ - sferik@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".mutant.yml"
23
+ - ".rubocop.yml"
24
+ - ".yardopts"
25
+ - ".yardstick.yml"
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - Steepfile
30
+ - lib/equitable.rb
31
+ - sig/equitable.rbs
32
+ homepage: https://github.com/sferik/equitable
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ homepage_uri: https://github.com/sferik/equitable
37
+ source_code_uri: https://github.com/sferik/equitable
38
+ changelog_uri: https://github.com/sferik/equitable/blob/main/CHANGELOG.md
39
+ rubygems_mfa_required: 'true'
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 4.0.4
55
+ specification_version: 4
56
+ summary: Define equality, equivalence, and hashing methods for Ruby objects
57
+ test_files: []