equalizer 0.0.11 → 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.
data/Rakefile CHANGED
@@ -1,5 +1,54 @@
1
- require 'bundler'
2
- require 'devtools'
1
+ # frozen_string_literal: true
3
2
 
4
- Bundler::GemHelper.install_tasks
5
- Devtools.init_rake_tasks
3
+ require "bundler/gem_tasks"
4
+
5
+ # Override release task to only tag and push to git
6
+ # The GitHub Action (push.yml) handles the actual gem push
7
+ Rake::Task["release"].clear
8
+ desc "Create tag v#{Equalizer::VERSION} and push to GitHub"
9
+ task "release", [:remote] => ["build", "release:guard_clean"] do |_, args|
10
+ Rake::Task["release:source_control_push"].invoke(args[:remote])
11
+ end
12
+ require "rake/testtask"
13
+ require "standard/rake"
14
+ require "rubocop/rake_task"
15
+ require "yard"
16
+ require "yardstick/rake/verify"
17
+
18
+ Rake::TestTask.new(:test) do |t|
19
+ t.libs << "test"
20
+ t.libs << "lib"
21
+ t.test_files = FileList["test/**/*_test.rb"]
22
+ t.warning = true
23
+ end
24
+
25
+ RuboCop::RakeTask.new(:rubocop) do |task|
26
+ task.options = %w[--display-cop-names]
27
+ end
28
+
29
+ YARD::Rake::YardocTask.new(:yard) do |t|
30
+ t.files = ["lib/**/*.rb"]
31
+ t.options = ["--no-private", "--markup", "markdown"]
32
+ end
33
+
34
+ Yardstick::Rake::Verify.new(:yardstick) do |verify|
35
+ verify.threshold = 100
36
+ verify.require_exact_threshold = false
37
+ end
38
+
39
+ desc "Run Steep type checker"
40
+ task :steep do
41
+ sh "steep check"
42
+ end
43
+
44
+ desc "Run mutation testing"
45
+ task :mutant do
46
+ ENV["MUTANT"] = "true"
47
+ sh "bundle exec mutant run"
48
+ end
49
+
50
+ desc "Run all quality checks"
51
+ task quality: %i[rubocop steep yardstick]
52
+
53
+ desc "Run all checks (tests, quality, mutation)"
54
+ 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/equalizer.rb CHANGED
@@ -1,121 +1,182 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- # Define equality, equivalence and inspection methods
4
- class Equalizer < Module
5
- # Initialize an Equalizer with the given keys
6
- #
7
- # Will use the keys with which it is initialized to define #cmp?,
8
- # #hash, and #inspect
9
- #
10
- # @param [Array<Symbol>] keys
11
- #
12
- # @return [undefined]
13
- #
14
- # @api private
15
- def initialize(*keys)
16
- @keys = keys
17
- define_methods
18
- freeze
19
- end
20
-
21
- private
3
+ # Equalizer 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 Equalizer.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 Equalizer
31
+ # The current version of the Equalizer gem
32
+ VERSION = "1.0.0"
22
33
 
23
- # Hook called when module is included
34
+ # Creates a module providing equality methods based on the given attributes
24
35
  #
25
- # @param [Module] descendant
26
- # the module or class including Equalizer
36
+ # @example Basic usage
37
+ # class Point
38
+ # include Equalizer.new(:x, :y)
39
+ # attr_reader :x, :y
40
+ # end
27
41
  #
28
- # @return [self]
42
+ # @param keys [Array<Symbol>] attribute names to use for equality
43
+ # @param inspect [Boolean] whether to override #inspect and #pretty_print
44
+ # @return [Module] a module to include in your class
45
+ # @raise [ArgumentError] if keys is empty or contains non-Symbols
29
46
  #
30
- # @api private
31
- def included(descendant)
32
- super
33
- descendant.module_eval { include Methods }
47
+ # @api public
48
+ def self.new(*keys, inspect: true)
49
+ validate_keys!(keys)
50
+ build_module(keys.freeze, inspect:)
34
51
  end
