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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/.travis.sh +39 -7
  4. data/.travis.yml +1 -0
  5. data/CHANGELOG.md +506 -28
  6. data/Gemfile +13 -6
  7. data/README.md +50 -42
  8. data/kind.gemspec +3 -3
  9. data/lib/kind.rb +2 -60
  10. data/lib/kind/__lib__/action_steps.rb +57 -0
  11. data/lib/kind/__lib__/attributes.rb +66 -0
  12. data/lib/kind/__lib__/kind.rb +51 -0
  13. data/lib/kind/__lib__/of.rb +17 -0
  14. data/lib/kind/__lib__/strict.rb +49 -0
  15. data/lib/kind/__lib__/undefined.rb +14 -0
  16. data/lib/kind/action.rb +127 -0
  17. data/lib/kind/active_model/validation.rb +3 -4
  18. data/lib/kind/basic.rb +79 -0
  19. data/lib/kind/basic/error.rb +29 -0
  20. data/lib/kind/{undefined.rb → basic/undefined.rb} +6 -1
  21. data/lib/kind/dig.rb +21 -5
  22. data/lib/kind/either.rb +30 -0
  23. data/lib/kind/either/left.rb +29 -0
  24. data/lib/kind/either/methods.rb +17 -0
  25. data/lib/kind/either/monad.rb +65 -0
  26. data/lib/kind/either/monad/wrapper.rb +19 -0
  27. data/lib/kind/either/right.rb +38 -0
  28. data/lib/kind/empty.rb +2 -0
  29. data/lib/kind/enum.rb +63 -0
  30. data/lib/kind/enum/item.rb +40 -0
  31. data/lib/kind/enum/methods.rb +72 -0
  32. data/lib/kind/function.rb +45 -0
  33. data/lib/kind/functional.rb +89 -0
  34. data/lib/kind/functional/action.rb +87 -0
  35. data/lib/kind/functional/steps.rb +22 -0
  36. data/lib/kind/immutable_attributes.rb +34 -0
  37. data/lib/kind/immutable_attributes/initializer.rb +70 -0
  38. data/lib/kind/immutable_attributes/reader.rb +38 -0
  39. data/lib/kind/maybe.rb +37 -12
  40. data/lib/kind/maybe/methods.rb +21 -0
  41. data/lib/kind/maybe/monad.rb +82 -0
  42. data/lib/kind/maybe/monad/wrapper.rb +19 -0
  43. data/lib/kind/maybe/none.rb +12 -19
  44. data/lib/kind/maybe/some.rb +68 -26
  45. data/lib/kind/maybe/typed.rb +11 -5
  46. data/lib/kind/maybe/{wrappable.rb → wrapper.rb} +8 -4
  47. data/lib/kind/monad.rb +22 -0
  48. data/lib/kind/monads.rb +5 -0
  49. data/lib/kind/objects.rb +17 -0
  50. data/lib/kind/objects/basic_object.rb +43 -0
  51. data/lib/kind/objects/modules.rb +32 -0
  52. data/lib/kind/{type_checkers → objects/modules}/core/array.rb +3 -1
  53. data/lib/kind/{type_checkers → objects/modules}/core/class.rb +1 -1
  54. data/lib/kind/{type_checkers → objects/modules}/core/comparable.rb +1 -1
  55. data/lib/kind/{type_checkers → objects/modules}/core/enumerable.rb +1 -1
  56. data/lib/kind/{type_checkers → objects/modules}/core/enumerator.rb +1 -1
  57. data/lib/kind/{type_checkers → objects/modules}/core/file.rb +1 -1
  58. data/lib/kind/{type_checkers → objects/modules}/core/float.rb +1 -1
  59. data/lib/kind/{type_checkers → objects/modules}/core/hash.rb +3 -1
  60. data/lib/kind/{type_checkers → objects/modules}/core/integer.rb +1 -1
  61. data/lib/kind/{type_checkers → objects/modules}/core/io.rb +1 -1
  62. data/lib/kind/{type_checkers → objects/modules}/core/method.rb +1 -1
  63. data/lib/kind/{type_checkers → objects/modules}/core/module.rb +1 -1
  64. data/lib/kind/{type_checkers → objects/modules}/core/numeric.rb +1 -1
  65. data/lib/kind/{type_checkers → objects/modules}/core/proc.rb +1 -1
  66. data/lib/kind/{type_checkers → objects/modules}/core/queue.rb +1 -1
  67. data/lib/kind/{type_checkers → objects/modules}/core/range.rb +1 -1
  68. data/lib/kind/{type_checkers → objects/modules}/core/regexp.rb +1 -1
  69. data/lib/kind/{type_checkers → objects/modules}/core/string.rb +3 -1
  70. data/lib/kind/{type_checkers → objects/modules}/core/struct.rb +1 -1
  71. data/lib/kind/{type_checkers → objects/modules}/core/symbol.rb +1 -1
  72. data/lib/kind/{type_checkers → objects/modules}/core/time.rb +1 -1
  73. data/lib/kind/{type_checkers → objects/modules}/custom/boolean.rb +2 -2
  74. data/lib/kind/{type_checkers → objects/modules}/custom/callable.rb +1 -1
  75. data/lib/kind/{type_checkers → objects/modules}/custom/lambda.rb +1 -1
  76. data/lib/kind/{type_checkers → objects/modules}/stdlib/open_struct.rb +3 -1
  77. data/lib/kind/{type_checkers → objects/modules}/stdlib/set.rb +3 -1
  78. data/lib/kind/objects/nil.rb +17 -0
  79. data/lib/kind/objects/not_nil.rb +9 -0
  80. data/lib/kind/objects/object.rb +56 -0
  81. data/lib/kind/objects/respond_to.rb +30 -0
  82. data/lib/kind/objects/union_type.rb +44 -0
  83. data/lib/kind/presence.rb +4 -2
  84. data/lib/kind/result.rb +31 -0
  85. data/lib/kind/result/abstract.rb +53 -0
  86. data/lib/kind/result/failure.rb +33 -0
  87. data/lib/kind/result/methods.rb +17 -0
  88. data/lib/kind/result/monad.rb +74 -0
  89. data/lib/kind/result/monad/wrapper.rb +19 -0
  90. data/lib/kind/result/success.rb +53 -0
  91. data/lib/kind/strict/disabled.rb +34 -0
  92. data/lib/kind/try.rb +22 -10
  93. data/lib/kind/validator.rb +111 -0
  94. data/lib/kind/version.rb +1 -1
  95. metadata +83 -42
  96. data/lib/kind/active_model/kind_validator.rb +0 -103
  97. data/lib/kind/core.rb +0 -8
  98. data/lib/kind/core/kind.rb +0 -61
  99. data/lib/kind/core/undefined.rb +0 -7
  100. data/lib/kind/error.rb +0 -15
  101. data/lib/kind/maybe/result.rb +0 -51
  102. data/lib/kind/type_checker.rb +0 -87
  103. 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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ UNDEFINED = Object.new.tap do |undefined|
5
+ def undefined.inspect
6
+ @inspect ||= 'UNDEFINED'.freeze
7
+ end
8
+
9
+ undefined.inspect
10
+ undefined.freeze
11
+ end
12
+
13
+ private_constant :UNDEFINED
14
+ end
@@ -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
- require 'kind'
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.null?(value)
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