kind 3.1.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.tool-versions +1 -0
- data/.travis.sh +67 -12
- data/.travis.yml +7 -5
- data/CHANGELOG.md +1647 -0
- data/Gemfile +22 -7
- data/README.md +920 -486
- data/kind.gemspec +1 -1
- data/lib/kind.rb +2 -314
- data/lib/kind/__lib__/attributes.rb +66 -0
- data/lib/kind/__lib__/kind.rb +71 -0
- data/lib/kind/__lib__/undefined.rb +14 -0
- data/lib/kind/action.rb +92 -0
- data/lib/kind/active_model/validation.rb +3 -4
- data/lib/kind/basic.rb +73 -0
- data/lib/kind/basic/error.rb +29 -0
- data/lib/kind/{undefined.rb → basic/undefined.rb} +8 -1
- data/lib/kind/dig.rb +31 -11
- data/lib/kind/either.rb +30 -0
- data/lib/kind/either/left.rb +29 -0
- data/lib/kind/either/methods.rb +17 -0
- data/lib/kind/either/monad.rb +65 -0
- data/lib/kind/either/monad/wrapper.rb +19 -0
- data/lib/kind/either/right.rb +38 -0
- data/lib/kind/empty.rb +4 -10
- data/lib/kind/empty/constant.rb +7 -0
- data/lib/kind/enum.rb +63 -0
- data/lib/kind/enum/item.rb +40 -0
- data/lib/kind/enum/methods.rb +72 -0
- data/lib/kind/function.rb +47 -0
- data/lib/kind/functional.rb +89 -0
- data/lib/kind/functional/action.rb +89 -0
- data/lib/kind/immutable_attributes.rb +34 -0
- data/lib/kind/immutable_attributes/initializer.rb +70 -0
- data/lib/kind/immutable_attributes/reader.rb +38 -0
- data/lib/kind/maybe.rb +35 -159
- data/lib/kind/maybe/methods.rb +21 -0
- data/lib/kind/maybe/monad.rb +82 -0
- data/lib/kind/maybe/monad/wrapper.rb +19 -0
- data/lib/kind/maybe/none.rb +50 -0
- data/lib/kind/maybe/some.rb +132 -0
- data/lib/kind/maybe/typed.rb +35 -0
- data/lib/kind/maybe/wrapper.rb +37 -0
- data/lib/kind/monad.rb +22 -0
- data/lib/kind/monads.rb +5 -0
- data/lib/kind/objects.rb +17 -0
- data/lib/kind/objects/basic_object.rb +45 -0
- data/lib/kind/objects/modules.rb +32 -0
- data/lib/kind/objects/modules/core/array.rb +19 -0
- data/lib/kind/objects/modules/core/class.rb +13 -0
- data/lib/kind/objects/modules/core/comparable.rb +13 -0
- data/lib/kind/objects/modules/core/enumerable.rb +13 -0
- data/lib/kind/objects/modules/core/enumerator.rb +13 -0
- data/lib/kind/objects/modules/core/file.rb +13 -0
- data/lib/kind/objects/modules/core/float.rb +13 -0
- data/lib/kind/objects/modules/core/hash.rb +19 -0
- data/lib/kind/objects/modules/core/integer.rb +13 -0
- data/lib/kind/objects/modules/core/io.rb +13 -0
- data/lib/kind/objects/modules/core/method.rb +13 -0
- data/lib/kind/objects/modules/core/module.rb +17 -0
- data/lib/kind/objects/modules/core/numeric.rb +13 -0
- data/lib/kind/objects/modules/core/proc.rb +13 -0
- data/lib/kind/objects/modules/core/queue.rb +14 -0
- data/lib/kind/objects/modules/core/range.rb +13 -0
- data/lib/kind/objects/modules/core/regexp.rb +13 -0
- data/lib/kind/objects/modules/core/string.rb +19 -0
- data/lib/kind/objects/modules/core/struct.rb +13 -0
- data/lib/kind/objects/modules/core/symbol.rb +13 -0
- data/lib/kind/objects/modules/core/time.rb +13 -0
- data/lib/kind/objects/modules/custom/boolean.rb +19 -0
- data/lib/kind/objects/modules/custom/callable.rb +19 -0
- data/lib/kind/objects/modules/custom/lambda.rb +19 -0
- data/lib/kind/objects/modules/stdlib/open_struct.rb +15 -0
- data/lib/kind/objects/modules/stdlib/set.rb +19 -0
- data/lib/kind/objects/nil.rb +17 -0
- data/lib/kind/objects/not_nil.rb +13 -0
- data/lib/kind/objects/object.rb +56 -0
- data/lib/kind/objects/respond_to.rb +30 -0
- data/lib/kind/objects/union_type.rb +44 -0
- data/lib/kind/presence.rb +35 -0
- data/lib/kind/result.rb +31 -0
- data/lib/kind/result/abstract.rb +53 -0
- data/lib/kind/result/failure.rb +31 -0
- data/lib/kind/result/methods.rb +17 -0
- data/lib/kind/result/monad.rb +69 -0
- data/lib/kind/result/monad/wrapper.rb +19 -0
- data/lib/kind/result/success.rb +40 -0
- data/lib/kind/try.rb +46 -0
- data/lib/kind/validator.rb +112 -1
- data/lib/kind/version.rb +1 -1
- data/test.sh +4 -4
- metadata +81 -13
- data/lib/kind/active_model/kind_validator.rb +0 -96
- data/lib/kind/checker.rb +0 -15
- data/lib/kind/checker/factory.rb +0 -35
- data/lib/kind/checker/protocol.rb +0 -73
- data/lib/kind/error.rb +0 -19
- data/lib/kind/is.rb +0 -19
- data/lib/kind/of.rb +0 -11
- data/lib/kind/types.rb +0 -115
data/lib/kind/action.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/basic'
|
4
|
+
require 'kind/result'
|
5
|
+
require 'kind/immutable_attributes'
|
6
|
+
|
7
|
+
module Kind
|
8
|
+
module Action
|
9
|
+
CALL_TMPL = [
|
10
|
+
'def self.call(arg)',
|
11
|
+
' new(Kind.of!(::Hash, arg)).call',
|
12
|
+
'end',
|
13
|
+
'',
|
14
|
+
'def call',
|
15
|
+
' result = call!',
|
16
|
+
'',
|
17
|
+
' return result if Kind::Result::Monad === result',
|
18
|
+
'',
|
19
|
+
' raise Kind::Error, "#{self.class.name}#call! must return a Success() or Failure()"',
|
20
|
+
'end'
|
21
|
+
].join("\n").freeze
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
include ImmutableAttributes::ClassMethods
|
25
|
+
|
26
|
+
def to_proc
|
27
|
+
@to_proc ||= ->(arg) { call(arg) }
|
28
|
+
end
|
29
|
+
|
30
|
+
ATTRIBUTE_METHODS = [
|
31
|
+
:attributes, :attribute,
|
32
|
+
:attribute?, :attribute!,
|
33
|
+
:with_attribute, :with_attributes,
|
34
|
+
:nil_attributes, :nil_attributes?
|
35
|
+
]
|
36
|
+
|
37
|
+
private_constant :ATTRIBUTE_METHODS
|
38
|
+
|
39
|
+
def kind_action!
|
40
|
+
return self if respond_to?(:call)
|
41
|
+
|
42
|
+
public_methods = self.public_instance_methods - ::Object.new.methods
|
43
|
+
|
44
|
+
remaining_methods = public_methods - (__attributes__.keys + ATTRIBUTE_METHODS)
|
45
|
+
|
46
|
+
unless remaining_methods.include?(:call!)
|
47
|
+
raise Kind::Error.new("expected #{self} to implement `#call!`")
|
48
|
+
end
|
49
|
+
|
50
|
+
if remaining_methods.size > 1
|
51
|
+
raise Kind::Error.new("#{self} can only have `#call!` as its public method")
|
52
|
+
end
|
53
|
+
|
54
|
+
call_parameters = public_instance_method(:call!).parameters
|
55
|
+
|
56
|
+
unless call_parameters.empty?
|
57
|
+
raise ArgumentError, "#{self.name}#call! must receive no arguments"
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.inherited(_)
|
61
|
+
raise RuntimeError, "#{self.name} is a Kind::Action and it can't be inherited"
|
62
|
+
end
|
63
|
+
|
64
|
+
self.send(:private_class_method, :new)
|
65
|
+
|
66
|
+
self.class_eval(CALL_TMPL)
|
67
|
+
|
68
|
+
self.send(:alias_method, :[], :call)
|
69
|
+
self.send(:alias_method, :===, :call)
|
70
|
+
self.send(:alias_method, :yield, :call)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.included(base)
|
75
|
+
KIND.of!(::Class, base).extend(ClassMethods)
|
76
|
+
|
77
|
+
base.send(:include, ImmutableAttributes::Reader)
|
78
|
+
end
|
79
|
+
|
80
|
+
include ImmutableAttributes::Initializer
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def Failure(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
85
|
+
Result::Failure[arg1, arg2, value_must_be_a: ::Hash]
|
86
|
+
end
|
87
|
+
|
88
|
+
def Success(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
89
|
+
Result::Success[arg1, arg2, value_must_be_a: ::Hash]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
warn '[DEPRECATION] "kind/active_model/validation" is deprecated; use "kind/validator" instead. ' \
|
4
|
+
'It will be removed on next major release.'
|
5
|
+
|
4
6
|
require 'kind/validator'
|
5
|
-
require 'active_model'
|
6
|
-
require 'active_model/validations'
|
7
|
-
require_relative 'kind_validator'
|
data/lib/kind/basic.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/version'
|
4
|
+
|
5
|
+
require 'kind/__lib__/kind'
|
6
|
+
require 'kind/__lib__/undefined'
|
7
|
+
|
8
|
+
require 'kind/basic/undefined'
|
9
|
+
require 'kind/basic/error'
|
10
|
+
|
11
|
+
module Kind
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def is?(kind, arg)
|
15
|
+
KIND.is?(kind, arg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def is(*args)
|
19
|
+
warn '[DEPRECATION] Kind.is will behave like Kind.is! in the next major release; ' \
|
20
|
+
'use Kind.is? instead.'
|
21
|
+
|
22
|
+
is?(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def is!(kind, arg, label: nil)
|
26
|
+
return arg if KIND.is?(kind, arg)
|
27
|
+
|
28
|
+
label_text = label ? "#{label}: " : ''
|
29
|
+
|
30
|
+
raise Kind::Error.new("#{label_text}#{arg} expected to be a #{kind}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def of?(kind, *args)
|
34
|
+
KIND.of?(kind, args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def of_class?(value)
|
38
|
+
KIND.of_class?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def of_module?(value)
|
42
|
+
KIND.of_module?(value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to?(value, *method_names)
|
46
|
+
return super(value) if method_names.empty?
|
47
|
+
|
48
|
+
KIND.interface?(method_names, value)
|
49
|
+
end
|
50
|
+
|
51
|
+
def of(kind, value, label: nil)
|
52
|
+
return value if kind === value
|
53
|
+
|
54
|
+
KIND.error!(kind.name, value, label)
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :of!, :of
|
58
|
+
|
59
|
+
def of_module_or_class(value)
|
60
|
+
KIND.of_module_or_class!(value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def respond_to(value, *method_names)
|
64
|
+
method_names.each { |method_name| KIND.respond_to!(method_name, value) }
|
65
|
+
|
66
|
+
value
|
67
|
+
end
|
68
|
+
alias_method :respond_to!, :respond_to
|
69
|
+
|
70
|
+
def value(kind, value, default:)
|
71
|
+
KIND.value(kind, value, of(kind, default))
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
class Error < StandardError
|
5
|
+
INVALID_DEFAULT_ARG = 'the default value must be defined as an argument or block'.freeze
|
6
|
+
|
7
|
+
def self.wrong_number_of_args!(given:, expected:)
|
8
|
+
raise ArgumentError, "wrong number of arguments (given #{given}, expected #{expected})"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.invalid_default_arg!
|
12
|
+
raise ArgumentError, INVALID_DEFAULT_ARG
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(arg, object = UNDEFINED, label: nil)
|
16
|
+
if UNDEFINED == object
|
17
|
+
# Will be used when the exception was raised with a message. e.g:
|
18
|
+
# raise Kind::Error, "some message"
|
19
|
+
super(arg)
|
20
|
+
else
|
21
|
+
label_text = label ? "#{label}: " : ''
|
22
|
+
|
23
|
+
super("#{label_text}#{object.inspect} expected to be a kind of #{arg}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private_constant :INVALID_DEFAULT_ARG
|
28
|
+
end
|
29
|
+
end
|
@@ -5,6 +5,11 @@ module Kind
|
|
5
5
|
def undefined.inspect
|
6
6
|
@inspect ||= 'Kind::Undefined'.freeze
|
7
7
|
end
|
8
|
+
undefined.inspect
|
9
|
+
|
10
|
+
def undefined.empty?
|
11
|
+
true
|
12
|
+
end
|
8
13
|
|
9
14
|
def undefined.to_s
|
10
15
|
inspect
|
@@ -19,9 +24,11 @@ module Kind
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def undefined.default(value, default)
|
22
|
-
return
|
27
|
+
return value if self != value
|
23
28
|
|
24
29
|
default.respond_to?(:call) ? default.call : default
|
25
30
|
end
|
31
|
+
|
32
|
+
undefined.freeze
|
26
33
|
end
|
27
34
|
end
|
data/lib/kind/dig.rb
CHANGED
@@ -1,35 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind/basic'
|
4
|
+
require 'kind/empty'
|
5
|
+
require 'kind/presence'
|
6
|
+
|
3
7
|
module Kind
|
4
8
|
module Dig
|
5
9
|
extend self
|
6
10
|
|
7
|
-
def call(data, keys)
|
8
|
-
return unless keys.is_a?(Array)
|
9
|
-
|
11
|
+
def call!(data, keys = Empty::ARRAY) # :nodoc
|
10
12
|
keys.reduce(data) do |memo, key|
|
11
13
|
value = get(memo, key)
|
12
14
|
|
13
|
-
break if
|
15
|
+
break if KIND.null?(value)
|
14
16
|
|
15
17
|
value
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
21
|
+
def call(data, *input)
|
22
|
+
args = input.size == 1 && input[0].kind_of?(::Array) ? input[0] : input
|
23
|
+
|
24
|
+
result = call!(data, args)
|
25
|
+
|
26
|
+
return result unless block_given?
|
27
|
+
|
28
|
+
yield(result) unless KIND.null?(result)
|
29
|
+
end
|
30
|
+
|
31
|
+
def presence(*args, &block)
|
32
|
+
Presence.(call(*args, &block))
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](*keys)
|
36
|
+
->(data) { call!(data, keys) }
|
37
|
+
end
|
38
|
+
|
19
39
|
private
|
20
40
|
|
21
41
|
def get(data, key)
|
22
|
-
return data[key] if Hash === data
|
42
|
+
return data[key] if ::Hash === data
|
23
43
|
|
24
44
|
case data
|
25
|
-
when Array
|
45
|
+
when ::Array
|
26
46
|
data[key] if key.respond_to?(:to_int)
|
27
|
-
when OpenStruct
|
47
|
+
when ::OpenStruct
|
28
48
|
data[key] if key.respond_to?(:to_sym)
|
29
|
-
when Struct
|
30
|
-
if key.respond_to?(:to_int) || key.respond_to?(:to_sym)
|
31
|
-
|
32
|
-
|
49
|
+
when ::Struct
|
50
|
+
data[key] rescue nil if key.respond_to?(:to_int) || key.respond_to?(:to_sym)
|
51
|
+
else
|
52
|
+
data.public_send(key) if key.respond_to?(:to_sym) && data.respond_to?(key)
|
33
53
|
end
|
34
54
|
end
|
35
55
|
end
|
data/lib/kind/either.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/basic'
|
4
|
+
|
5
|
+
module Kind
|
6
|
+
module Either
|
7
|
+
require 'kind/either/monad'
|
8
|
+
require 'kind/either/left'
|
9
|
+
require 'kind/either/right'
|
10
|
+
require 'kind/either/methods'
|
11
|
+
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def new(value)
|
15
|
+
Right[value]
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :[], :new
|
19
|
+
|
20
|
+
def self.from
|
21
|
+
result = yield
|
22
|
+
|
23
|
+
Either::Monad === result ? result : Either::Right[result]
|
24
|
+
rescue StandardError => e
|
25
|
+
Either::Left[e]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
extend Either::Methods
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
class Either::Left < Either::Monad
|
5
|
+
def left?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def value_or(default = UNDEFINED, &block)
|
10
|
+
Error.invalid_default_arg! if UNDEFINED == default && !block
|
11
|
+
|
12
|
+
UNDEFINED != default ? default : block.call
|
13
|
+
end
|
14
|
+
|
15
|
+
def map(&_)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :map!, :map
|
20
|
+
alias_method :then, :map
|
21
|
+
alias_method :then!, :map
|
22
|
+
alias_method :and_then, :map
|
23
|
+
alias_method :and_then!, :map
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
'#<%s value=%p>' % ['Kind::Left', value]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Either::Methods
|
5
|
+
def Left(value)
|
6
|
+
Either::Left[value]
|
7
|
+
end
|
8
|
+
|
9
|
+
def Right(value)
|
10
|
+
Either::Right[value]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.send(:private, :Left, :Right)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
class Either::Monad
|
5
|
+
require 'kind/either/monad/wrapper'
|
6
|
+
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
singleton_class.send(:alias_method, :[], :new)
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def left?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def right?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def value_or(_method_name = UNDEFINED, &block)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def map(&_)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :map!, :map
|
32
|
+
alias_method :then, :map
|
33
|
+
alias_method :then!, :map
|
34
|
+
alias_method :and_then, :map
|
35
|
+
alias_method :and_then!, :map
|
36
|
+
|
37
|
+
def on
|
38
|
+
monad = Wrapper.new(self)
|
39
|
+
|
40
|
+
yield(monad)
|
41
|
+
|
42
|
+
monad.output
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_right(matcher = UNDEFINED)
|
46
|
+
yield(value) if right? && either?(matcher)
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_left(matcher = UNDEFINED)
|
52
|
+
yield(value) if left? && either?(matcher)
|
53
|
+
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def either?(matcher)
|
58
|
+
UNDEFINED == matcher || matcher === value
|
59
|
+
end
|
60
|
+
|
61
|
+
def ===(monad)
|
62
|
+
self.class === monad && self.value === monad.value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|