equalizer 0.0.10 → 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,34 +1,54 @@
1
- # encoding: utf-8
2
-
3
- require 'bundler'
4
- Bundler::GemHelper.install_tasks
5
-
6
- require 'rspec/core/rake_task'
7
- RSpec::Core::RakeTask.new(:spec)
8
-
9
- task :test => :spec
10
- task :default => :spec
11
-
12
- begin
13
- require 'rubocop/rake_task'
14
- RuboCop::RakeTask.new do |task|
15
- task.options = %w(--config config/rubocop.yml)
16
- end
17
- rescue LoadError
18
- desc 'Run RuboCop'
19
- task :rubocop do
20
- $stderr.puts 'RuboCop is disabled'
21
- end
1
+ # frozen_string_literal: true
2
+
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]
22
27
  end
23
28
 
24
- require 'yardstick/rake/measurement'
25
- Yardstick::Rake::Measurement.new do |measurement|
26
- measurement.output = 'measurement/report.txt'
29
+ YARD::Rake::YardocTask.new(:yard) do |t|
30
+ t.files = ["lib/**/*.rb"]
31
+ t.options = ["--no-private", "--markup", "markdown"]
27
32
  end
28
33
 
29
- require 'yardstick/rake/verify'
30
- Yardstick::Rake::Verify.new do |verify|
34
+ Yardstick::Rake::Verify.new(:yardstick) do |verify|
31
35
  verify.threshold = 100
36
+ verify.require_exact_threshold = false
32
37
  end
33
38
 
34
- task :ci => [:spec, :rubocop, :verify_measurements]
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,119 +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? { |key| send(key).send(comparator, other.send(key)) }
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)
56
82
  end
57
- private :cmp?
58
- end
59
83
 
60
- # Define a #hash method based on the instance's values identified by #keys
61
- #
62
- # @return [undefined]
63
- #
64
- # @api private
65
- def define_hash_method
66
- keys = @keys
67
- define_method(:hash) do | |
68
- keys.collect(&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)
69
91
  end
70
- end
71
92
 
72
- # Define an inspect method that reports the values of the instance's keys
73
- #
74
- # @return [undefined]
75
- #
76
- # @api private
77
- def define_inspect_method
78
- keys = @keys
79
- define_method(:inspect) do | |
80
- klass = self.class
81
- name = klass.name || klass.inspect
82
- "#<#{name}#{keys.collect { |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
83
98
  end
84
- end
85
99
 
86
- # The comparison methods
87
- module Methods
88
- # Compare the object with other object for equality
100
+ # Array deconstruction for pattern matching
89
101
  #
90
- # @example
91
- # 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
92
108
  #
93
- # @param [Object] other
94
- # 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
95
119
  #
96
- # @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
97
123
  #
98
- # @api public
99
- def eql?(other)
100
- 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
101
130
  end
131
+ end
102
132
 
103
- # Compare the object with other object for equivalency
104
- #
105
- # @example
106
- # 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
107
138
  #
108
- # @param [Object] other
109
- # 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
110
148
  #
111
- # @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
112
156
  #