35
52
 
36
- # Define the equalizer methods based on #keys
53
+ # Validates that keys are non-empty and all Symbols
37
54
  #
38
- # @return [undefined]
55
+ # @param keys [Array<Object>] the keys to validate
56
+ # @return [void]
57
+ # @raise [ArgumentError] if keys is empty or contains non-Symbols
39
58
  #
40
59
  # @api private
41
- def define_methods
42
- define_cmp_method
43
- define_hash_method
44
- define_inspect_method
60
+ def self.validate_keys!(keys)
61
+ raise ArgumentError, "at least one attribute is required" if keys.empty?
62
+
63
+ invalid_types = keys.grep_v(Symbol).map(&:class).uniq
64
+ return if invalid_types.empty?
65
+
66
+ raise ArgumentError,
67
+ "attribute must be a Symbol, got #{invalid_types.join(", ")}"
45
68
  end
69
+ private_class_method :validate_keys!
46
70
 
47
- # Define an #cmp? method based on the instance's values identified by #keys
48
- #
49
- # @return [undefined]
71
+ # Instance methods mixed into classes that include an Equalizer module
50
72
  #
51
73
  # @api private
52
- def define_cmp_method
53
- keys = @keys
54
- define_method(:cmp?) do |comparator, other|
55
- keys.all? do |key|
56
- __send__(key).public_send(comparator, other.__send__(key))
57
- end
74
+ module InstanceMethods
75
+ # Equality comparison allowing subclasses
76
+ #
77
+ # @param other [Object] object to compare
78
+ # @return [Boolean] true if other is_a? same class with equal attributes
79
+ def ==(other)
80
+ other.is_a?(self.class) &&
81
+ cmp?(:==, other)
58
82
  end
59
- private :cmp?
60
- end
61
83
 
62
- # Define a #hash method based on the instance's values identified by #keys
63
- #
64
- # @return [undefined]
65
- #
66
- # @api private
67
- def define_hash_method
68
- keys = @keys
69
- define_method(:hash) do | |
70
- keys.map(&method(:send)).push(self.class).hash
84
+ # Strict equality requiring exact class match
85
+ #
86
+ # @param other [Object] object to compare
87
+ # @return [Boolean] true if other is exact same class with eql? attributes
88
+ def eql?(other)
89
+ other.instance_of?(self.class) &&
90
+ cmp?(:eql?, other)
71
91
  end
72
- end
73
92
 
74
- # Define an inspect method that reports the values of the instance's keys
75
- #
76
- # @return [undefined]
77
- #
78
- # @api private
79
- def define_inspect_method
80
- keys = @keys
81
- define_method(:inspect) do | |
82
- klass = self.class
83
- name = klass.name || klass.inspect
84
- "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
93
+ # Hash code based on class and attribute values
94
+ #
95
+ # @return [Integer] hash code
96
+ def hash
97
+ [self.class, *deconstruct].hash
85
98
  end
86
- end
87
99
 
88
- # The comparison methods
89
- module Methods
90
- # Compare the object with other object for equality
100
+ # Array deconstruction for pattern matching
91
101
  #
92
- # @example
93
- # object.eql?(other) # => true or false
102
+ # @return [Array] attribute values in order
103
+ def deconstruct
104
+ equalizer_keys.map { |key| public_send(key) }
105
+ end
106
+
107
+ # Hash deconstruction for pattern matching
94
108
  #
95
- # @param [Object] other
96
- # the other object to compare with
109
+ # @param requested [Array<Symbol>, nil] keys to include, or nil for all
110
+ # @return [Hash{Symbol => Object}] requested attribute key-value pairs
111
+ def deconstruct_keys(requested)
112
+ subset = requested.nil? ? equalizer_keys : equalizer_keys & requested
113
+ subset.to_h { |key| [key, public_send(key)] }
114
+ end
115
+
116
+ private
117
+
118
+ # Compare all attributes using the given comparator
97
119
  #
