Skip to content

Instantly share code, notes, and snippets.

@estum
Created September 1, 2022 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estum/a5590fec0b21e19769a3a3d0c161d691 to your computer and use it in GitHub Desktop.
Save estum/a5590fec0b21e19769a3a3d0c161d691 to your computer and use it in GitHub Desktop.
Dry::Core::Comparator
# frozen_string_literal: true
require 'dry/core/equalizer'
module Dry
# @api public
def self.Comparator(*keys, **options)
if keys.size == 0 && options.size > 0
keys << options.dup
options.replace(keys[0].extract!(:immutable, :inspect, :compare))
end
mapping = keys.extract_options!
keys.concat mapping.keys if mapping.present?
case keys.size
when 0
raise ArgumentError, 'missing key to compare'
when 1
key = keys[0]
Dry::Core::MonoComparator.new(key, **options, compare: mapping[key])
else
Dry::Core::PolyComparator.new(*keys, **options, compare: mapping)
end
end
module Core
class Comparator < Equalizer
DEFAULT_MAPPING = begin
hsh = Hash.new
hsh.default = :<=>
hsh.freeze
end
module Methods
include Equalizer::Methods
include Comparable
def <=>(other)
other.is_a?(self.class) && cmp(other)
end
end
def self.inherited(subklass)
unless subklass.const_defined?(:Methods)
subklass.const_set(:Methods, const_get(:Methods).dup)
end
super if defined?(super)
end
private
# @api private
def included(descendant)
super
descendant.include self.class.const_get(:Methods)
end
# @see Dry::Core::Equalizer#define_methods
# @param compare [Hash{ key => Symbol }]
# maps comparation methods to keys
# @api private
def define_methods(inspect: false, immutable: false, compare: nil)
super(inspect: inspect, immutable: immutable)
define_cmp_op_method(compare: compare)
return
end
end
private_constant :Comparator
class MonoComparator < Comparator
def define_cmp_op_method(compare: nil)
compare ||= DEFAULT_MAPPING.default
fn = proc(&@keys[0])
define_method(:cmp) do |other|
fn[self].public_send(compare, fn[other])
end
private :cmp
end
end
class PolyComparator < Comparator
def define_cmp_op_method(compare: nil)
compare =
if compare.is_a?(Hash) && compare.size > 0
compare.with_defaults(DEFAULT_MAPPING)
else
DEFAULT_MAPPING
end
keys = @keys
define_method(:cmp) do |other|
keys.inject(0) do |a, key|
break nil if a.nil?
a.nonzero? || __send__(key).public_send(compare[key], other.__send__(key))
end
end
private :cmp
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment