kind 4.0.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/.travis.sh +39 -7
  4. data/.travis.yml +1 -2
  5. data/CHANGELOG.md +486 -28
  6. data/Gemfile +13 -6
  7. data/README.md +153 -49
  8. data/kind.gemspec +1 -1
  9. data/lib/kind.rb +2 -84
  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 -2
  29. data/lib/kind/empty/constant.rb +7 -0
  30. data/lib/kind/enum.rb +63 -0
  31. data/lib/kind/enum/item.rb +40 -0
  32. data/lib/kind/enum/methods.rb +72 -0
  33. data/lib/kind/function.rb +45 -0
  34. data/lib/kind/functional.rb +89 -0
  35. data/lib/kind/functional/action.rb +90 -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} +9 -7
  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 +21 -11
  93. data/lib/kind/validator.rb +111 -0
  94. data/lib/kind/version.rb +1 -1
  95. metadata +78 -48
  96. data/lib/kind/active_model/kind_validator.rb +0 -96
  97. data/lib/kind/core.rb +0 -9
  98. data/lib/kind/core/deprecation.rb +0 -29
  99. data/lib/kind/core/kind.rb +0 -61
  100. data/lib/kind/core/undefined.rb +0 -7
  101. data/lib/kind/deprecations/built_in_type_checkers.rb +0 -23
  102. data/lib/kind/deprecations/checker.rb +0 -16
  103. data/lib/kind/deprecations/checker/factory.rb +0 -31
  104. data/lib/kind/deprecations/checker/protocol.rb +0 -73
  105. data/lib/kind/deprecations/is.rb +0 -35
  106. data/lib/kind/deprecations/of.rb +0 -258
  107. data/lib/kind/deprecations/types.rb +0 -121
  108. data/lib/kind/error.rb +0 -15
  109. data/lib/kind/maybe/result.rb +0 -51
  110. data/lib/kind/type_checker.rb +0 -73
  111. data/lib/kind/type_checkers.rb +0 -30
data/kind.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = %q{A simple type system (at runtime) for Ruby - free of dependencies.}
11
11
  spec.homepage = 'https://github.com/serradura/kind'
