kind 3.0.1 → 5.1.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.
- checksums.yaml +4 -4
- data/.tool-versions +1 -0
- data/.travis.sh +42 -12
- data/.travis.yml +7 -5
- data/CHANGELOG.md +1309 -0
- data/Gemfile +22 -7
- data/README.md +990 -490
- data/kind.gemspec +1 -1
- data/lib/kind.rb +29 -292
- data/lib/kind/active_model/validation.rb +3 -4
- data/lib/kind/core.rb +15 -0
- data/lib/kind/core/dig.rb +40 -0
- data/lib/kind/core/empty.rb +13 -0
- data/lib/kind/core/empty/constant.rb +7 -0
- data/lib/kind/{error.rb → core/error.rb} +2 -6
- data/lib/kind/core/maybe.rb +42 -0
- data/lib/kind/core/maybe/none.rb +57 -0
- data/lib/kind/core/maybe/result.rb +51 -0
- data/lib/kind/core/maybe/some.rb +90 -0
- data/lib/kind/core/maybe/typed.rb +29 -0
- data/lib/kind/core/maybe/wrappable.rb +33 -0
- data/lib/kind/core/presence.rb +33 -0
- data/lib/kind/core/try.rb +34 -0
- data/lib/kind/core/type_checker.rb +87 -0
- data/lib/kind/core/type_checkers.rb +30 -0
- data/lib/kind/core/type_checkers/custom/boolean.rb +19 -0
- data/lib/kind/core/type_checkers/custom/callable.rb +19 -0
- data/lib/kind/core/type_checkers/custom/lambda.rb +19 -0
- data/lib/kind/core/type_checkers/ruby/array.rb +17 -0
- data/lib/kind/core/type_checkers/ruby/class.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/comparable.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/enumerable.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/enumerator.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/file.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/float.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/hash.rb +17 -0
- data/lib/kind/core/type_checkers/ruby/integer.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/io.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/method.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/module.rb +17 -0
- data/lib/kind/core/type_checkers/ruby/numeric.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/proc.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/queue.rb +14 -0
- data/lib/kind/core/type_checkers/ruby/range.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/regexp.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/string.rb +17 -0
- data/lib/kind/core/type_checkers/ruby/struct.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/symbol.rb +13 -0
- data/lib/kind/core/type_checkers/ruby/time.rb +13 -0
- data/lib/kind/core/type_checkers/stdlib/open_struct.rb +13 -0
- data/lib/kind/core/type_checkers/stdlib/set.rb +17 -0
- data/lib/kind/{undefined.rb → core/undefined.rb} +4 -2
- data/lib/kind/core/utils/kind.rb +61 -0
- data/lib/kind/core/utils/undefined.rb +7 -0
- data/lib/kind/validator.rb +108 -1
- data/lib/kind/version.rb +1 -1
- data/test.sh +4 -4
- metadata +50 -15
- 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/empty.rb +0 -21
- data/lib/kind/is.rb +0 -19
- data/lib/kind/maybe.rb +0 -183
- data/lib/kind/of.rb +0 -11
- data/lib/kind/types.rb +0 -115
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
require 'kind/core/maybe/result'
|
6
|
+
require 'kind/core/maybe/none'
|
7
|
+
require 'kind/core/maybe/some'
|
8
|
+
require 'kind/core/maybe/wrappable'
|
9
|
+
require 'kind/core/maybe/typed'
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def new(value)
|
14
|
+
(KIND.null?(value) ? None : Some)
|
15
|
+
.new(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :[], :new
|
19
|
+
|
20
|
+
extend Wrappable
|
21
|
+
end
|
22
|
+
|
23
|
+
Optional = Maybe
|
24
|
+
|
25
|
+
None = Maybe.none
|
26
|
+
|
27
|
+
def self.None
|
28
|
+
None
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.Some(value)
|
32
|
+
Maybe.some(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.Maybe(kind)
|
36
|
+
Maybe::Typed.new(kind)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.Optional(kind)
|
40
|
+
Maybe::Typed.new(kind)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
class None < Result
|
6
|
+
INVALID_DEFAULT_ARG = 'the default value must be defined as an argument or block'.freeze
|
7
|
+
|
8
|
+
def value_or(default = UNDEFINED, &block)
|
9
|
+
raise ArgumentError, INVALID_DEFAULT_ARG if UNDEFINED == default && !block
|
10
|
+
|
11
|
+
UNDEFINED != default ? default : block.call
|
12
|
+
end
|
13
|
+
|
14
|
+
def none?; true; end
|
15
|
+
|
16
|
+
def map(&fn)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :map!, :map
|
21
|
+
alias_method :then, :map
|
22
|
+
alias_method :then!, :map
|
23
|
+
alias_method :check, :map
|
24
|
+
|
25
|
+
def try!(method_name = UNDEFINED, *args, &block)
|
26
|
+
KIND.of!(::Symbol, method_name)if UNDEFINED != method_name
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :try, :try!
|
32
|
+
|
33
|
+
def dig(*keys)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def presence
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private_constant :INVALID_DEFAULT_ARG
|
42
|
+
end
|
43
|
+
|
44
|
+
NONE_WITH_NIL_VALUE = None.new(nil)
|
45
|
+
NONE_WITH_UNDEFINED_VALUE = None.new(Undefined)
|
46
|
+
|
47
|
+
def self.none
|
48
|
+
NONE_WITH_NIL_VALUE
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.__none__(value) # :nodoc:
|
52
|
+
None.new(value)
|
53
|
+
end
|
54
|
+
|
55
|
+
private_constant :NONE_WITH_NIL_VALUE, :NONE_WITH_UNDEFINED_VALUE
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
class Result
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
Value = ->(arg) { arg.kind_of?(Maybe::Result) ? arg.value : arg } # :nodoc:
|
9
|
+
|
10
|
+
def initialize(arg)
|
11
|
+
@value = Value.(arg)
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_or(method_name = UNDEFINED, &block)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def none?
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def some?; !none?; end
|
23
|
+
|
24
|
+
def map(&fn)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method :map!, :map
|
29
|
+
alias_method :then, :map
|
30
|
+
alias_method :then!, :map
|
31
|
+
|
32
|
+
def check(&fn)
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def try(method_name = UNDEFINED, &block)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :try!, :try
|
41
|
+
|
42
|
+
def dig(*keys)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def presence
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
class Some < Result
|
6
|
+
def value_or(default = UNDEFINED, &block)
|
7
|
+
@value
|
8
|
+
end
|
9
|
+
|
10
|
+
def none?; false; end
|
11
|
+
|
12
|
+
def map(&fn)
|
13
|
+
map!(&fn)
|
14
|
+
rescue StandardError => exception
|
15
|
+
None.new(exception)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :then, :map
|
19
|
+
|
20
|
+
def check(&fn)
|
21
|
+
result = fn.call(@value)
|
22
|
+
|
23
|
+
!result || KIND.null?(result) ? NONE_WITH_NIL_VALUE : self
|
24
|
+
end
|
25
|
+
|
26
|
+
def map!(&fn)
|
27
|
+
result = fn.call(@value)
|
28
|
+
|
29
|
+
resolve(result)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :then!, :map!
|
33
|
+
|
34
|
+
def try!(method_name = UNDEFINED, *args, &block)
|
35
|
+
return __try_block__(block, args) if block
|
36
|
+
|
37
|
+
return __try_method__(method_name, args) if UNDEFINED != method_name
|
38
|
+
|
39
|
+
raise ArgumentError, 'method name or a block must be present'
|
40
|
+
end
|
41
|
+
|
42
|
+
def try(method_name = UNDEFINED, *args, &block)
|
43
|
+
return __try_block__(block, args) if block
|
44
|
+
|
45
|
+
return __try_method__(method_name, args) if value.respond_to?(method_name)
|
46
|
+
|
47
|
+
NONE_WITH_NIL_VALUE
|
48
|
+
end
|
49
|
+
|
50
|
+
def dig(*keys)
|
51
|
+
resolve(Dig.(value, keys))
|
52
|
+
end
|
53
|
+
|
54
|
+
def presence
|
55
|
+
resolve(Presence.(value))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def __try_method__(method_name, args)
|
61
|
+
__try_block__(KIND.of!(::Symbol, method_name).to_proc, args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def __try_block__(block, args)
|
65
|
+
result = args.empty? ? block.call(value) : block.call(*args.unshift(value))
|
66
|
+
|
67
|
+
resolve(result)
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolve(result)
|
71
|
+
return result if Maybe::None === result
|
72
|
+
return None.new(result) if Exception === result
|
73
|
+
return NONE_WITH_NIL_VALUE if result.nil?
|
74
|
+
return NONE_WITH_UNDEFINED_VALUE if Undefined == result
|
75
|
+
|
76
|
+
Some.new(result)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
VALUE_CANT_BE_NONE = "value can't be nil or Kind::Undefined".freeze
|
81
|
+
|
82
|
+
def self.some(value)
|
83
|
+
return Maybe::Some.new(value) if !KIND.null?(value)
|
84
|
+
|
85
|
+
raise ArgumentError, VALUE_CANT_BE_NONE
|
86
|
+
end
|
87
|
+
|
88
|
+
private_constant :VALUE_CANT_BE_NONE
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
class Typed
|
6
|
+
include Wrappable
|
7
|
+
|
8
|
+
def initialize(kind)
|
9
|
+
@kind = kind
|
10
|
+
end
|
11
|
+
|
12
|
+
def new(arg)
|
13
|
+
value = Result::Value.(arg)
|
14
|
+
|
15
|
+
@kind === value ? Maybe.some(value) : Maybe.none
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :[], :new
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def __call_before_expose_the_arg_in_a_block(arg)
|
23
|
+
value = Result::Value.(arg)
|
24
|
+
|
25
|
+
@kind === value ? value : Maybe.none
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Maybe
|
5
|
+
module Wrappable
|
6
|
+
def wrap(arg = UNDEFINED)
|
7
|
+
if block_given?
|
8
|
+
begin
|
9
|
+
return new(yield) if UNDEFINED == arg
|
10
|
+
|
11
|
+
input = __call_before_expose_the_arg_in_a_block(arg)
|
12
|
+
|
13
|
+
input.kind_of?(Maybe::None) ? input : new(yield(input))
|
14
|
+
rescue StandardError => exception
|
15
|
+
Maybe.__none__(exception)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
return new(arg) if UNDEFINED != arg
|
19
|
+
|
20
|
+
raise ArgumentError, 'wrong number of arguments (given 0, expected 1)'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def __call_before_expose_the_arg_in_a_block(input)
|
27
|
+
input
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private_constant :Wrappable
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Presence
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(object)
|
8
|
+
return object.blank? ? nil : object if object.respond_to?(:blank?)
|
9
|
+
|
10
|
+
return object if TrueClass === object
|
11
|
+
|
12
|
+
return blank_str?(object) ? nil : object if String === object
|
13
|
+
|
14
|
+
return object.empty? ? nil : object if object.respond_to?(:empty?)
|
15
|
+
|
16
|
+
return object if object
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_proc
|
20
|
+
-> object { call(object) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
26
|
+
|
27
|
+
def blank_str?(object)
|
28
|
+
object.empty? || BLANK_RE === object
|
29
|
+
end
|
30
|
+
|
31
|
+
private_constant :BLANK_RE
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Try
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
object = args.shift
|
9
|
+
|
10
|
+
call!(object, args.shift, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.[](*args)
|
14
|
+
method_name = args.shift
|
15
|
+
|
16
|
+
->(object) { call!(object, method_name, args) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def call!(object, method_name, args) # :nodoc
|
20
|
+
return if KIND.null?(object)
|
21
|
+
|
22
|
+
resolve(object, method_name, args)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def resolve(object, method_name, args = Empty::ARRAY)
|
28
|
+
return unless object.respond_to?(method_name)
|
29
|
+
return object.public_send(method_name) if args.empty?
|
30
|
+
|
31
|
+
object.public_send(method_name, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module TypeChecker
|
5
|
+
def name
|
6
|
+
kind.name
|
7
|
+
end
|
8
|
+
|
9
|
+
def ===(value)
|
10
|
+
kind === value
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](value)
|
14
|
+
return value if self === value
|
15
|
+
|
16
|
+
KIND.error!(name, value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def or_nil(value)
|
20
|
+
return value if self === value
|
21
|
+
end
|
22
|
+
|
23
|
+
def or_undefined(value)
|
24
|
+
or_nil(value) || Undefined
|
25
|
+
end
|
26
|
+
|
27
|
+
def or(fallback, value = UNDEFINED)
|
28
|
+
return __or_func.(fallback) if UNDEFINED === value
|
29
|
+
|
30
|
+
self === value ? value : fallback
|
31
|
+
end
|
32
|
+
|
33
|
+
def value?(value = UNDEFINED)
|
34
|
+
return self === value if UNDEFINED != value
|
35
|
+
|
36
|
+
@__is_value ||= ->(tc) { ->(arg) { tc === arg } }.(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def value(arg, default:)
|
40
|
+
KIND.value(self, arg, self[default])
|
41
|
+
end
|
42
|
+
|
43
|
+
def or_null(value) # :nodoc:
|
44
|
+
KIND.null?(value) ? value : self[value]
|
45
|
+
end
|
46
|
+
|
47
|
+
def maybe(value = UNDEFINED, &block)
|
48
|
+
return __maybe[value] if UNDEFINED != value && !block
|
49
|
+
return __maybe.wrap(&block) if UNDEFINED == value && block
|
50
|
+
return __maybe.wrap(value, &block) if UNDEFINED != value && block
|
51
|
+
|
52
|
+
__maybe
|
53
|
+
end
|
54
|
+
|
55
|
+
alias optional maybe
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def __maybe
|
60
|
+
@__maybe ||= Maybe::Typed.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def __or_func
|
64
|
+
@__or_func ||= ->(tc, fb, value) { tc === value ? value : tc.or_null(fb) }.curry[self]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TypeChecker::Object # :nodoc: all
|
69
|
+
include TypeChecker
|
70
|
+
|
71
|
+
ResolveKindName = ->(kind, opt) do
|
72
|
+
name = Try.(opt, :[], :name)
|
73
|
+
name || Try.(kind, :name)
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :kind, :name
|
77
|
+
|
78
|
+
def initialize(kind, opt)
|
79
|
+
name = ResolveKindName.(kind, opt)
|
80
|
+
|
81
|
+
@name = KIND.of!(::String, name)
|
82
|
+
@kind = KIND.respond_to!(:===, kind)
|
83
|
+
end
|
84
|
+
|
85
|
+
private_constant :ResolveKindName
|
86
|
+
end
|
87
|
+
end
|