98
- # @return [Boolean]
120
+ # @param comparator [Symbol] method to use for comparison
121
+ # @param other [Object] object to compare against
122
+ # @return [Boolean] true if all attributes match
99
123
  #
100
- # @api public
101
- def eql?(other)
102
- instance_of?(other.class) && cmp?(__method__, other)
124
+ # @api private
125
+ def cmp?(comparator, other)
126
+ equalizer_keys.all? do |key|
127
+ public_send(key)
128
+ .public_send(comparator, other.public_send(key))
129
+ end
103
130
  end
131
+ end
104
132
 
105
- # Compare the object with other object for equivalency
106
- #
107
- # @example
108
- # object == other # => true or false
133
+ # Instance methods for inspect and pretty print output
134
+ #
135
+ # @api private
136
+ module InspectMethods
137
+ # String representation showing only equalizer attributes
109
138
  #
110
- # @param [Object] other
111
- # the other object to compare with
139
+ # @return [String] inspect output
140
+ def inspect
141
+ attrs = equalizer_keys
142
+ .map { |key| "@#{key}=#{public_send(key).inspect}" }
143
+ .join(", ")
144
+ Object.instance_method(:to_s).bind_call(self).sub(/>\z/, " #{attrs}>")
145
+ end
146
+
147
+ # Pretty print output using PP's object formatting
112
148
  #
113
- # @return [Boolean]
149
+ # @param q [PP] pretty printer
150
+ # @return [void]
151
+ def pretty_print(q)
152
+ q.pp_object(self)
153
+ end
154
+
155
+ # Instance variables to display in pretty print output
114
156
  #
115
- # @api public
116
- def ==(other)
117
- other = coerce(other).first if respond_to?(:coerce, true)
118
- other.kind_of?(self.class) && cmp?(__method__, other)
157
+ # @return [Array<Symbol>] instance variable names
158
+ def pretty_print_instance_variables
159
+ equalizer_keys.map { |key| :"@#{key}" }
160
+ end
161
+ end
162
+
163
+ # Builds the module with equality methods for the given keys
164
+ #
165
+ # @param keys [Array<Symbol>] attribute names (frozen)
166
+ # @param inspect [Boolean] whether to include inspect methods
167
+ # @return [Module] the configured module
168
+ #
169
+ # @api private
170
+ def self.build_module(keys, inspect:)
171
+ Module.new do
172
+ include InstanceMethods
173
+ include InspectMethods if inspect
174
+
175
+ set_temporary_name("Equalizer(#{keys.join(", ")})")
176
+
177
+ define_method(:equalizer_keys) { keys }
178
+ private :equalizer_keys
119
179
  end
120
- end # module Methods
121
- end # class Equalizer
180
+ end
181
+ private_class_method :build_module
182
+ end
data/sig/equalizer.rbs ADDED
@@ -0,0 +1,64 @@
1
+ # Type signatures for the Equalizer gem
2
+ #
3
+ # Equalizer provides equality, equivalence, hashing, and inspection methods
4
+ # for Ruby objects based on specified attributes.
5
+
6
+ module Equalizer
7
+ # The current version of the Equalizer 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, ?inspect: bool) -> 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, inspect: bool) -> Module
21
+
22
+ # Instance methods mixed into classes that include an Equalizer module.
23
+ # Assumes equalizer_keys is provided by the dynamic module.
24
+ module InstanceMethods : _Equalizer
25
+ private
26
+
27
+ # Compare all attributes using the given comparator.
28
+ def cmp?: (Symbol comparator, untyped other) -> bool
29
+ end
30
+
31
+ # Inspect and pretty print methods, optionally included.
32
+ module InspectMethods : _Equalizer
33
+ # Custom inspect output showing equalizer attributes.
34
+ def inspect: () -> String
35
+
36
+ # Pretty print output using PP's object formatting.
37
+ def pretty_print: (untyped q) -> void
38
+
39
+ # Returns instance variable names for PP (pretty print).
40
+ def pretty_print_instance_variables: () -> Array[Symbol]
41
+ end
42
+ end
43
+
44
+ # Interface for classes that include an Equalizer module
45
+ interface _Equalizer
46
+ # Returns the attribute keys used for equality comparison (private).
47
+ def equalizer_keys: () -> Array[Symbol]
48
+
49
+ # Equivalence check - allows subclass instances.
50
+ def ==: (untyped other) -> bool
51
+
52
+ # Strict equality check - requires exact same class.
53
+ def eql?: (untyped other) -> bool
54
+
55
+ # Generate a hash code based on class and attribute values.
56
+ def hash: () -> Integer
57
+
58
+ # Array deconstruction for pattern matching.
59
+ def deconstruct: () -> Array[untyped]
60
+
61
+ # Hash deconstruction for pattern matching.
62
+ def deconstruct_keys: (Array[Symbol]? requested) -> Hash[Symbol, untyped]
63
+ end
64
+
metadata CHANGED
@@ -1,80 +1,54 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: equalizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Kubb
8
8
  - Markus Schirp
