kind 5.0.0 → 5.4.1
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 -1
- data/.travis.sh +39 -7
- data/.travis.yml +1 -0
- data/CHANGELOG.md +506 -28
- data/Gemfile +13 -6
- data/README.md +50 -42
- data/kind.gemspec +3 -3
- data/lib/kind.rb +2 -60
- data/lib/kind/__lib__/action_steps.rb +57 -0
- data/lib/kind/__lib__/attributes.rb +66 -0
- data/lib/kind/__lib__/kind.rb +51 -0
- data/lib/kind/__lib__/of.rb +17 -0
- data/lib/kind/__lib__/strict.rb +49 -0
- data/lib/kind/__lib__/undefined.rb +14 -0
- data/lib/kind/action.rb +127 -0
- data/lib/kind/active_model/validation.rb +3 -4
- data/lib/kind/basic.rb +79 -0
- data/lib/kind/basic/error.rb +29 -0
- data/lib/kind/{undefined.rb → basic/undefined.rb} +6 -1
- data/lib/kind/dig.rb +21 -5
- 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 +2 -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 +45 -0
- data/lib/kind/functional.rb +89 -0
- data/lib/kind/functional/action.rb +87 -0
- data/lib/kind/functional/steps.rb +22 -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 +37 -12
- 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 +12 -19
- data/lib/kind/maybe/some.rb +68 -26
- data/lib/kind/maybe/typed.rb +11 -5
- data/lib/kind/maybe/{wrappable.rb → wrapper.rb} +8 -4
- 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 +43 -0
- data/lib/kind/objects/modules.rb +32 -0
- data/lib/kind/{type_checkers → objects/modules}/core/array.rb +3 -1
- data/lib/kind/{type_checkers → objects/modules}/core/class.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/comparable.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/enumerable.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/enumerator.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/file.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/float.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/hash.rb +3 -1
- data/lib/kind/{type_checkers → objects/modules}/core/integer.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/io.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/method.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/module.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/numeric.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/proc.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/queue.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/range.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/regexp.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/string.rb +3 -1
- data/lib/kind/{type_checkers → objects/modules}/core/struct.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/symbol.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/core/time.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/custom/boolean.rb +2 -2
- data/lib/kind/{type_checkers → objects/modules}/custom/callable.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/custom/lambda.rb +1 -1
- data/lib/kind/{type_checkers → objects/modules}/stdlib/open_struct.rb +3 -1
- data/lib/kind/{type_checkers → objects/modules}/stdlib/set.rb +3 -1
- data/lib/kind/objects/nil.rb +17 -0
- data/lib/kind/objects/not_nil.rb +9 -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 +4 -2
- data/lib/kind/result.rb +31 -0
- data/lib/kind/result/abstract.rb +53 -0
- data/lib/kind/result/failure.rb +33 -0
- data/lib/kind/result/methods.rb +17 -0
- data/lib/kind/result/monad.rb +74 -0
- data/lib/kind/result/monad/wrapper.rb +19 -0
- data/lib/kind/result/success.rb +53 -0
- data/lib/kind/strict/disabled.rb +34 -0
- data/lib/kind/try.rb +22 -10
- data/lib/kind/validator.rb +111 -0
- data/lib/kind/version.rb +1 -1
- metadata +83 -42
- data/lib/kind/active_model/kind_validator.rb +0 -103
- data/lib/kind/core.rb +0 -8
- data/lib/kind/core/kind.rb +0 -61
- data/lib/kind/core/undefined.rb +0 -7
- data/lib/kind/error.rb +0 -15
- data/lib/kind/maybe/result.rb +0 -51
- data/lib/kind/type_checker.rb +0 -87
- data/lib/kind/type_checkers.rb +0 -30
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/__lib__/strict'
|
4
|
+
|
5
|
+
module Kind
|
6
|
+
module KIND
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def nil_or_undefined?(value) # :nodoc:
|
10
|
+
value.nil? || Undefined == value
|
11
|
+
end
|
12
|
+
|
13
|
+
def of?(kind, values) # :nodoc:
|
14
|
+
of_kind = -> value { kind === value }
|
15
|
+
|
16
|
+
values.empty? ? of_kind : values.all?(&of_kind)
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to!(method_name, value) # :nodoc:
|
20
|
+
return value if value.respond_to?(method_name)
|
21
|
+
|
22
|
+
raise Error.new("expected #{value} to respond to :#{method_name}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def interface?(method_names, value) # :nodoc:
|
26
|
+
method_names.all? { |method_name| value.respond_to?(method_name) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def value(kind, arg, default) # :nodoc:
|
30
|
+
kind === arg ? arg : default
|
31
|
+
end
|
32
|
+
|
33
|
+
def is?(expected, value) # :nodoc:
|
34
|
+
is(STRICT.module_or_class(expected), value)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def is(expected_kind, value) # :nodoc:
|
40
|
+
kind = STRICT.module_or_class(value)
|
41
|
+
|
42
|
+
if OF.class?(kind)
|
43
|
+
kind <= expected_kind || expected_kind == ::Class
|
44
|
+
else
|
45
|
+
kind == expected_kind || kind.kind_of?(expected_kind)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private_constant :KIND
|
51
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module OF
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def class?(value) # :nodoc:
|
8
|
+
value.kind_of?(::Class)
|
9
|
+
end
|
10
|
+
|
11
|
+
def module?(value) # :nodoc:
|
12
|
+
::Module == value || (value.kind_of?(::Module) && !class?(value))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private_constant :OF
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/__lib__/of'
|
4
|
+
|
5
|
+
module Kind
|
6
|
+
module STRICT
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def error(kind_name, value, label = nil) # :nodoc:
|
10
|
+
raise Error.new(kind_name, value, label: label)
|
11
|
+
end
|
12
|
+
|
13
|
+
def object_is_a(kind, value, label = nil) # :nodoc:
|
14
|
+
return value if kind === value
|
15
|
+
|
16
|
+
error(kind.name, value, label)
|
17
|
+
end
|
18
|
+
|
19
|
+
def kind_of(kind, value, kind_name = nil) # :nodoc:
|
20
|
+
return value if kind === value
|
21
|
+
|
22
|
+
error(kind_name || kind.name, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def module_or_class(value) # :nodoc:
|
26
|
+
kind_of(::Module, value, 'Module/Class')
|
27
|
+
end
|
28
|
+
|
29
|
+
def class!(value) # :nodoc:
|
30
|
+
kind_of(::Class, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def module!(value) # :nodoc:
|
34
|
+
return value if OF.module?(value)
|
35
|
+
|
36
|
+
error('Module', value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def not_nil(value, label) # :nodoc:
|
40
|
+
return value unless value.nil?
|
41
|
+
|
42
|
+
label_text = label ? "#{label}: " : ''
|
43
|
+
|
44
|
+
raise Error.new("#{label_text}expected to not be nil")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private_constant :STRICT
|
49
|
+
end
|
data/lib/kind/action.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/basic'
|
4
|
+
require 'kind/empty'
|
5
|
+
require 'kind/result'
|
6
|
+
require 'kind/immutable_attributes'
|
7
|
+
require 'kind/__lib__/action_steps'
|
8
|
+
|
9
|
+
module Kind
|
10
|
+
module Action
|
11
|
+
CALL_TMPL = [
|
12
|
+
'def self.call(arg)',
|
13
|
+
' new(Kind.of!(::Hash, arg)).call',
|
14
|
+
'end',
|
15
|
+
'',
|
16
|
+
'def call',
|
17
|
+
' result = call!',
|
18
|
+
'',
|
19
|
+
' return result if Kind::Result::Monad === result',
|
20
|
+
'',
|
21
|
+
' raise Kind::Error, "#{self.class.name}#call! must return a Success() or Failure()"',
|
22
|
+
'end'
|
23
|
+
].join("\n").freeze
|
24
|
+
|
25
|
+
private_constant :CALL_TMPL
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
include ImmutableAttributes::ClassMethods
|
29
|
+
|
30
|
+
def to_proc
|
31
|
+
@to_proc ||= ->(arg) { call(arg) }
|
32
|
+
end
|
33
|
+
|
34
|
+
ATTRIBUTE_METHODS = [
|
35
|
+
:attributes, :attribute,
|
36
|
+
:attribute?, :attribute!,
|
37
|
+
:with_attribute, :with_attributes,
|
38
|
+
:nil_attributes, :nil_attributes?
|
39
|
+
]
|
40
|
+
|
41
|
+
private_constant :ATTRIBUTE_METHODS
|
42
|
+
|
43
|
+
def kind_action!
|
44
|
+
return self if respond_to?(:call)
|
45
|
+
|
46
|
+
public_methods = self.public_instance_methods - ::Object.new.methods
|
47
|
+
|
48
|
+
remaining_methods = public_methods - (__attributes__.keys + ATTRIBUTE_METHODS)
|
49
|
+
|
50
|
+
unless remaining_methods.include?(:call!)
|
51
|
+
raise Kind::Error.new("expected #{self} to implement `#call!`")
|
52
|
+
end
|
53
|
+
|
54
|
+
if remaining_methods.size > 1
|
55
|
+
raise Kind::Error.new("#{self} can only have `#call!` as its public method")
|
56
|
+
end
|
57
|
+
|
58
|
+
call_parameters = public_instance_method(:call!).parameters
|
59
|
+
|
60
|
+
unless call_parameters.empty?
|
61
|
+
raise ArgumentError, "#{self.name}#call! must receive no arguments"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.inherited(_)
|
65
|
+
raise RuntimeError, "#{self.name} is a Kind::Action and it can't be inherited"
|
66
|
+
end
|
67
|
+
|
68
|
+
self.send(:private_class_method, :new)
|
69
|
+
|
70
|
+
self.class_eval(CALL_TMPL)
|
71
|
+
|
72
|
+
self.send(:alias_method, :[], :call)
|
73
|
+
self.send(:alias_method, :===, :call)
|
74
|
+
self.send(:alias_method, :yield, :call)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module StepAdapters
|
79
|
+
private
|
80
|
+
|
81
|
+
def Check!(mthod); __Check(mthod, Empty::HASH); end
|
82
|
+
def Step!(mthod); __Step(mthod, Empty::HASH); end
|
83
|
+
def Map!(mthod); __Map(mthod, Empty::HASH); end
|
84
|
+
def Tee!(_mthod); raise NotImplementedError; end
|
85
|
+
def Try!(mthod, opt = Empty::HASH); __Try(mthod, Empty::HASH, opt); end
|
86
|
+
|
87
|
+
def __resolve_step(method_name, value)
|
88
|
+
m = method(method_name)
|
89
|
+
m.arity > 0 ? m.call(value) : m.call
|
90
|
+
end
|
91
|
+
|
92
|
+
def __map_step_exception(value)
|
93
|
+
{ exception: value }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private_constant :StepAdapters
|
98
|
+
|
99
|
+
def self.included(base)
|
100
|
+
Kind.of_class(base).extend(ClassMethods)
|
101
|
+
|
102
|
+
base.send(:include, ACTION_STEPS)
|
103
|
+
base.send(:include, StepAdapters)
|
104
|
+
base.send(:include, ImmutableAttributes::Reader)
|
105
|
+
end
|
106
|
+
|
107
|
+
include ImmutableAttributes::Initializer
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
'#<%s attributes=%p nil_attributes=%p>' % [self.class.name, attributes, nil_attributes]
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def Failure(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
116
|
+
arg1 = Empty::HASH if UNDEFINED == arg1 && UNDEFINED == arg2
|
117
|
+
|
118
|
+
Result::Failure[arg1, arg2, value_must_be_a: ::Hash]
|
119
|
+
end
|
120
|
+
|
121
|
+
def Success(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
122
|
+
arg1 = Empty::HASH if UNDEFINED == arg1 && UNDEFINED == arg2
|
123
|
+
|
124
|
+
Result::Success[arg1, arg2, value_must_be_a: ::Hash]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
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,79 @@
|
|
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
|
+
OF.class?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def of_module?(value)
|
42
|
+
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
|
+
STRICT.object_is_a(kind, value, label)
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :of!, :of
|
56
|
+
|
57
|
+
def of_class(value)
|
58
|
+
STRICT.class!(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def of_module(value)
|
62
|
+
STRICT.module!(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def of_module_or_class(value)
|
66
|
+
STRICT.module_or_class(value)
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to(value, *method_names)
|
70
|
+
method_names.each { |method_name| KIND.respond_to!(method_name, value) }
|
71
|
+
|
72
|
+
value
|
73
|
+
end
|
74
|
+
alias_method :respond_to!, :respond_to
|
75
|
+
|
76
|
+
def value(kind, value, default:)
|
77
|
+
KIND.value(kind, value, of(kind, default))
|
78
|
+
end
|
79
|
+
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
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Kind
|
3
4
|
Undefined = Object.new.tap do |undefined|
|
4
5
|
def undefined.inspect
|
5
6
|
@inspect ||= 'Kind::Undefined'.freeze
|
6
7
|
end
|
8
|
+
undefined.inspect
|
9
|
+
|
10
|
+
def undefined.empty?
|
11
|
+
true
|
12
|
+
end
|
7
13
|
|
8
14
|
def undefined.to_s
|
9
15
|
inspect
|
@@ -23,7 +29,6 @@ module Kind
|
|
23
29
|
default.respond_to?(:call) ? default.call : default
|
24
30
|
end
|
25
31
|
|
26
|
-
undefined.inspect
|
27
32
|
undefined.freeze
|
28
33
|
end
|
29
34
|
end
|
data/lib/kind/dig.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
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.kind_of?(::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 KIND.
|
15
|
+
break if KIND.nil_or_undefined?(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.nil_or_undefined?(result)
|
29
|
+
end
|
30
|
+
|
31
|
+
def presence(*args, &block)
|
32
|
+
Presence.(call(*args, &block))
|
33
|
+
end
|
34
|
+
|
19
35
|
def [](*keys)
|
20
|
-
->(data) { call(data, keys) }
|
36
|
+
->(data) { call!(data, keys) }
|
21
37
|
end
|
22
38
|
|
23
39
|
private
|