kind 3.1.0 → 5.2.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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -0
  3. data/.travis.sh +67 -12
  4. data/.travis.yml +7 -5
  5. data/CHANGELOG.md +1647 -0
  6. data/Gemfile +22 -7
  7. data/README.md +920 -486
  8. data/kind.gemspec +1 -1
  9. data/lib/kind.rb +2 -314
  10. data/lib/kind/__lib__/attributes.rb +66 -0
  11. data/lib/kind/__lib__/kind.rb +71 -0
  12. data/lib/kind/__lib__/undefined.rb +14 -0
  13. data/lib/kind/action.rb +92 -0
  14. data/lib/kind/active_model/validation.rb +3 -4
  15. data/lib/kind/basic.rb +73 -0
  16. data/lib/kind/basic/error.rb +29 -0
  17. data/lib/kind/{undefined.rb → basic/undefined.rb} +8 -1
  18. data/lib/kind/dig.rb +31 -11
  19. data/lib/kind/either.rb +30 -0
  20. data/lib/kind/either/left.rb +29 -0
  21. data/lib/kind/either/methods.rb +17 -0
  22. data/lib/kind/either/monad.rb +65 -0
  23. data/lib/kind/either/monad/wrapper.rb +19 -0
  24. data/lib/kind/either/right.rb +38 -0
  25. data/lib/kind/empty.rb +4 -10
  26. data/lib/kind/empty/constant.rb +7 -0
  27. data/lib/kind/enum.rb +63 -0
  28. data/lib/kind/enum/item.rb +40 -0
  29. data/lib/kind/enum/methods.rb +72 -0
  30. data/lib/kind/function.rb +47 -0
  31. data/lib/kind/functional.rb +89 -0
  32. data/lib/kind/functional/action.rb +89 -0
  33. data/lib/kind/immutable_attributes.rb +34 -0
  34. data/lib/kind/immutable_attributes/initializer.rb +70 -0
  35. data/lib/kind/immutable_attributes/reader.rb +38 -0
  36. data/lib/kind/maybe.rb +35 -159
  37. data/lib/kind/maybe/methods.rb +21 -0
  38. data/lib/kind/maybe/monad.rb +82 -0
  39. data/lib/kind/maybe/monad/wrapper.rb +19 -0
  40. data/lib/kind/maybe/none.rb +50 -0
  41. data/lib/kind/maybe/some.rb +132 -0
  42. data/lib/kind/maybe/typed.rb +35 -0
  43. data/lib/kind/maybe/wrapper.rb +37 -0
  44. data/lib/kind/monad.rb +22 -0
  45. data/lib/kind/monads.rb +5 -0
  46. data/lib/kind/objects.rb +17 -0
  47. data/lib/kind/objects/basic_object.rb +45 -0
  48. data/lib/kind/objects/modules.rb +32 -0
  49. data/lib/kind/objects/modules/core/array.rb +19 -0
  50. data/lib/kind/objects/modules/core/class.rb +13 -0
  51. data/lib/kind/objects/modules/core/comparable.rb +13 -0
  52. data/lib/kind/objects/modules/core/enumerable.rb +13 -0
  53. data/lib/kind/objects/modules/core/enumerator.rb +13 -0
  54. data/lib/kind/objects/modules/core/file.rb +13 -0
  55. data/lib/kind/objects/modules/core/float.rb +13 -0
  56. data/lib/kind/objects/modules/core/hash.rb +19 -0
  57. data/lib/kind/objects/modules/core/integer.rb +13 -0
  58. data/lib/kind/objects/modules/core/io.rb +13 -0
  59. data/lib/kind/objects/modules/core/method.rb +13 -0
  60. data/lib/kind/objects/modules/core/module.rb +17 -0
  61. data/lib/kind/objects/modules/core/numeric.rb +13 -0
  62. data/lib/kind/objects/modules/core/proc.rb +13 -0
  63. data/lib/kind/objects/modules/core/queue.rb +14 -0
  64. data/lib/kind/objects/modules/core/range.rb +13 -0
  65. data/lib/kind/objects/modules/core/regexp.rb +13 -0
  66. data/lib/kind/objects/modules/core/string.rb +19 -0
  67. data/lib/kind/objects/modules/core/struct.rb +13 -0
  68. data/lib/kind/objects/modules/core/symbol.rb +13 -0
  69. data/lib/kind/objects/modules/core/time.rb +13 -0
  70. data/lib/kind/objects/modules/custom/boolean.rb +19 -0
  71. data/lib/kind/objects/modules/custom/callable.rb +19 -0
  72. data/lib/kind/objects/modules/custom/lambda.rb +19 -0
  73. data/lib/kind/objects/modules/stdlib/open_struct.rb +15 -0
  74. data/lib/kind/objects/modules/stdlib/set.rb +19 -0
  75. data/lib/kind/objects/nil.rb +17 -0
  76. data/lib/kind/objects/not_nil.rb +13 -0
  77. data/lib/kind/objects/object.rb +56 -0
  78. data/lib/kind/objects/respond_to.rb +30 -0
  79. data/lib/kind/objects/union_type.rb +44 -0
  80. data/lib/kind/presence.rb +35 -0
  81. data/lib/kind/result.rb +31 -0
  82. data/lib/kind/result/abstract.rb +53 -0
  83. data/lib/kind/result/failure.rb +31 -0
  84. data/lib/kind/result/methods.rb +17 -0
  85. data/lib/kind/result/monad.rb +69 -0
  86. data/lib/kind/result/monad/wrapper.rb +19 -0
  87. data/lib/kind/result/success.rb +40 -0
  88. data/lib/kind/try.rb +46 -0
  89. data/lib/kind/validator.rb +112 -1
  90. data/lib/kind/version.rb +1 -1
  91. data/test.sh +4 -4
  92. metadata +81 -13
  93. data/lib/kind/active_model/kind_validator.rb +0 -96
  94. data/lib/kind/checker.rb +0 -15
  95. data/lib/kind/checker/factory.rb +0 -35
  96. data/lib/kind/checker/protocol.rb +0 -73
  97. data/lib/kind/error.rb +0 -19
  98. data/lib/kind/is.rb +0 -19
  99. data/lib/kind/of.rb +0 -11
  100. data/lib/kind/types.rb +0 -115
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class RespondTo
5
+ include Kind::BasicObject
6
+
7
+ def self.[](*args)
8
+ args.each { |arg| KIND.of!(::Symbol, arg) }
9
+
10
+ new(args)
11
+ end
12
+
13
+ private_class_method :new
14
+
15
+ attr_reader :inspect
16
+
17
+ def initialize(method_names)
18
+ @method_names = method_names
19
+ @inspect = "Kind::RespondTo#{@method_names}"
20
+ end
21
+
22
+ def ===(value)
23
+ KIND.interface?(@method_names, value)
24
+ end
25
+
26
+ alias_method :call, :===
27
+
28
+ alias_method :name, :inspect
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class UnionType
5
+ include Kind::BasicObject
6
+
7
+ Interface = Kind::RespondTo[:name, :===]
8
+
9
+ singleton_class.send(:alias_method, :[], :new)
10
+
11
+ attr_reader :inspect
12
+
13
+ def initialize(kind)
14
+ @kinds = Array(kind)
15
+ @inspect = "(#{@kinds.map(&:name).join(' | ')})"
16
+ end
17
+
18
+ def |(kind)
19
+ self.class.new(@kinds + [Interface[kind]])
20
+ end
21
+
22
+ def ===(value)
23
+ @kinds.any? { |kind| kind === value }
24
+ end
25
+
26
+ alias_method :name, :inspect
27
+
28
+ module Buildable
29
+ def |(another_kind)
30
+ __union_type | another_kind
31
+ end
32
+
33
+ private
34
+
35
+ def __union_type
36
+ @__union_type ||= UnionType[self]
37
+ end
38
+ end
39
+
40
+ private_constant :Interface
41
+ end
42
+
43
+ RespondTo.send(:include, UnionType::Buildable)
44
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+
5
+ module Kind
6
+ module Presence
7
+ extend self
8
+
9
+ def call(object)
10
+ return if KIND.null?(object)
11
+
12
+ return object.blank? ? nil : object if object.respond_to?(:blank?)
13
+
14
+ return blank_str?(object) ? nil : object if String === object
15
+
16
+ return object.empty? ? nil : object if object.respond_to?(:empty?)
17
+
18
+ return object if object
19
+ end
20
+
21
+ def to_proc
22
+ -> object { call(object) }
23
+ end
24
+
25
+ private
26
+
27
+ BLANK_RE = /\A[[:space:]]*\z/
28
+
29
+ def blank_str?(object)
30
+ object.empty? || BLANK_RE === object
31
+ end
32
+
33
+ private_constant :BLANK_RE
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+
5
+ module Kind
6
+ module Result
7
+ require 'kind/result/abstract'
8
+ require 'kind/result/monad'
9
+ require 'kind/result/failure'
10
+ require 'kind/result/success'
11
+ require 'kind/result/methods'
12
+
13
+ extend self
14
+
15
+ def new(value)
16
+ Success[value]
17
+ end
18
+
19
+ alias_method :[], :new
20
+
21
+ def self.from
22
+ result = yield
23
+
24
+ Result::Monad === result ? result : Result::Success[result]
25
+ rescue StandardError => e
26
+ Result::Failure[:exception, e]
27
+ end
28
+ end
29
+
30
+ extend Result::Methods
31
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Result::Abstract
5
+ def failure?
6
+ false
7
+ end
8
+
9
+ def failed?
10
+ failure?
11
+ end
12
+
13
+ def success?
14
+ false
15
+ end
16
+
17
+ def succeeded?
18
+ success?
19
+ end
20
+
21
+ def on(&block)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def on_success(types = Undefined, matcher = Undefined)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def on_failure(types = Undefined, matcher = Undefined)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def result?(types, matcher)
34
+ undef_t = Undefined == (t = types)
35
+ undef_m = Undefined == (m = matcher)
36
+
37
+ return true if undef_t && undef_m
38
+
39
+ if !undef_t && undef_m && !(Array === types || Symbol === types)
40
+ m, t = types, matcher
41
+
42
+ undef_m, undef_t = false, true
43
+ end
44
+
45
+ is_type = undef_t || (::Array === t ? t.empty? || t.include?(type) : t == type)
46
+ is_type && (undef_m || m === value)
47
+ end
48
+
49
+ def to_ary
50
+ [type, value]
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Result::Failure < Result::Monad
5
+ DEFAULT_TYPE = :error
6
+
7
+ def failure?
8
+ true
9
+ end
10
+
11
+ def value_or(default = UNDEFINED, &block)
12
+ Error.invalid_default_arg! if UNDEFINED == default && !block
13
+
14
+ UNDEFINED != default ? default : block.call
15
+ end
16
+
17
+ def map(&_)
18
+ self
19
+ end
20
+
21
+ alias_method :map!, :map
22
+ alias_method :then, :map
23
+ alias_method :then!, :map
24
+ alias_method :and_then, :map
25
+ alias_method :and_then!, :map
26
+
27
+ def inspect
28
+ '#<%s type=%p value=%p>' % ['Kind::Failure', type, value]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Result::Methods
5
+ def Failure(arg1 = UNDEFINED, arg2 = UNDEFINED)
6
+ Result::Failure[arg1, arg2]
7
+ end
8
+
9
+ def Success(arg1 = UNDEFINED, arg2 = UNDEFINED)
10
+ Result::Success[arg1, arg2]
11
+ end
12
+
13
+ def self.included(base)
14
+ base.send(:private, :Success, :Failure)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Result::Monad
5
+ include Result::Abstract
6
+
7
+ require 'kind/result/monad/wrapper'
8
+
9
+ attr_reader :type, :value
10
+
11
+ def self.[](arg1 = UNDEFINED, arg2 = UNDEFINED, value_must_be_a: nil) # :nodoc:
12
+ type = UNDEFINED == arg2 ? self::DEFAULT_TYPE : KIND.of!(::Symbol, arg1)
13
+
14
+ Error.wrong_number_of_args!(given: 0, expected: '1 or 2') if UNDEFINED == arg1
15
+
16
+ value = UNDEFINED == arg2 ? arg1 : arg2
17
+
18
+ new(type, (value_must_be_a ? KIND.of!(value_must_be_a, value) : value))
19
+ end
20
+
21
+ private_class_method :new
22
+
23
+ def initialize(type, value)
24
+ @type = type
25
+ @value = value
26
+ end
27
+
28
+ def value_or(_method_name = UNDEFINED, &block)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def map(&_)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ alias_method :map!, :map
37
+ alias_method :then, :map
38
+ alias_method :then!, :map
39
+ alias_method :and_then, :map
40
+ alias_method :and_then!, :map
41
+
42
+ def on
43
+ monad = Wrapper.new(self)
44
+
45
+ yield(monad)
46
+
47
+ monad.output
48
+ end
49
+
50
+ def on_success(types = Undefined, matcher = Undefined)
51
+ yield(value) if success? && result?(types, matcher)
52
+
53
+ self
54
+ end
55
+
56
+ def on_failure(types = Undefined, matcher = Undefined)
57
+ yield(value) if failure? && result?(types, matcher)
58
+
59
+ self
60
+ end
61
+
62
+ def ===(m)
63
+ return false unless Result::Abstract === m
64
+ return false unless (self.success? && m.success?) || (self.failure? && m.failure?)
65
+
66
+ self.type == m.type && self.value === m.value
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ require 'kind/monad'
5
+
6
+ class Result::Monad::Wrapper < Kind::Monad::Wrapper
7
+ def failure(types = Undefined, matcher = Undefined)
8
+ return if @monad.success? || output?
9
+
10
+ @output = yield(@monad.value) if @monad.result?(types, matcher)
11
+ end
12
+
13
+ def success(types = Undefined, matcher = Undefined)
14
+ return if @monad.failure? || output?
15
+
16
+ @output = yield(@monad.value) if @monad.result?(types, matcher)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Result::Success < Result::Monad
5
+ DEFAULT_TYPE = :ok
6
+
7
+ def success?
8
+ true
9
+ end
10
+
11
+ def value_or(_default = UNDEFINED, &block)
12
+ @value
13
+ end
14
+
15
+ def map(&fn)
16
+ map!(&fn)
17
+ rescue Kind::Monad::Error => e
18
+ raise e
19
+ rescue StandardError => e
20
+ Result::Failure[:exception, e]
21
+ end
22
+
23
+ def map!(&fn)
24
+ monad = fn.call(@value)
25
+
26
+ return monad if Result::Monad === monad
27
+
28
+ raise Kind::Monad::Error.new('Kind::Success | Kind::Failure', monad)
29
+ end
30
+
31
+ alias_method :then, :map
32
+ alias_method :then!, :map!
33
+ alias_method :and_then, :map
34
+ alias_method :and_then!, :map!
35
+
36
+ def inspect
37
+ '#<%s type=%p value=%p>' % ['Kind::Success', type, value]
38
+ end
39
+ end
40
+ end
data/lib/kind/try.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+ require 'kind/empty'
5
+ require 'kind/presence'
6
+
7
+ module Kind
8
+ module Try
9
+ extend self
10
+
11
+ def call!(object, method_name, args = Empty::ARRAY) # :nodoc
12
+ return if KIND.null?(object)
13
+
14
+ resolve(object, method_name, args)
15
+ end
16
+
17
+ def call(object, *input)
18
+ args = input.size == 1 && input[0].kind_of?(::Array) ? input[0] : input
19
+
20
+ result = call!(object, args.shift, args)
21
+
22
+ return result unless block_given?
23
+
24
+ yield(result) unless KIND.null?(result)
25
+ end
26
+
27
+ def presence(*args, &block)
28
+ Presence.(call(*args, &block))
29
+ end
30
+
31
+ def self.[](*args)
32
+ method_name = args.shift
33
+
34
+ ->(object) { call!(object, method_name, args) }
35
+ end
36
+
37
+ private
38
+
39
+ def resolve(object, method_name, args)
40
+ return unless object.respond_to?(method_name)
41
+ return object.public_send(method_name) if args.empty?
42
+
43
+ object.public_send(method_name, *args)
44
+ end
45
+ end
46
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kind/basic'
4
+
3
5
  module Kind
