kind 4.0.0 → 5.3.0

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 (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