12
12
  spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 2.2.0')
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.1.0')
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = 'https://github.com/serradura/kind'
data/lib/kind.rb CHANGED
@@ -1,86 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require 'ostruct'
5
-
6
- require 'kind/version'
7
- require 'kind/core'
8
-
9
- require 'kind/error'
10
- require 'kind/empty'
11
- require 'kind/dig'
12
- require 'kind/try'
13
- require 'kind/presence'
14
- require 'kind/undefined'
15
- require 'kind/type_checker'
16
-
17
- require 'kind/type_checkers'
18
- require 'kind/maybe'
19
-
20
- require 'kind/deprecations/checker'
21
- require 'kind/deprecations/of'
22
- require 'kind/deprecations/is'
23
- require 'kind/deprecations/types'
24
- require 'kind/deprecations/built_in_type_checkers'
25
-
26
- module Kind
27
- def self.is?(kind, arg)
28
- KIND.is?(kind, arg)
29
- end
30
-
31
- def self.of?(kind, *args)
32
- KIND.of?(kind, args)
33
- end
34
-
35
- def self.of_class?(value)
36
- KIND.of_class?(value)
37
- end
38
-
39
- def self.of_module?(value)
40
- KIND.of_module?(value)
41
- end
42
-
43
- def self.respond_to(value, *method_names)
44
- method_names.each { |method_name| KIND.respond_to!(method_name, value) }
45
-
46
- value
47
- end
48
-
49
- def self.of_module_or_class(value)
50
- KIND.of_module_or_class!(value)
51
- end
52
-
53
- def self.is(expected = UNDEFINED, object = UNDEFINED)
54
- if UNDEFINED == expected && UNDEFINED == object
55
- DEPRECATION.warn('`Kind.is` without args is deprecated. This behavior will be removed in %{version}')
56
-
57
- return Is
58
- end
59
-
60
- return is?(expected, object) if UNDEFINED != object
61
-
62
- raise ArgumentError, 'wrong number of arguments (given 1, expected 2)'
63
- end
64
-
65
- def self.of(kind = UNDEFINED, object = UNDEFINED)
66
- if UNDEFINED == kind && UNDEFINED == object
67
- DEPRECATION.warn('`Kind.of` without args is deprecated. This behavior will be removed in %{version}')
68
-
69
- Of
70
- elsif UNDEFINED != object
71
- KIND.of!(kind, object)
72
- else
73
- DEPRECATION.warn_method_replacement('Kind.of(<Kind>)', 'Kind::Of(<Kind>)')
74
-
75
- Checker::Factory.create(kind)
76
- end
77
- end
78
-
79
- def self.value(kind, value, default:)
80
- KIND.value(kind, value, of(kind, default))
81
- end
82
-
83
- def self.Of(kind, opt = Empty::HASH)
84
- TypeChecker::Object.new(kind, opt)
85
- end
86
- end
3
+ require 'kind/basic'
4
+ require 'kind/objects'
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module ACTION_STEPS
5
+ private
6
+
7
+ def Check(mthod); ->(value) { __Check(thod, value) }; end
8
+ def Step(mthod); ->(value) { __Step(mthod, value) }; end
9
+ def Map(mthod); ->(value) { __Map(mthod, value) }; end
10
+ def Tee(mthod); ->(value) { __Tee(mthod, value) }; end
11
+ def Try(mthod, opt = Empty::HASH)
12
+ ->(value) { __Try(mthod, value, opt) }
13
+ end
14
+
15
+ def Check!(mthod, value); __Check(mthod, value); end
16
+ def Step!(mthod, value); __Step(mthod, value); end
17
+ def Map!(mthod, value); __Map(mthod, value); end
18
+ def Tee!(mthod, value); __Tee(mthod, value); end
19
+ def Try!(mthod, value, opt = Empty::HASH); __Try(mthod, value, opt); end
20
+
21
+ def __Check(mthod, value) # :nodoc:
22
+ __resolve_step(mthod, value) ? Success(mthod, value) : Failure(mthod, value)
23
+ end
24
+
25
+ def __Step(mthod, value) # :nodoc:
26
+ __resolve_step(mthod, value)
27
+ end
28
+
29
+ def __Map(mthod, value) # :nodoc:
30
+ Success(mthod, __resolve_step(mthod, value))
31
+ end
32
+
33
+ def __Tee(mthod, value) # :nodoc:
34
+ __resolve_step(mthod, value)
35
+
36
+ Success(mthod, value)
37
+ end
38
+
39
+ def __Try(mthod, value, opt = Empty::HASH) # :nodoc:
40
+ begin
41
+ Success(mthod, __resolve_step(mthod, value))
42
+ rescue opt.fetch(:catch, StandardError) => e
43
+ Failure(mthod, __map_step_exception(e))
44
+ end
45
+ end
46
+
47
+ def __resolve_step(mthod, value) # :nodoc:
48
+ send(mthod, value)
49
+ end
50
+
51
+ def __map_step_exception(value) # :nodoc:
52
+ value
53
+ end
54
+ end
55
+
56
+ private_constant :ACTION_STEPS
57
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/__lib__/kind'
4
+ require 'kind/__lib__/undefined'
5
+
6
+ module Kind
7
+ module ATTRIBUTES
8
+ extend self
9
+
10
+ def name!(name)
11
+ STRICT.kind_of(::Symbol, name)
12
+ end
13
+
14
+ def value(kind, default, visibility = :private)
15
+ [kind, default, visibility]
16
+ end
17
+
18
+ def value_to_assign(kind, default, hash, name)
19
+ raw_value = hash[name]
20
+
21
+ return raw_value if kind.nil? && UNDEFINED == default
22
+
23
+ value = resolve_value_to_assign(kind, default, raw_value)
24
+
25
+ (kind.nil? || kind === value) ? value : nil
26
+ end
27
+
28
+ def value!(kind, default)
29
+ return value(kind, default) unless kind.nil?
30
+
31
+ raise Error.new("kind expected to not be nil")
32
+ end
33
+
34
+ def value_to_assign!(kind, default, hash, name)
35
+ value = resolve_value_to_assign(kind, default, hash[name])
36
+
37
+ Kind.of(kind, value, label: name)
38
+ end
39
+
40
+ private
41
+
42
+ def resolve_value_to_assign(kind, default, value)
43
+ if kind == ::Proc
44
+ UNDEFINED == default ? value : KIND.value(kind, value, default)
45
+ else
46
+ default_is_a_callable = default.respond_to?(:call)
47
+
48
+ default_value =
49
+ if default_is_a_callable
50
+ default_fn = Proc === default ? default : default.method(:call)
51
+
52
+ default_fn.arity != 0 ? default_fn.call(value) : default_fn.call
53
+ else
54
+ default
55
+ end
56
+
57
+ return value if UNDEFINED == default_value
58
+ return default_value || value if kind.nil?
59
+
60
+ default_is_a_callable ? KIND.value(kind, default_value, value) : KIND.value(kind, value, default_value)
61
+ end
62
+ end
63
+ end
64
+
65
+ private_constant :ATTRIBUTES
66
+ end
@@ -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