9
- autorequire:
9
+ - Piotr Solnica
10
+ - Erik Berlin
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2015-03-23 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: bundler
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: '1.3'
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 1.3.5
24
- type: :development
25
- prerelease: false
26
- version_requirements: !ruby/object:Gem::Requirement
27
- requirements:
28
- - - "~>"
29
- - !ruby/object:Gem::Version
30
- version: '1.3'
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.3.5
34
- description: Module to define equality, equivalence and inspection methods
13
+ date: 1980-01-02 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: |
16
+ Equalizer provides a simple way to define equality
17
+ (==), equivalence (eql?), and hashing (hash) methods
18
+ for Ruby objects based on specified attributes. Includes
19
+ pattern matching support and clean inspect output.
35
20
  email:
36
21
  - dan.kubb@gmail.com
37
22
  - mbj@schirp-dso.com
23
+ - piotr.solnica@gmail.com
24
+ - sferik@gmail.com
38
25
  executables: []
39
26
  extensions: []
40
- extra_rdoc_files:
41
- - LICENSE
42
- - README.md
43
- - CONTRIBUTING.md
27
+ extra_rdoc_files: []
44
28
  files:
45
- - ".gitignore"
46
- - ".rspec"
29
+ - ".mutant.yml"
47
30
  - ".rubocop.yml"
48
- - ".ruby-gemset"
49
- - ".ruby-version"
50
- - ".travis.yml"
31
+ - ".yardopts"
51
32
  - ".yardstick.yml"
33
+ - CHANGELOG.md
52
34
  - CONTRIBUTING.md
53
- - Gemfile
54
35
  - LICENSE
55
36
  - README.md
56
37
  - Rakefile
57
- - config/devtools.yml
58
- - config/flay.yml
59
- - config/flog.yml
60
- - config/mutant.yml
61
- - config/reek.yml
62
- - config/rubocop.yml
63
- - config/yardstick.yml
64
- - equalizer.gemspec
38
+ - Steepfile
65
39
  - lib/equalizer.rb
66
- - lib/equalizer/version.rb
67
- - spec/spec_helper.rb
68
- - spec/support/config_alias.rb
69
- - spec/unit/equalizer/included_spec.rb
70
- - spec/unit/equalizer/methods/eql_predicate_spec.rb
71
- - spec/unit/equalizer/methods/equality_operator_spec.rb
72
- - spec/unit/equalizer/universal_spec.rb
40
+ - sig/equalizer.rbs
73
41
  homepage: https://github.com/dkubb/equalizer
74
42
  licenses:
75
43
  - MIT
76
- metadata: {}
77
- post_install_message:
44
+ metadata:
45
+ allowed_push_host: https://rubygems.org
46
+ bug_tracker_uri: https://github.com/dkubb/equalizer/issues
47
+ changelog_uri: https://github.com/dkubb/equalizer/blob/master/CHANGELOG.md
48
+ documentation_uri: https://rubydoc.info/gems/equalizer/
49
+ homepage_uri: https://github.com/dkubb/equalizer
50
+ rubygems_mfa_required: 'true'
51
+ source_code_uri: https://github.com/dkubb/equalizer
78
52
  rdoc_options: []
