comparability 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d7df560ce69d77e22e8c096500a37c2ed83c271c
4
+ data.tar.gz: 959f6c8776ac7c4922246edd4e5d20e68149c4c8
5
+ SHA512:
6
+ metadata.gz: 66d97494c122f12583f01d78c949a063b28a00d922e4a8a5113edc354e84fe0efbfc248eaf55e956dca16f4319e7c3a1fc6e09b9e54130cbfbc1d8e589ea2bd7
7
+ data.tar.gz: b3805cdb0f7d3c6a949187b581508fdbbd5965466d63c138ef7d18f8b69660fe0cf17353c1ce21ef378c53efd9ebcdda5ba38e69db7f4edd411951b74a409986
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ippei Ukai
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,126 @@
1
+ # Comparability
2
+
3
+ Provides Comparator and declarative definition of comparison operator.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'comparability'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install comparability
18
+
19
+ ## Usage
20
+
21
+ ### ComparableBy (Declarative definition of comparison operator)
22
+
23
+ You can declaratively define your class's comparison with ``comparable_by`` method.
24
+ To use, you need to extend your class with ``ComparableBy`` module.
25
+
26
+ require 'comparability'
27
+
28
+ class MyClass < Struct.new(:a, :b, :c, :d)
29
+ extend ComparableBy
30
+
31
+ attr_reader :a, :b, :c, :d, :e
32
+
33
+ comparable_by :a, # order of a
34
+ [:b, reverse: true], # reverse order of b
35
+ [:c, nil: :first], # order of c, ordered first if c is nil
36
+ [:d, reverse: true, nil: :last] # reverse order of d, ordered last if d is nil
37
+ ->(_){ _.e.upcase }, # order of e, case-insensitive
38
+ end
39
+
40
+ Call of ``comparable_by`` defines ``<=>`` instance method, and includes ``Comparable`` module.
41
+ Each parameter to ``comparable_by`` should be either an instance of ``Comparator`` or arguments to ``Comparator.create``
42
+ in order of precedence.
43
+
44
+ ### Comparator
45
+
46
+ Some pre-defined comparisons are accessible with special methods.
47
+ Other variations are created with ``Comparator.create``.
48
+
49
+ #### natural order
50
+
51
+ The most simple ``Comparator`` which just evokes the comparison (spaceship) operator.
52
+
53
+ Comparator.natural_order # essentially compare(a,b) is defined as a <=> b
54
+ Comparator.natural_order(reverse: true) # b <=> a instead
55
+
56
+ #### nil priority
57
+
58
+ This special ``Comparator`` orders only by whether object is nil or not.
59
+
60
+ Comparator.prioritize_nil(:first) # nil is smaller
61
+ Comparator.prioritize_nil(:last) # nil is bigger
62
+
63
+ #### by attribute value
64
+
65
+ If you specify a symbol to ``Comparator.create``,
66
+ the comparator will compare objects by the results of the specified method.
67
+
68
+ Comparator.create(:size) # compares by size (prioritise smaller)
69
+ Comparator.create(:size, reverse: true) # compares by size (prioritise bigger)
70
+
71
+ You can use ``:nil`` option to specify the priority of nil value.
72
+
73
+ Comparator.create(:priority, nil: :last) # compares by priority (prioritise smaller, last if nil)
74
+ Comparator.create(:priority, reverse: true, nil: :last) # compares by priority (prioritise bigger, last if nil)
75
+
76
+ The attribute method can be public or protected, but not private.
77
+
78
+ #### by value extractor
79
+
80
+ If you specify a proc with arity 1 to ``Comparator.create``,
81
+ the comparator will compare objects by the results of the specified proc yielding each object.
82
+
83
+ You can also pass the comparison as block.
84
+
85
+ Comparator.create { |str| str.length } # compares by length (prioritise shorter)
86
+ Comparator.create { |str| -str.length } # compares by length (prioritise longer)
87
+
88
+ #### with proc
89
+
90
+ If you specify a proc with arity 2 to ``Comparator.create``,
91
+ it creates a comparator defined by the specified proc.
92
+
93
+ You can also pass the comparison as block.
94
+
95
+ Comparator.create do |obj1, obj2|
96
+ # ... your comparison here ...
97
+ end
98
+
99
+ #### chaining
100
+
101
+ You can chain comparators with ``Comparator.chain`` or its alias ``Comparator.[]``.
102
+
103
+ Comparator[comparator1, comparator2] # compares with comparator1, then tie-breaks with comparator2
104
+
105
+ #### reversing
106
+
107
+ You can reverse existing comparators with ``Comparator.reverse``.
108
+
109
+ Comparator.reverse(comparator) # reverses the order
110
+
111
+ #### ``Enumerable#sort`` usage
112
+
113
+ ``Comparator#to_proc`` returns a ``Proc`` compatible with ``Enumerable#sort``.
114
+ You can use the ampersand shorthand to convert a comparator and pass as block.
115
+
116
+ comparator = Comparator[Comparator.prioritize_nil(:last),
117
+ Comparator.natural_order(reverse: true)]
118
+ [nil,1,2,3,4].sort(&comparator) # => [4, 3, 2, 1, nil]
119
+
120
+ ## Contributing
121
+
122
+ 1. Fork it ( http://github.com/ippeiukai/comparability/fork )
123
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
124
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
125
+ 4. Push to the branch (`git push origin my-new-feature`)
126
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'comparability/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'comparability'
8
+ spec.version = Comparability::VERSION
9
+ spec.authors = ['Ippei Ukai']
10
+ spec.email = ['ippei@users.sourceforge.net']
11
+ spec.summary = 'Provides Comparator and declarative definition of comparison operator.'
12
+ spec.homepage = 'http://github.com/ippeiukai/comparability'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x00")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.5'
21
+ spec.add_development_dependency 'rake'
22
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'comparability/version'
2
+
3
+ require_relative 'comparability/comparator'
4
+ Comparator = Comparability::Comparator
5
+
6
+ require_relative 'comparability/comparable_by'
7
+ ComparableBy = Comparability::ComparableBy
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+
3
+ module Comparability
4
+ module ComparableBy
5
+
6
+ private
7
+
8
+ # @!visibility public
9
+ # Declares the comparison for this class.
10
+ # This defines <=> instance method and includes Comparable module.
11
+ def comparable_by(*args)
12
+ raise ArgumentError if args.empty?
13
+ comparator = Comparator.chain(*args)
14
+ const_set(:COMPARATOR, comparator)
15
+ define_method(:<=>) { |other| self.class::COMPARATOR.compare(self, other) }
16
+ include Comparable
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Comparability
4
+ class Comparator
5
+ # @!parse extend Comparability::Comparators::FactoryMethods
6
+
7
+ def compare(me, other)
8
+ me <=> other
9
+ end
10
+
11
+ def to_proc
12
+ @_to_proc ||= method(:compare).to_proc
13
+ end
14
+
15
+ end
16
+ end
17
+
18
+ # require files in ./comparators
19
+ File.dirname(File.expand_path(__FILE__)).tap do |this_dirname|
20
+ Dir[File.join(this_dirname, 'comparators', '**', '*.rb')].each do |file|
21
+ name = file.gsub(%r|^#{Regexp.escape(this_dirname)}/(.*)#{Regexp.escape('.rb')}|, '\1')
22
+ require_relative name
23
+ end
24
+ end
25
+
26
+ Comparability::Comparator.extend(Comparability::Comparators::FactoryMethods)
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'value_comparator'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ class AttributeValueComparator < ValueComparator
8
+
9
+ def initialize(attribute)
10
+ attribute = attribute.to_s.freeze unless attribute.is_a?(Symbol)
11
+ @attribute = attribute
12
+ end
13
+
14
+ attr_reader :attribute
15
+
16
+ private
17
+
18
+ def extract_value(me)
19
+ me.send(attribute)
20
+ end
21
+
22
+ def extract_other_value(me, _other_)
23
+ if instance_evaluable_attribute?
24
+ # me can access the protected attribute of the other
25
+ me.instance_eval("_other_.#{attribute}()")
26
+ else
27
+ _other_.public_send(attribute)
28
+ end
29
+ end
30
+
31
+ INSTANCE_EVALUABLE_METHOD_NAME = /\A([_a-zA-Z][_a-zA-Z0-9]*[!?]?)|([!~+-]@)\Z/
32
+ def instance_evaluable_attribute?
33
+ INSTANCE_EVALUABLE_METHOD_NAME =~ attribute
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+
3
+ require 'forwardable'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ class ComparatorChain < Comparator
8
+ extend Forwardable
9
+ include Enumerable
10
+
11
+ def initialize(comparators)
12
+ raise ArgumentError unless comparators.size > 0
13
+ raise ArgumentError unless comparators.all? { |c| c.is_a?(Comparator) }
14
+ @comparators = comparators.freeze
15
+ end
16
+
17
+ attr_reader :comparators
18
+ delegate :each => :comparators
19
+
20
+ def compare(me, other)
21
+ inject(0) do |zero, comparator|
22
+ result = comparator.compare(me, other)
23
+ if result.nil?
24
+ # the comparator failed
25
+ return nil
26
+ elsif result.nonzero?
27
+ # we have the conclusion
28
+ return result
29
+ else
30
+ # a tie
31
+ next 0
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,103 @@
1
+ # coding: utf-8
2
+
3
+
4
+ module Comparability
5
+ module Comparators
6
+ module FactoryMethods
7
+
8
+ def create(comparison = nil, options = {}, &block)
9
+ comparison ||= block
10
+ comparator =
11
+ case comparison
12
+ when Symbol
13
+ create_from_symbol(comparison)
14
+ when Proc
15
+ create_from_proc(comparison)
16
+ when ->(_) { _.respond_to?(:to_proc) }
17
+ create_from_proc(comparison.to_proc)
18
+ else
19
+ raise ArgumentError, 'invalid comparison'
20
+ end
21
+ apply_options(comparator, options)
22
+ end
23
+
24
+ # @param comparisons [Array]
25
+ # each comparison should be either Comparison instance or argument(s) accepted by #create
26
+ def chain(*comparisons)
27
+ comparators = comparisons.map do |comparison_or_comparator|
28
+ case comparison_or_comparator
29
+ when Comparator
30
+ comparison_or_comparator
31
+ else
32
+ create(*comparison_or_comparator)
33
+ end
34
+ end
35
+ ComparatorChain.new(comparators)
36
+ end
37
+ alias_method :[], :chain
38
+
39
+ # @param priority [Symbol] :first or :last
40
+ def prioritize_nil(priority)
41
+ NilComparator.with_nil_priority(priority)
42
+ end
43
+
44
+ # @option options [true] :reverse
45
+ def natural_order(options = {})
46
+ @_natural_order ||= Comparator.new
47
+ if options[:reverse]
48
+ @_reverse_natural_order ||= reverse(@_natural_order)
49
+ else
50
+ @_natural_order
51
+ end
52
+ end
53
+
54
+ # @param comparator [Comparator]
55
+ def reverse(comparator)
56
+ ReverseWrapperComparator.wrap(comparator)
57
+ end
58
+
59
+ private
60
+
61
+ def create_from_symbol(attr)
62
+ AttributeValueComparator.new(attr)
63
+ end
64
+
65
+ def create_from_proc(proc)
66
+ case proc.arity
67
+ when 1
68
+ ValueExtractorComparator.new(proc)
69
+ when 2
70
+ ProcComparator.new(proc)
71
+ else
72
+ raise ArgumentError, 'invalid comparison'
73
+ end
74
+ end
75
+
76
+ def apply_options(comparator, options)
77
+ nil_value_priority = options.fetch(:nil, nil)
78
+ reversing = options.fetch(:reverse, false)
79
+
80
+ if nil_value_priority
81
+ raise ArgumentError unless comparator.respond_to?(:nil_value_priority=)
82
+ if reversing
83
+ # value comparison should be reversed, but not the nil priority
84
+ case nil_value_priority
85
+ when :first
86
+ nil_value_priority = :last
87
+ when :last
88
+ nil_value_priority = :first
89
+ end
90
+ end
91
+ comparator.nil_value_priority = nil_value_priority
92
+ end
93
+
94
+ if reversing
95
+ comparator = reverse(comparator)
96
+ end
97
+
98
+ comparator
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+
3
+ require 'singleton'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ module NilComparator
8
+
9
+ class << self
10
+
11
+ def with_nil_priority(priority)
12
+ raise ArgumentError unless %[first last].include?(priority.to_s)
13
+ public_send("nil_#{priority}")
14
+ end
15
+
16
+ def nil_first
17
+ @_nil_first ||= ReverseWrapperComparator.wrap(NilLastComparator.instance)
18
+ end
19
+
20
+ def nil_last
21
+ NilLastComparator.instance
22
+ end
23
+
24
+ end
25
+
26
+ class NilLastComparator < Comparator
27
+ include Singleton
28
+
29
+ def compare(me, other)
30
+ if me.nil? && other.nil?
31
+ 0
32
+ elsif me.nil?
33
+ 1
34
+ elsif other.nil?
35
+ -1
36
+ else
37
+ 0
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+ module Comparability
4
+ module Comparators
5
+ class ProcComparator < Comparator
6
+
7
+ def initialize(comparison)
8
+ raise ArgumentError unless comparison.is_a?(Proc) && comparison.arity == 2
9
+ self.singleton_class.send(:define_method, :compare, &comparison)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'wrapper_comparator'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ class ReverseWrapperComparator < WrapperComparator
8
+
9
+ def compare(me, other)
10
+ reverse(wrapped_compare(me, other))
11
+ end
12
+
13
+ private
14
+
15
+ def reverse(comparison_result)
16
+ if comparison_result.nil? || comparison_result.zero?
17
+ comparison_result
18
+ else
19
+ -comparison_result
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'nil_comparator'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ class ValueComparator < Comparator
8
+
9
+ def compare(me, other)
10
+ me_value = extract_value(me)
11
+ other_value = extract_other_value(me, other)
12
+
13
+ if nil_value_priority
14
+ nil_result = NilComparator.with_nil_priority(nil_value_priority).compare(me_value, other_value)
15
+ return nil_result if nil_result && nil_result.nonzero?
16
+ end
17
+
18
+ me_value <=> other_value
19
+ end
20
+
21
+ def nil_value_priority=(priority)
22
+ raise ArgumentError unless [nil, :first, :last].include?(priority)
23
+ @nil_value_priority = priority
24
+ end
25
+
26
+ attr_reader :nil_value_priority
27
+
28
+ private
29
+
30
+ def extract_value(me)
31
+ raise 'abstract'
32
+ end
33
+
34
+ def extract_other_value(me, obj)
35
+ extract_value(obj)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'value_comparator'
4
+
5
+ module Comparability
6
+ module Comparators
7
+ class ValueExtractorComparator < ValueComparator
8
+
9
+ def initialize(value_extractor)
10
+ raise ArgumentError unless value_extractor.is_a?(Proc) && value_extractor.arity == 1
11
+ @value_extractor = value_extractor
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :value_extractor
17
+
18
+ def extract_value(obj)
19
+ value_extractor.(obj)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Comparability
4
+ module Comparators
5
+ class WrapperComparator < Comparator
6
+
7
+ class << self
8
+ alias_method :wrap, :new
9
+ end
10
+
11
+ def initialize(comparator)
12
+ raise ArgumentError unless comparator.is_a?(Comparator)
13
+ @wrapped = comparator
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :wrapped
19
+
20
+ def wrapped_compare(me, other)
21
+ wrapped.compare(me, other)
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Comparability
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: comparability
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ippei Ukai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - ippei@users.sourceforge.net
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - comparability.gemspec
54
+ - lib/comparability.rb
55
+ - lib/comparability/comparable_by.rb
56
+ - lib/comparability/comparator.rb
57
+ - lib/comparability/comparators/attribute_value_comparator.rb
58
+ - lib/comparability/comparators/comparator_chain.rb
59
+ - lib/comparability/comparators/factory_methods.rb
60
+ - lib/comparability/comparators/nil_comparator.rb
61
+ - lib/comparability/comparators/proc_comparator.rb
62
+ - lib/comparability/comparators/reverse_wrapper_comparator.rb
63
+ - lib/comparability/comparators/value_comparator.rb
64
+ - lib/comparability/comparators/value_extractor_comparator.rb
65
+ - lib/comparability/comparators/wrapper_comparator.rb
66
+ - lib/comparability/version.rb
67
+ homepage: http://github.com/ippeiukai/comparability
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.2
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Provides Comparator and declarative definition of comparison operator.
91
+ test_files: []