4
6
  module Validator
5
- DEFAULT_STRATEGIES = Set.new(%w[instance_of kind_of]).freeze
7
+ DEFAULT_STRATEGIES = ::Set.new(%w[instance_of kind_of]).freeze
6
8
 
7
9
  class InvalidDefinition < ArgumentError
8
10
  OPTIONS = 'Options to define one: :of, :is, :respond_to, :instance_of, :array_of or :array_with'.freeze
@@ -38,3 +40,112 @@ module Kind
38
40
  end
39
41
  end
40
42
  end
43
+
44
+ require 'active_model'
45
+ require 'active_model/validations'
46
+
47
+ class KindValidator < ActiveModel::EachValidator
48
+ def validate_each(record, attribute, value)
49
+ return if options[:allow_nil] && value.nil?
50
+
51
+ return unless error = call_validation_for(attribute, value)
52
+
53
+ raise Kind::Error.new("#{attribute} #{error}") if options[:strict]
54
+
55
+ record.errors.add(attribute, error)
56
+ end
57
+
58
+ private
59
+
60
+ def call_validation_for(attribute, value)
61
+ expected = options[:with] || options[:in]
62
+
63
+ return validate_with_default_strategy(expected, value) if expected
64
+
65
+ return kind_of(expected, value) if expected = options[:of]
66
+ return kind_is(expected, value) if expected = options[:is]
67
+ return respond_to(expected, value) if expected = options[:respond_to]
68
+ return instance_of(expected, value) if expected = options[:instance_of]
69
+ return array_with(expected, value) if expected = options[:array_with]
70
+ return array_of(expected, value) if expected = options[:array_of]
71
+
72
+ raise Kind::Validator::InvalidDefinition.new(attribute)
73
+ end
74
+
75
+ def validate_with_default_strategy(expected, value)
76
+ send(Kind::Validator.default_strategy, expected, value)
77
+ end
78
+
79
+ def kind_of(expected, value)
80
+ if ::Array === expected
81
+ return if expected.any? { |type| type === value }
82
+
83
+ "must be a kind of #{expected.map { |type| type.name }.join(', ')}"
84
+ else
85
+ return if expected === value
86
+
87
+ expected.respond_to?(:name) ? "must be a kind of #{expected.name}" : 'invalid kind'
88
+ end
89
+ end
90
+
91
+ CLASS_OR_MODULE = 'Class/Module'.freeze
92
+
93
+ def kind_is(expected, value)
94
+ return kind_is_not(expected, value) unless expected.kind_of?(::Array)
95
+
96
+ result = expected.map { |kind| kind_is_not(kind, value) }.tap(&:compact!)
97
+
98
+ result.empty? || result.size < expected.size ? nil : result.join(', ')
99
+ end
100
+
101
+ def kind_is_not(expected, value)
102
+ case expected
103
+ when ::Class
104
+ return if expected == Kind.of!(::Class, value) || value < expected
105
+
106
+ "must be the class or a subclass of `#{expected.name}`"
107
+ when ::Module
108
+ return if value.kind_of?(::Class) && value <= expected
109
+ return if expected == Kind.of_module_or_class(value) || value.kind_of?(expected)
110
+
111
+ "must include the `#{expected.name}` module"
112
+ else
113
+ raise Kind::Error.new(CLASS_OR_MODULE, expected)
114
+ end
115
+ end
116
+
117
+ def respond_to(expected, value)
118
+ method_names = Array(expected)
119
+
120
+ expected_methods = method_names.select { |method_name| !value.respond_to?(method_name) }
121
+ expected_methods.map! { |method_name| "`#{method_name}`" }
122
+
123
+ return if expected_methods.empty?
124
+
125
+ methods = expected_methods.size == 1 ? 'method' : 'methods'
126
+
127
+ "must respond to the #{methods}: #{expected_methods.join(', ')}"
128
+ end
129
+
130
+ def instance_of(expected, value)
131
+ types = Array(expected)
132
+
133
+ return if types.any? { |type| value.instance_of?(type) }
134
+
135
+ "must be an instance of #{types.map { |klass| klass.name }.join(', ')}"
136
+ end
137
+
138
+ def array_with(expected, value)
139
+ return if value.kind_of?(::Array) && !value.empty? && (value - Kind.of!(::Array, expected)).empty?
140
+
141
+ "must be an array with #{expected.join(', ')}"
142
+ end
143
+
144
+ def array_of(expected, value)
145
+ types = Array(expected)
146
+
147
+ return if value.kind_of?(::Array) && !value.empty? && value.all? { |val| types.any? { |type| type === val } }
148
+
149
+ "must be an array of #{types.map { |klass| klass.name }.join(', ')}"
150
+ end
151
+ end