79
53
  require_paths:
80
54
  - lib
@@ -82,21 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
56
  requirements:
83
57
  - - ">="
84
58
  - !ruby/object:Gem::Version
85
- version: 1.8.7
59
+ version: '3.3'
86
60
  required_rubygems_version: !ruby/object:Gem::Requirement
87
61
  requirements:
88
62
  - - ">="
89
63
  - !ruby/object:Gem::Version
90
64
  version: '0'
91
65
  requirements: []
92
- rubyforge_project:
93
- rubygems_version: 2.4.5
94
- signing_key:
66
+ rubygems_version: 4.0.8
95
67
  specification_version: 4
96
- summary: Module to define equality, equivalence and inspection methods
97
- test_files:
98
- - spec/unit/equalizer/included_spec.rb
99
- - spec/unit/equalizer/methods/eql_predicate_spec.rb
100
- - spec/unit/equalizer/methods/equality_operator_spec.rb
101
- - spec/unit/equalizer/universal_spec.rb
102
- has_rdoc:
68
+ summary: Define equality, equivalence, and hashing methods for Ruby objects
69
+ test_files: []
data/.gitignore DELETED
@@ -1,39 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## Rubinius
17
- *.rbc
18
- .rbx
19
-
20
- ## PROJECT::GENERAL
21
- *.gem
22
- coverage
23
- profiling
24
- turbulence
25
- rdoc
26
- pkg
27
- tmp
28
- doc
29
- log
30
- .yardoc
31
- measurements
32
-
33
- ## BUNDLER
34
- .bundle
35
- Gemfile.lock
36
- bin
37
- bundle
38
-
39
- ## PROJECT::SPECIFIC
data/.rspec DELETED
@@ -1,6 +0,0 @@
1
- --backtrace
2
- --color
3
- --format progress
4
- --order random
5
- --profile
6
- --warnings
data/.ruby-gemset DELETED
@@ -1 +0,0 @@
1
- equalizer
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- ruby-2.2.1
data/.travis.yml DELETED
@@ -1,21 +0,0 @@
1
- language: ruby
2
- bundler_args: --without yard guard benchmarks
3
- script: "bundle exec rake ci"
4
- cache: bundler
5
- sudo: false
6
- rvm:
7
- - 2.0
8
- - 2.1
9
- - 2.2
10
- - ruby-head
11
- - rbx-2
12
- matrix:
13
- include:
14
- - rvm: jruby
15
- env: JRUBY_OPTS="$JRUBY_OPTS --debug --2.0" # for simplecov
16
- - rvm: jruby-head
17
- env: JRUBY_OPTS="$JRUBY_OPTS --debug --2.0" # for simplecov
18
- allow_failures:
19
- - rvm: ruby-head
20
- - rvm: jruby-head
21
- fast_finish: true
data/Gemfile DELETED
@@ -1,9 +0,0 @@
1
- # encoding: utf-8
2
-
3
- source 'https://rubygems.org'
4
-
5
- group :development, :test do
6
- gem 'devtools', '= 0.0.2', git: 'https://github.com/mbj/devtools.git'
7
- end
8
-
9
- gemspec
data/config/devtools.yml DELETED
@@ -1,2 +0,0 @@
1
- ---
2
- unit_test_timeout: 1
data/config/flay.yml DELETED
@@ -1,4 +0,0 @@
1
- ---
2
- threshold: 0
3
- total_score: 0
4
- lib_dirs: []
data/config/flog.yml DELETED
@@ -1,3 +0,0 @@
1
- ---
2
- threshold: 15.6
3
- lib_dirs: ['lib']
data/config/mutant.yml DELETED
@@ -1,2 +0,0 @@
1
- name: equalizer
2
- namespace: Equalizer