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
data/lib/kind/presence.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind/basic'
|
4
|
+
|
3
5
|
module Kind
|
4
6
|
module Presence
|
5
7
|
extend self
|
6
8
|
|
7
9
|
def call(object)
|
8
|
-
return
|
10
|
+
return if KIND.nil_or_undefined?(object)
|
9
11
|
|
10
|
-
return object
|
12
|
+
return object.blank? ? nil : object if object.respond_to?(:blank?)
|
11
13
|
|
12
14
|
return blank_str?(object) ? nil : object if String === object
|
13
15
|
|
data/lib/kind/result.rb
ADDED
@@ -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,33 @@
|
|
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(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def map(_ = UNDEFINED, &_fn)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :|, :map
|
22
|
+
alias_method :>>, :map
|
23
|
+
alias_method :map!, :map
|
24
|
+
alias_method :then, :map
|
25
|
+
alias_method :then!, :map
|
26
|
+
alias_method :and_then, :map
|
27
|
+
alias_method :and_then!, :map
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
'#<%s type=%p value=%p>' % ['Kind::Failure', type, value]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
class Result::Monad
|
5
|
+
include Result::Abstract
|
6
|
+
|
7
|
+
require 'kind/empty'
|
8
|
+
require 'kind/result/monad/wrapper'
|
9
|
+
|
10
|
+
attr_reader :type, :value
|
11
|
+
|
12
|
+
def self.[](arg1 = UNDEFINED, arg2 = UNDEFINED, opt = Empty::HASH) # :nodoc:
|
13
|
+
value_must_be_a = opt[:value_must_be_a]
|
14
|
+
|
15
|
+
type = UNDEFINED == arg2 ? self::DEFAULT_TYPE : STRICT.kind_of(::Symbol, arg1)
|
16
|
+
|
17
|
+
Error.wrong_number_of_args!(given: 0, expected: '1 or 2') if UNDEFINED == arg1
|
18
|
+
|
19
|
+
value = UNDEFINED == arg2 ? arg1 : arg2
|
20
|
+
|
21
|
+
new(type, (value_must_be_a ? STRICT.kind_of(value_must_be_a, value) : value))
|
22
|
+
end
|
23
|
+
|
24
|
+
private_class_method :new
|
25
|
+
|
26
|
+
def initialize(type, value)
|
27
|
+
@type = type
|
28
|
+
@value = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_or(_method_name = UNDEFINED, &block)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def map(_ = UNDEFINED, &_fn)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :|, :map
|
40
|
+
alias_method :>>, :map
|
41
|
+
alias_method :map!, :map
|
42
|
+
alias_method :then, :map
|
43
|
+
alias_method :then!, :map
|
44
|
+
alias_method :and_then, :map
|
45
|
+
alias_method :and_then!, :map
|
46
|
+
|
47
|
+
def on
|
48
|
+
monad = Wrapper.new(self)
|
49
|
+
|
50
|
+
yield(monad)
|
51
|
+
|
52
|
+
monad.output
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_success(types = Undefined, matcher = Undefined)
|
56
|
+
yield(value) if success? && result?(types, matcher)
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_failure(types = Undefined, matcher = Undefined)
|
62
|
+
yield(value) if failure? && result?(types, matcher)
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def ===(m)
|
68
|
+
return false unless Result::Abstract === m
|
69
|
+
return false unless (self.success? && m.success?) || (self.failure? && m.failure?)
|
70
|
+
|
71
|
+
self.type == m.type && self.value === m.value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
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,53 @@
|
|
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(callable = UNDEFINED, &fn)
|
16
|
+
_resolve_map(callable, fn)
|
17
|
+
rescue Kind::Monad::Error => e
|
18
|
+
raise e
|
19
|
+
rescue StandardError => e
|
20
|
+
Result::Failure[:exception, e]
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :then, :map
|
24
|
+
alias_method :and_then, :map
|
25
|
+
|
26
|
+
def map!(callable = UNDEFINED, &fn)
|
27
|
+
_resolve_map(callable, fn)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :|, :map!
|
31
|
+
alias_method :>>, :map!
|
32
|
+
alias_method :then!, :map!
|
33
|
+
alias_method :and_then!, :map!
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
'#<%s type=%p value=%p>' % ['Kind::Success', type, value]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def _resolve_map(callable, fn)
|
42
|
+
callable.respond_to?(:call) ? _map(callable) : _map(fn)
|
43
|
+
end
|
44
|
+
|
45
|
+
def _map(fn)
|
46
|
+
monad = fn.call(@value)
|
47
|
+
|
48
|
+
return monad if Result::Monad === monad
|
49
|
+
|
50
|
+
raise Kind::Monad::Error.new('Kind::Success | Kind::Failure', monad)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module STRICT
|
5
|
+
[
|
6
|
+
:object_is_a, :class!, :kind_of,
|
7
|
+
:module_or_class, :module!, :not_nil
|
8
|
+
].each { |method_name| remove_method(method_name) }
|
9
|
+
|
10
|
+
def object_is_a(_kind, value, _label = nil) # :nodoc:
|
11
|
+
value
|
12
|
+
end
|
13
|
+
|
14
|
+
def class!(value) # :nodoc:
|
15
|
+
value
|
16
|
+
end
|
17
|
+
|
18
|
+
def kind_of(_kind, value, _kind_name = nil) # :nodoc:
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def module_or_class(value) # :nodoc:
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
def module!(value) # :nodoc:
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def not_nil(value, label) # :nodoc:
|
31
|
+
value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/kind/try.rb
CHANGED
@@ -1,30 +1,42 @@
|
|
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 Try
|
5
9
|
extend self
|
6
10
|
|
7
|
-
def call(
|
8
|
-
|
11
|
+
def call!(object, method_name, args = Empty::ARRAY) # :nodoc
|
12
|
+
return if KIND.nil_or_undefined?(object)
|
9
13
|
|
10
|
-
|
14
|
+
resolve(object, method_name, args)
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
14
|
-
|
17
|
+
def call(object, *input)
|
18
|
+
args = input.size == 1 && input[0].kind_of?(::Array) ? input[0] : input
|
15
19
|
|
16
|
-
|
20
|
+
result = call!(object, args.shift, args)
|
21
|
+
|
22
|
+
return result unless block_given?
|
23
|
+
|
24
|
+
yield(result) unless KIND.nil_or_undefined?(result)
|
17
25
|
end
|
18
26
|
|
19
|
-
def
|
20
|
-
|
27
|
+
def presence(*args, &block)
|
28
|
+
Presence.(call(*args, &block))
|
29
|
+
end
|
21
30
|
|
22
|
-
|
31
|
+
def self.[](*args)
|
32
|
+
method_name = args.shift
|
33
|
+
|
34
|
+
->(object) { call!(object, method_name, args) }
|
23
35
|
end
|
24
36
|
|
25
37
|
private
|
26
38
|
|
27
|
-
def resolve(object, method_name, args
|
39
|
+
def resolve(object, method_name, args)
|
28
40
|
return unless object.respond_to?(method_name)
|
29
41
|
return object.public_send(method_name) if args.empty?
|
30
42
|
|
data/lib/kind/validator.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind/basic'
|
4
|
+
|
3
5
|
module Kind
|
4
6
|
module Validator
|
5
7
|
DEFAULT_STRATEGIES = ::Set.new(%w[instance_of kind_of]).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
|