kind 1.7.0 → 2.2.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.
- checksums.yaml +4 -4
- data/.travis.sh +20 -0
- data/.travis.yml +3 -1
- data/Gemfile +31 -6
- data/README.md +549 -55
- data/kind.gemspec +2 -2
- data/lib/kind.rb +59 -34
- data/lib/kind/active_model/kind_validator.rb +96 -0
- data/lib/kind/active_model/validation.rb +7 -0
- data/lib/kind/checker.rb +41 -3
- data/lib/kind/error.rb +12 -2
- data/lib/kind/maybe.rb +36 -11
- data/lib/kind/types.rb +14 -3
- data/lib/kind/undefined.rb +1 -1
- data/lib/kind/validator.rb +40 -0
- data/lib/kind/version.rb +1 -1
- data/test.sh +11 -0
- metadata +9 -4
data/kind.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ['Rodrigo Serradura']
|
7
7
|
spec.email = ['rodrigo.serradura@gmail.com']
|
8
8
|
|
9
|
-
spec.summary = %q{
|
10
|
-
spec.description = %q{
|
9
|
+
spec.summary = %q{A simple type system (at runtime) for Ruby.}
|
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
13
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.2.0')
|
data/lib/kind.rb
CHANGED
@@ -18,9 +18,9 @@ module Kind
|
|
18
18
|
private_constant :WRONG_NUMBER_OF_ARGUMENTS
|
19
19
|
|
20
20
|
def self.is(expected = Undefined, object = Undefined)
|
21
|
-
return Is if
|
21
|
+
return Is if Undefined == expected && Undefined == object
|
22
22
|
|
23
|
-
return Kind::Is.(expected, object) if
|
23
|
+
return Kind::Is.(expected, object) if Undefined != object
|
24
24
|
|
25
25
|
raise ArgumentError, WRONG_NUMBER_OF_ARGUMENTS
|
26
26
|
end
|
@@ -29,10 +29,16 @@ module Kind
|
|
29
29
|
|
30
30
|
private_constant :MODULE_OR_CLASS
|
31
31
|
|
32
|
+
private_class_method def self.__checkers__
|
33
|
+
@__checkers__ ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
__checkers__
|
37
|
+
|
32
38
|
def self.of(kind = Undefined, object = Undefined)
|
33
|
-
return Of if
|
39
|
+
return Of if Undefined == kind && Undefined == object
|
34
40
|
|
35
|
-
return Kind::Of.(kind, object) if
|
41
|
+
return Kind::Of.(kind, object) if Undefined != object
|
36
42
|
|
37
43
|
__checkers__[kind] ||= begin
|
38
44
|
kind_name = kind.name
|
@@ -45,8 +51,8 @@ module Kind
|
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
|
49
|
-
|
54
|
+
def self.of?(kind, *args)
|
55
|
+
Kind.of(kind).instance?(*args)
|
50
56
|
end
|
51
57
|
|
52
58
|
# --------------------- #
|
@@ -55,20 +61,19 @@ module Kind
|
|
55
61
|
|
56
62
|
module Is
|
57
63
|
def self.Class(value)
|
58
|
-
value.
|
64
|
+
value.kind_of?(::Class)
|
59
65
|
end
|
60
66
|
|
61
67
|
def self.Module(value)
|
62
|
-
|
68
|
+
::Module == value || (value.is_a?(::Module) && !self.Class(value))
|
63
69
|
end
|
64
70
|
|
65
71
|
def self.Boolean(value)
|
66
|
-
|
67
|
-
klass <= TrueClass || klass <= FalseClass
|
72
|
+
Kind.of.Class(value) <= TrueClass || value <= FalseClass
|
68
73
|
end
|
69
74
|
|
70
75
|
def self.Callable(value)
|
71
|
-
value.respond_to?(:call)
|
76
|
+
value.respond_to?(:call)
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
@@ -76,7 +81,7 @@ module Kind
|
|
76
81
|
# -- Class
|
77
82
|
|
78
83
|
def self.Class(object = Undefined)
|
79
|
-
return Class if
|
84
|
+
return Class if Undefined == object
|
80
85
|
|
81
86
|
self.call(::Class, object)
|
82
87
|
end
|
@@ -88,13 +93,17 @@ module Kind
|
|
88
93
|
|
89
94
|
def self.class?(value); Kind::Is.Class(value); end
|
90
95
|
|
91
|
-
def self.
|
96
|
+
def self.__is_instance__(value); class?(value); end
|
92
97
|
end)
|
93
98
|
|
99
|
+
def self.Class?(*args)
|
100
|
+
Kind::Of::Class.instance?(*args)
|
101
|
+
end
|
102
|
+
|
94
103
|
# -- Module
|
95
104
|
|
96
105
|
def self.Module(object = Undefined)
|
97
|
-
return Module if
|
106
|
+
return Module if Undefined == object
|
98
107
|
|
99
108
|
self.call(::Module, object)
|
100
109
|
end
|
@@ -102,7 +111,11 @@ module Kind
|
|
102
111
|
const_set(:Module, ::Module.new do
|
103
112
|
extend Checkable
|
104
113
|
|
105
|
-
def self.
|
114
|
+
def self.__kind_undefined(value)
|
115
|
+
__kind_error(Kind::Undefined) if Kind::Undefined == value
|
116
|
+
|
117
|
+
yield
|
118
|
+
end
|
106
119
|
|
107
120
|
def self.__kind_error(value)
|
108
121
|
raise Kind::Error.new('Module'.freeze, value)
|
@@ -114,11 +127,7 @@ module Kind
|
|
114
127
|
__kind_error(value)
|
115
128
|
end
|
116
129
|
|
117
|
-
def self.
|
118
|
-
__kind_error(Kind::Undefined) if value == Kind::Undefined
|
119
|
-
|
120
|
-
yield
|
121
|
-
end
|
130
|
+
def self.__kind; ::Module; end
|
122
131
|
|
123
132
|
def self.class?(value); Kind::Is.Module(value); end
|
124
133
|
|
@@ -128,21 +137,25 @@ module Kind
|
|
128
137
|
if ::Kind::Maybe::Value.none?(default)
|
129
138
|
__kind_undefined(value) { __kind_of(value) }
|
130
139
|
else
|
131
|
-
return value if instance?(value)
|
140
|
+
return value if Kind::Undefined != value && instance?(value)
|
132
141
|
|
133
142
|
__kind_undefined(default) { __kind_of(default) }
|
134
143
|
end
|
135
144
|
end
|
136
145
|
|
137
|
-
def self.
|
146
|
+
def self.__is_instance__(value); class?(value); end
|
138
147
|
end)
|
139
148
|
|
149
|
+
def self.Module?(*args)
|
150
|
+
Kind::Of::Module.instance?(*args)
|
151
|
+
end
|
152
|
+
|
140
153
|
# -- Boolean
|
141
154
|
|
142
155
|
def self.Boolean(object = Undefined, options = Empty::HASH)
|
143
156
|
default = options[:or]
|
144
157
|
|
145
|
-
return Kind::Of::Boolean if
|
158
|
+
return Kind::Of::Boolean if Undefined == object && default.nil?
|
146
159
|
|
147
160
|
bool = object.nil? ? default : object
|
148
161
|
|
@@ -164,21 +177,21 @@ module Kind
|
|
164
177
|
if ::Kind::Maybe::Value.none?(default)
|
165
178
|
__kind_undefined(value) { Kind::Of::Boolean(value) }
|
166
179
|
else
|
167
|
-
return value if instance?(value)
|
180
|
+
return value if Kind::Undefined != value && instance?(value)
|
168
181
|
|
169
182
|
__kind_undefined(default) { Kind::Of::Boolean(default) }
|
170
183
|
end
|
171
184
|
end
|
172
185
|
|
173
186
|
def self.__kind_undefined(value)
|
174
|
-
if
|
187
|
+
if Kind::Undefined == value
|
175
188
|
raise Kind::Error.new('Boolean'.freeze, Kind::Undefined)
|
176
189
|
else
|
177
190
|
yield
|
178
191
|
end
|
179
192
|
end
|
180
193
|
|
181
|
-
def self.
|
194
|
+
def self.__is_instance__(value);
|
182
195
|
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
183
196
|
end
|
184
197
|
|
@@ -188,12 +201,16 @@ module Kind
|
|
188
201
|
end
|
189
202
|
end)
|
190
203
|
|
204
|
+
def self.Boolean?(*args)
|
205
|
+
Kind::Of::Boolean.instance?(*args)
|
206
|
+
end
|
207
|
+
|
191
208
|
# -- Lambda
|
192
209
|
|
193
210
|
def self.Lambda(object = Undefined, options = Empty::HASH)
|
194
211
|
default = options[:or]
|
195
212
|
|
196
|
-
return Kind::Of::Lambda if
|
213
|
+
return Kind::Of::Lambda if Undefined == object && default.nil?
|
197
214
|
|
198
215
|
func = object || default
|
199
216
|
|
@@ -213,31 +230,35 @@ module Kind
|
|
213
230
|
if ::Kind::Maybe::Value.none?(default)
|
214
231
|
__kind_undefined(value) { Kind::Of::Lambda(value) }
|
215
232
|
else
|
216
|
-
return value if instance?(value)
|
233
|
+
return value if Kind::Undefined != value && instance?(value)
|
217
234
|
|
218
235
|
__kind_undefined(default) { Kind::Of::Lambda(default) }
|
219
236
|
end
|
220
237
|
end
|
221
238
|
|
222
239
|
def self.__kind_undefined(value)
|
223
|
-
if
|
240
|
+
if Kind::Undefined == value
|
224
241
|
raise Kind::Error.new('Lambda'.freeze, Kind::Undefined)
|
225
242
|
else
|
226
243
|
yield
|
227
244
|
end
|
228
245
|
end
|
229
246
|
|
230
|
-
def self.
|
247
|
+
def self.__is_instance__(value)
|
231
248
|
value.is_a?(__kind) && value.lambda?
|
232
249
|
end
|
233
250
|
end)
|
234
251
|
|
252
|
+
def self.Lambda?(*args)
|
253
|
+
Kind::Of::Lambda.instance?(*args)
|
254
|
+
end
|
255
|
+
|
235
256
|
# -- Callable
|
236
257
|
|
237
258
|
def self.Callable(object = Undefined, options = Empty::HASH)
|
238
259
|
default = options[:or]
|
239
260
|
|
240
|
-
return Kind::Of::Callable if
|
261
|
+
return Kind::Of::Callable if Undefined == object && default.nil?
|
241
262
|
|
242
263
|
callable = object || default
|
243
264
|
|
@@ -261,25 +282,29 @@ module Kind
|
|
261
282
|
if ::Kind::Maybe::Value.none?(default)
|
262
283
|
__kind_undefined(value) { Kind::Of::Callable(value) }
|
263
284
|
else
|
264
|
-
return value if instance?(value)
|
285
|
+
return value if Kind::Undefined != value && instance?(value)
|
265
286
|
|
266
287
|
__kind_undefined(default) { Kind::Of::Callable(default) }
|
267
288
|
end
|
268
289
|
end
|
269
290
|
|
270
291
|
def self.__kind_undefined(value)
|
271
|
-
if
|
292
|
+
if Kind::Undefined == value
|
272
293
|
raise Kind::Error.new('Callable'.freeze, Kind::Undefined)
|
273
294
|
else
|
274
295
|
yield
|
275
296
|
end
|
276
297
|
end
|
277
298
|
|
278
|
-
def self.
|
299
|
+
def self.__is_instance__(value);
|
279
300
|
value.respond_to?(:call)
|
280
301
|
end
|
281
302
|
end)
|
282
303
|
|
304
|
+
def self.Callable?(*args)
|
305
|
+
Kind::Of::Callable.instance?(*args)
|
306
|
+
end
|
307
|
+
|
283
308
|
# ---------------------- #
|
284
309
|
# Built-in type checkers #
|
285
310
|
# ---------------------- #
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class KindValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
return if options[:allow_nil] && value.nil?
|
6
|
+
|
7
|
+
return unless error = call_validation_for(attribute, value)
|
8
|
+
|
9
|
+
raise Kind::Error.new("#{attribute} #{error}") if options[:strict]
|
10
|
+
|
11
|
+
record.errors.add(attribute, error)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def call_validation_for(attribute, value)
|
17
|
+
expected = options[:with] || options[:in]
|
18
|
+
|
19
|
+
return validate_with_default_strategy(expected, value) if expected
|
20
|
+
|
21
|
+
return kind_of(expected, value) if expected = options[:of]
|
22
|
+
return kind_is(expected, value) if expected = options[:is]
|
23
|
+
return respond_to(expected, value) if expected = options[:respond_to]
|
24
|
+
return instance_of(expected, value) if expected = options[:instance_of]
|
25
|
+
return array_with(expected, value) if expected = options[:array_with]
|
26
|
+
return array_of(expected, value) if expected = options[:array_of]
|
27
|
+
|
28
|
+
raise Kind::Validator::InvalidDefinition.new(attribute)
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_with_default_strategy(expected, value)
|
32
|
+
send(Kind::Validator.default_strategy, expected, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def kind_of(expected, value)
|
36
|
+
types = Array(expected)
|
37
|
+
|
38
|
+
return if types.any? { |type| value.kind_of?(type) }
|
39
|
+
|
40
|
+
"must be a kind of: #{types.map { |klass| klass.name }.join(', ')}"
|
41
|
+
end
|
42
|
+
|
43
|
+
CLASS_OR_MODULE = 'Class/Module'.freeze
|
44
|
+
|
45
|
+
def kind_is(expected, value)
|
46
|
+
return kind_is_not(expected, value) unless expected.kind_of?(Array)
|
47
|
+
|
48
|
+
result = expected.map { |kind| kind_is_not(kind, value) }.compact
|
49
|
+
|
50
|
+
result.empty? || result.size < expected.size ? nil : result.join(', ')
|
51
|
+
end
|
52
|
+
|
53
|
+
def kind_is_not(expected, value)
|
54
|
+
case expected
|
55
|
+
when Class
|
56
|
+
return if expected == Kind.of.Class(value) || value < expected
|
57
|
+
|
58
|
+
"must be the class or a subclass of `#{expected.name}`"
|
59
|
+
when Module
|
60
|
+
return if value.kind_of?(Class) && value <= expected
|
61
|
+
return if expected == Kind.of.Module(value) || value.kind_of?(expected)
|
62
|
+
|
63
|
+
"must include the `#{expected.name}` module"
|
64
|
+
else
|
65
|
+
raise Kind::Error.new(CLASS_OR_MODULE, expected)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to(method_name, value)
|
70
|
+
return if value.respond_to?(method_name)
|
71
|
+
|
72
|
+
"must respond to the method `#{method_name}`"
|
73
|
+
end
|
74
|
+
|
75
|
+
def instance_of(expected, value)
|
76
|
+
types = Array(expected)
|
77
|
+
|
78
|
+
return if types.any? { |type| value.instance_of?(type) }
|
79
|
+
|
80
|
+
"must be an instance of: #{types.map { |klass| klass.name }.join(', ')}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def array_with(expected, value)
|
84
|
+
return if value.kind_of?(Array) && !value.empty? && (value - Kind.of.Array(expected)).empty?
|
85
|
+
|
86
|
+
"must be an array with: #{expected.join(', ')}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def array_of(expected, value)
|
90
|
+
types = Array(expected)
|
91
|
+
|
92
|
+
return if value.kind_of?(Array) && !value.empty? && value.all? { |value| types.any? { |type| value.kind_of?(type) } }
|
93
|
+
|
94
|
+
"must be an array of: #{types.map { |klass| klass.name }.join(', ')}"
|
95
|
+
end
|
96
|
+
end
|
data/lib/kind/checker.rb
CHANGED
@@ -11,15 +11,34 @@ module Kind
|
|
11
11
|
|
12
12
|
return Kind::Of.(__kind, value) if ::Kind::Maybe::Value.none?(default)
|
13
13
|
|
14
|
-
instance?(value) ? value : Kind::Of.(__kind, default)
|
14
|
+
Kind::Undefined != value && instance?(value) ? value : Kind::Of.(__kind, default)
|
15
15
|
end
|
16
16
|
|
17
17
|
def [](value, options = options = Empty::HASH)
|
18
18
|
instance(value, options)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def to_proc
|
22
|
+
@to_proc ||=
|
23
|
+
-> checker { -> value { checker.instance(value) } }.call(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def __is_instance__(value)
|
27
|
+
value.kind_of?(__kind)
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_instance_to_proc
|
31
|
+
@is_instance_to_proc ||=
|
32
|
+
-> checker { -> value { checker.__is_instance__(value) } }.call(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def instance?(*args)
|
36
|
+
return is_instance_to_proc if args.empty?
|
37
|
+
|
38
|
+
return args.all? { |object| __is_instance__(object) } if args.size > 1
|
39
|
+
|
40
|
+
arg = args[0]
|
41
|
+
Kind::Undefined == arg ? is_instance_to_proc : __is_instance__(arg)
|
23
42
|
end
|
24
43
|
|
25
44
|
def or_nil(value)
|
@@ -29,6 +48,25 @@ module Kind
|
|
29
48
|
def or_undefined(value)
|
30
49
|
or_nil(value) || Kind::Undefined
|
31
50
|
end
|
51
|
+
|
52
|
+
def __as_maybe__(value)
|
53
|
+
Kind::Maybe.new(or_nil(value))
|
54
|
+
end
|
55
|
+
|
56
|
+
def as_maybe_to_proc
|
57
|
+
@as_maybe_to_proc ||=
|
58
|
+
-> checker { -> value { checker.__as_maybe__(value) } }.call(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def as_maybe(value = Kind::Undefined)
|
62
|
+
return __as_maybe__(value) if Kind::Undefined != value
|
63
|
+
|
64
|
+
as_maybe_to_proc
|
65
|
+
end
|
66
|
+
|
67
|
+
def as_optional(value = Kind::Undefined)
|
68
|
+
as_maybe(value)
|
69
|
+
end
|
32
70
|
end
|
33
71
|
|
34
72
|
private_constant :Checkable
|
data/lib/kind/error.rb
CHANGED
@@ -2,8 +2,18 @@
|
|
2
2
|
|
3
3
|
module Kind
|
4
4
|
class Error < TypeError
|
5
|
-
|
6
|
-
|
5
|
+
UNDEFINED_OBJECT = Object.new
|
6
|
+
|
7
|
+
private_constant :UNDEFINED_OBJECT
|
8
|
+
|
9
|
+
def initialize(arg, object = UNDEFINED_OBJECT)
|
10
|
+
if UNDEFINED_OBJECT == object
|
11
|
+
# Will be used when the exception was raised with a message. e.g:
|
12
|
+
# raise Kind::Error, "some message"
|
13
|
+
super(arg)
|
14
|
+
else
|
15
|
+
super("#{object.inspect} expected to be a kind of #{arg}")
|
16
|
+
end
|
7
17
|
end
|
8
18
|
end
|
9
19
|
end
|