kind 3.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -0
  3. data/.travis.sh +67 -12
  4. data/.travis.yml +7 -5
  5. data/CHANGELOG.md +1647 -0
  6. data/Gemfile +22 -7
  7. data/README.md +920 -486
  8. data/kind.gemspec +1 -1
  9. data/lib/kind.rb +2 -314
  10. data/lib/kind/__lib__/attributes.rb +66 -0
  11. data/lib/kind/__lib__/kind.rb +71 -0
  12. data/lib/kind/__lib__/undefined.rb +14 -0
  13. data/lib/kind/action.rb +92 -0
  14. data/lib/kind/active_model/validation.rb +3 -4
  15. data/lib/kind/basic.rb +73 -0
  16. data/lib/kind/basic/error.rb +29 -0
  17. data/lib/kind/{undefined.rb → basic/undefined.rb} +8 -1
  18. data/lib/kind/dig.rb +31 -11
  19. data/lib/kind/either.rb +30 -0
  20. data/lib/kind/either/left.rb +29 -0
  21. data/lib/kind/either/methods.rb +17 -0
  22. data/lib/kind/either/monad.rb +65 -0
  23. data/lib/kind/either/monad/wrapper.rb +19 -0
  24. data/lib/kind/either/right.rb +38 -0
  25. data/lib/kind/empty.rb +4 -10
  26. data/lib/kind/empty/constant.rb +7 -0
  27. data/lib/kind/enum.rb +63 -0
  28. data/lib/kind/enum/item.rb +40 -0
  29. data/lib/kind/enum/methods.rb +72 -0
  30. data/lib/kind/function.rb +47 -0
  31. data/lib/kind/functional.rb +89 -0
  32. data/lib/kind/functional/action.rb +89 -0
  33. data/lib/kind/immutable_attributes.rb +34 -0
  34. data/lib/kind/immutable_attributes/initializer.rb +70 -0
  35. data/lib/kind/immutable_attributes/reader.rb +38 -0
  36. data/lib/kind/maybe.rb +35 -159
  37. data/lib/kind/maybe/methods.rb +21 -0
  38. data/lib/kind/maybe/monad.rb +82 -0
  39. data/lib/kind/maybe/monad/wrapper.rb +19 -0
  40. data/lib/kind/maybe/none.rb +50 -0
  41. data/lib/kind/maybe/some.rb +132 -0
  42. data/lib/kind/maybe/typed.rb +35 -0
  43. data/lib/kind/maybe/wrapper.rb +37 -0
  44. data/lib/kind/monad.rb +22 -0
  45. data/lib/kind/monads.rb +5 -0
  46. data/lib/kind/objects.rb +17 -0
  47. data/lib/kind/objects/basic_object.rb +45 -0
  48. data/lib/kind/objects/modules.rb +32 -0
  49. data/lib/kind/objects/modules/core/array.rb +19 -0
  50. data/lib/kind/objects/modules/core/class.rb +13 -0
  51. data/lib/kind/objects/modules/core/comparable.rb +13 -0
  52. data/lib/kind/objects/modules/core/enumerable.rb +13 -0
  53. data/lib/kind/objects/modules/core/enumerator.rb +13 -0
  54. data/lib/kind/objects/modules/core/file.rb +13 -0
  55. data/lib/kind/objects/modules/core/float.rb +13 -0
  56. data/lib/kind/objects/modules/core/hash.rb +19 -0
  57. data/lib/kind/objects/modules/core/integer.rb +13 -0
  58. data/lib/kind/objects/modules/core/io.rb +13 -0
  59. data/lib/kind/objects/modules/core/method.rb +13 -0
  60. data/lib/kind/objects/modules/core/module.rb +17 -0
  61. data/lib/kind/objects/modules/core/numeric.rb +13 -0
  62. data/lib/kind/objects/modules/core/proc.rb +13 -0
  63. data/lib/kind/objects/modules/core/queue.rb +14 -0
  64. data/lib/kind/objects/modules/core/range.rb +13 -0
  65. data/lib/kind/objects/modules/core/regexp.rb +13 -0
  66. data/lib/kind/objects/modules/core/string.rb +19 -0
  67. data/lib/kind/objects/modules/core/struct.rb +13 -0
  68. data/lib/kind/objects/modules/core/symbol.rb +13 -0
  69. data/lib/kind/objects/modules/core/time.rb +13 -0
  70. data/lib/kind/objects/modules/custom/boolean.rb +19 -0
  71. data/lib/kind/objects/modules/custom/callable.rb +19 -0
  72. data/lib/kind/objects/modules/custom/lambda.rb +19 -0
  73. data/lib/kind/objects/modules/stdlib/open_struct.rb +15 -0
  74. data/lib/kind/objects/modules/stdlib/set.rb +19 -0
  75. data/lib/kind/objects/nil.rb +17 -0
  76. data/lib/kind/objects/not_nil.rb +13 -0
  77. data/lib/kind/objects/object.rb +56 -0
  78. data/lib/kind/objects/respond_to.rb +30 -0
  79. data/lib/kind/objects/union_type.rb +44 -0
  80. data/lib/kind/presence.rb +35 -0
  81. data/lib/kind/result.rb +31 -0
  82. data/lib/kind/result/abstract.rb +53 -0
  83. data/lib/kind/result/failure.rb +31 -0
  84. data/lib/kind/result/methods.rb +17 -0
  85. data/lib/kind/result/monad.rb +69 -0
  86. data/lib/kind/result/monad/wrapper.rb +19 -0
  87. data/lib/kind/result/success.rb +40 -0
  88. data/lib/kind/try.rb +46 -0
  89. data/lib/kind/validator.rb +112 -1
  90. data/lib/kind/version.rb +1 -1
  91. data/test.sh +4 -4
  92. metadata +81 -13
  93. data/lib/kind/active_model/kind_validator.rb +0 -96
  94. data/lib/kind/checker.rb +0 -15
  95. data/lib/kind/checker/factory.rb +0 -35
  96. data/lib/kind/checker/protocol.rb +0 -73
  97. data/lib/kind/error.rb +0 -19
  98. data/lib/kind/is.rb +0 -19
  99. data/lib/kind/of.rb +0 -11
  100. data/lib/kind/types.rb +0 -115
@@ -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,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
- 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,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 self if self != value
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 value.nil?
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
- data[key] rescue nil
32
- end
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
@@ -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