113
- # @api public
114
- def ==(other)
115
- other = coerce(other).first if respond_to?(:coerce, true)
116
- other.is_a?(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
117
179
  end
118
- end # module Methods
119
- 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,94 +1,69 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: equalizer
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.10
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
5
  platform: ruby
6
- authors:
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
-
13
- date: 2015-03-20 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- type: :development
17
- version_requirements: &id001 !ruby/object:Gem::Requirement
18
- requirements:
19
- - - ~>
20
- - !ruby/object:Gem::Version
21
- version: "1.3"
22
- - - ">="
23
- - !ruby/object:Gem::Version
24
- version: 1.3.5
25
- requirement: *id001
26
- prerelease: false
27
- name: bundler
28
- description: Module to define equality, equivalence and inspection methods
29
- email:
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.
20
+ email:
30
21
  - dan.kubb@gmail.com
31
22
  - mbj@schirp-dso.com
23
+ - piotr.solnica@gmail.com
24
+ - sferik@gmail.com
32
25
  executables: []
33
-
34
26
  extensions: []
35
-
36
- extra_rdoc_files:
37
- - LICENSE
38
- - README.md
39
- - CONTRIBUTING.md
40
- files:
41
- - .gitignore
42
- - .rspec
43
- - .rubocop.yml
44
- - .ruby-gemset
45
- - .travis.yml
46
- - .yardstick.yml
27
+ extra_rdoc_files: []
28
+ files:
29
+ - ".mutant.yml"
30
+ - ".rubocop.yml"
31
+ - ".yardopts"
32
+ - ".yardstick.yml"
33
+ - CHANGELOG.md
47
34
  - CONTRIBUTING.md
48
- - Gemfile
49
35
  - LICENSE
50
36
  - README.md
51
37
  - Rakefile
52
- - config/rubocop.yml
53
- - equalizer.gemspec
38
+ - Steepfile
54
39
  - lib/equalizer.rb
55
- - lib/equalizer/version.rb
56
- - spec/spec_helper.rb
57
- - spec/support/config_alias.rb
58
- - spec/unit/equalizer/included_spec.rb
59
- - spec/unit/equalizer/methods/eql_predicate_spec.rb
60
- - spec/unit/equalizer/methods/equality_operator_spec.rb
61
- - spec/unit/equalizer/universal_spec.rb
40
+ - sig/equalizer.rbs
62
41
  homepage: https://github.com/dkubb/equalizer
63
- licenses:
42
+ licenses:
64
43
  - MIT
65
- metadata: {}
66
-
67
- 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
68
52
  rdoc_options: []
69
-
70
- require_paths:
53
+ require_paths:
71
54
  - lib
72
- required_ruby_version: !ruby/object:Gem::Requirement
73
- requirements:
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
74
57
  - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 1.8.7
77
- required_rubygems_version: !ruby/object:Gem::Requirement
78
- requirements:
58
+ - !ruby/object:Gem::Version
59
+ version: '3.3'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
79
62
  - - ">="
80
- - !ruby/object:Gem::Version
81
- version: "0"
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
82
65
  requirements: []
83
-
84
- rubyforge_project:
85
- rubygems_version: 2.4.6
86
- signing_key:
66
+ rubygems_version: 4.0.8
87
67
  specification_version: 4
88
- summary: Module to define equality, equivalence and inspection methods
89
- test_files:
90
- - spec/unit/equalizer/included_spec.rb
91
- - spec/unit/equalizer/methods/eql_predicate_spec.rb
92
- - spec/unit/equalizer/methods/equality_operator_spec.rb
93
- - spec/unit/equalizer/universal_spec.rb
94
- 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/.travis.yml DELETED
@@ -1,31 +0,0 @@
1
- before_install: gem update bundler
2
- cache: bundler
3
- sudo: false
4
- env:
5
- global:
6
- - JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov
7
- language: ruby
8
- script: "bundle exec rake ci"
9
- rvm:
10
- - 1.8.7
11
- - 1.9.3
12
- - 2.0.0
13
- - 2.1
14
- - 2.2
15
- - jruby-18mode
16
- - jruby-19mode
17
- - jruby-head
18
- - rbx-2
19
- - ree
20
- - ruby-head
21
- matrix:
22
- allow_failures:
23
- - rvm: ruby-head
24
- - rvm: jruby-head
25
- fast_finish: true
26
- notifications:
27
- irc:
28
- channels:
29
- - irc.freenode.org#rom-rb
30
- on_success: never
31
- on_failure: change
data/Gemfile DELETED
@@ -1,19 +0,0 @@
1
- # encoding: utf-8
2
-
3
- source 'https://rubygems.org'
4
-
5
- gem 'rake'
6
-
7
- group :test do
8
- gem 'backports'
9
- gem 'coveralls'
10
- gem 'json', :platforms => [:ruby_19]
11
- gem 'mime-types', '~> 1.25', :platforms => [:jruby, :ruby_18]
12
- gem 'rest-client', '~> 1.6.0', :platforms => [:jruby, :ruby_18]
13
- gem 'rspec', '~> 3.0'
14
- gem 'rubocop', '>= 0.25', :platforms => [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
15
- gem 'simplecov', '>= 0.9'
16
- gem 'yardstick'
17
- end
18
-
19
- gemspec