kind 2.1.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/README.md +315 -143
- data/lib/kind.rb +28 -44
- data/lib/kind/active_model/kind_validator.rb +2 -2
- data/lib/kind/checker.rb +3 -71
- data/lib/kind/checker/factory.rb +35 -0
- data/lib/kind/checker/protocol.rb +73 -0
- data/lib/kind/dig.rb +36 -0
- data/lib/kind/error.rb +1 -1
- data/lib/kind/maybe.rb +74 -16
- data/lib/kind/types.rb +3 -3
- data/lib/kind/undefined.rb +1 -1
- data/lib/kind/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b62da87ad8443fb4d3f1d96535bc69259addf767721db70288401d677733967
|
4
|
+
data.tar.gz: 995f0a3a34ed67a3244e181380d627e076d4576a2b6e0c7de2b34fa415c5f868
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3398bbf2b67fe8b81023d9c781b14ac115f5749f45641a74fc1ff1c8d05f4fd1c4e2a4bc817d6c71e7f8dc96b245ec9f9c80a776ae11d22ba69dbc878dfaa847
|
7
|
+
data.tar.gz: 75c6c1e387548447edf601d4b73b18ff3e2700debf3f6715b2f772f582829d39a72e7bb9d8af61fe8bcd32e9f72aa8398f1703f2c706668a0ab5e59fc0612b05
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -31,22 +31,25 @@ One of the goals of this project is to do simple type checking like `"some strin
|
|
31
31
|
- [Classes' type checkers](#classes-type-checkers)
|
32
32
|
- [Modules' type checkers](#modules-type-checkers)
|
33
33
|
- [Specials' type checkers](#specials-type-checkers)
|
34
|
+
- [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
|
35
|
+
- [Usage](#usage-1)
|
36
|
+
- [Defining the default validation strategy](#defining-the-default-validation-strategy)
|
37
|
+
- [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
|
34
38
|
- [Kind::Undefined](#kindundefined)
|
35
39
|
- [Kind.of.\<Type\>.or_undefined()](#kindoftypeor_undefined)
|
36
40
|
- [Kind::Maybe](#kindmaybe)
|
37
41
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)
|
38
|
-
- [Kind::Maybe[] and Kind::Maybe#then method aliases](#kindmaybe-and-kindmaybethen-method-aliases)
|
42
|
+
- [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)
|
39
43
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)
|
40
44
|
- [Kind::None() and Kind::Some()](#kindnone-and-kindsome)
|
41
45
|
- [Kind.of.Maybe()](#kindofmaybe)
|
42
46
|
- [Kind::Optional](#kindoptional)
|
43
47
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
|
44
48
|
- [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
49
|
+
- [Kind::Maybe(<Type>)](#kindmaybetype)
|
45
50
|
- [Kind::Maybe#try](#kindmaybetry)
|
46
|
-
- [Kind::
|
47
|
-
- [
|
48
|
-
- [Defining the default validation strategy](#defining-the-default-validation-strategy)
|
49
|
-
- [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
|
51
|
+
- [Kind::Maybe#try!](#kindmaybetry-1)
|
52
|
+
- [Kind::Maybe#dig](#kindmaybedig)
|
50
53
|
- [Kind::Empty](#kindempty)
|
51
54
|
- [Similar Projects](#similar-projects)
|
52
55
|
- [Development](#development)
|
@@ -163,13 +166,19 @@ Kind.of.Boolean.or_nil(true) # true
|
|
163
166
|
Use the method `.instance?` to verify if the given object has the expected type.
|
164
167
|
|
165
168
|
```ruby
|
166
|
-
Kind.of.Hash.instance?(
|
169
|
+
Kind.of.Hash.instance?({}) # true
|
170
|
+
Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
|
171
|
+
|
172
|
+
Kind.of.Hash.instance?('') # false
|
173
|
+
Kind.of.Hash.instance?({}, '') # false
|
167
174
|
|
168
175
|
# ---
|
169
176
|
|
170
|
-
Kind.of.Boolean.instance?(
|
171
|
-
Kind.of.Boolean.instance?(true)
|
172
|
-
|
177
|
+
Kind.of.Boolean.instance?(true) # true
|
178
|
+
Kind.of.Boolean.instance?(true, false) # true
|
179
|
+
|
180
|
+
Kind.of.Boolean.instance?(nil) # false
|
181
|
+
Kind.of.Boolean.instance?(false, true, nil) # false
|
173
182
|
```
|
174
183
|
|
175
184
|
> **Note:** When `.instance?` is called without an argument,
|
@@ -186,6 +195,22 @@ collection
|
|
186
195
|
> To do this, use Kind.of.\<Type\>?()
|
187
196
|
|
188
197
|
```ruby
|
198
|
+
Kind.of.Hash?({}) # true
|
199
|
+
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
200
|
+
|
201
|
+
Kind.of.Hash?('') # false
|
202
|
+
Kind.of.Hash?({}, '') # false
|
203
|
+
|
204
|
+
# ---
|
205
|
+
|
206
|
+
Kind.of.Boolean?(true) # true
|
207
|
+
Kind.of.Boolean?(false, true) # true
|
208
|
+
|
209
|
+
Kind.of.Boolean?(nil) # false
|
210
|
+
Kind.of.Boolean?(false, true, nil) # false
|
211
|
+
|
212
|
+
# ---
|
213
|
+
|
189
214
|
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
190
215
|
|
191
216
|
collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
|
@@ -434,6 +459,7 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
|
|
434
459
|
- `Kind.of.Range`
|
435
460
|
- `Kind.of.Hash`
|
436
461
|
- `Kind.of.Struct`
|
462
|
+
- `Kind.of.OpenStruct`
|
437
463
|
- `Kind.of.Enumerator`
|
438
464
|
- `Kind.of.Set`
|
439
465
|
- `Kind.of.Method`
|
@@ -459,6 +485,158 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
|
|
459
485
|
|
460
486
|
[⬆️ Back to Top](#table-of-contents-)
|
461
487
|
|
488
|
+
## Kind::Validator (ActiveModel::Validations)
|
489
|
+
|
490
|
+
This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, < 6.1.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
|
491
|
+
|
492
|
+
```ruby
|
493
|
+
class Person
|
494
|
+
include ActiveModel::Validations
|
495
|
+
|
496
|
+
attr_accessor :first_name, :last_name
|
497
|
+
|
498
|
+
validates :first_name, :last_name, kind: String
|
499
|
+
end
|
500
|
+
```
|
501
|
+
|
502
|
+
And to make use of it, you will need to do an explicitly require. e.g:
|
503
|
+
|
504
|
+
```ruby
|
505
|
+
# In some Gemfile
|
506
|
+
gem 'kind', require: 'kind/active_model/validation'
|
507
|
+
|
508
|
+
# In some .rb file
|
509
|
+
require 'kind/active_model/validation'
|
510
|
+
```
|
511
|
+
|
512
|
+
### Usage
|
513
|
+
|
514
|
+
**[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
validates :name, kind: { of: String }
|
518
|
+
|
519
|
+
# Use an array to verify if the attribute
|
520
|
+
# is an instance of one of the classes/modules.
|
521
|
+
|
522
|
+
validates :status, kind: { of: [String, Symbol]}
|
523
|
+
```
|
524
|
+
|
525
|
+
**[Kind.is](#verifying-the-kind-of-some-classmodule)**
|
526
|
+
|
527
|
+
```ruby
|
528
|
+
#
|
529
|
+
# Verifying if the attribute value is the class or a subclass.
|
530
|
+
#
|
531
|
+
class Human; end
|
532
|
+
class Person < Human; end
|
533
|
+
class User < Human; end
|
534
|
+
|
535
|
+
validates :human_kind, kind: { is: Human }
|
536
|
+
|
537
|
+
#
|
538
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
539
|
+
#
|
540
|
+
module Human; end
|
541
|
+
class Person; include Human; end
|
542
|
+
class User; include Human; end
|
543
|
+
|
544
|
+
validates :human_kind, kind: { is: Human }
|
545
|
+
|
546
|
+
#
|
547
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
548
|
+
#
|
549
|
+
module Human; end
|
550
|
+
module Person; extend Human; end
|
551
|
+
module User; extend Human; end
|
552
|
+
|
553
|
+
validates :human_kind, kind: { is: Human }
|
554
|
+
|
555
|
+
# or use an array to verify if the attribute
|
556
|
+
# is a kind of one those classes/modules.
|
557
|
+
|
558
|
+
validates :human_kind, kind: { is: [Person, User] }
|
559
|
+
```
|
560
|
+
|
561
|
+
**[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
|
562
|
+
|
563
|
+
```ruby
|
564
|
+
validates :name, kind: { instance_of: String }
|
565
|
+
|
566
|
+
# or use an array to verify if the attribute
|
567
|
+
# is an instance of one of the classes/modules.
|
568
|
+
|
569
|
+
validates :name, kind: { instance_of: [String, Symbol] }
|
570
|
+
```
|
571
|
+
|
572
|
+
|
573
|
+
**[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
|
574
|
+
|
575
|
+
```ruby
|
576
|
+
validates :handler, kind: { respond_to: :call }
|
577
|
+
```
|
578
|
+
|
579
|
+
**Array.new.all? { |item| item.kind_of?(Class) }**
|
580
|
+
|
581
|
+
```ruby
|
582
|
+
validates :account_types, kind: { array_of: String }
|
583
|
+
|
584
|
+
# or use an array to verify if the attribute
|
585
|
+
# is an instance of one of the classes
|
586
|
+
|
587
|
+
validates :account_types, kind: { array_of: [String, Symbol] }
|
588
|
+
```
|
589
|
+
|
590
|
+
**Array.new.all? { |item| expected_values.include?(item) }**
|
591
|
+
|
592
|
+
```ruby
|
593
|
+
# Verifies if the attribute value
|
594
|
+
# is an array with some or all the expected values.
|
595
|
+
|
596
|
+
validates :account_types, kind: { array_with: ['foo', 'bar'] }
|
597
|
+
```
|
598
|
+
|
599
|
+
#### Defining the default validation strategy
|
600
|
+
|
601
|
+
By default, you can define the attribute type directly (without a hash). e.g.
|
602
|
+
|
603
|
+
```ruby
|
604
|
+
validates :name, kind: String
|
605
|
+
# or
|
606
|
+
validates :name, kind: [String, Symbol]
|
607
|
+
```
|
608
|
+
|
609
|
+
To changes this behavior you can set another strategy to validates the attributes types:
|
610
|
+
|
611
|
+
```ruby
|
612
|
+
Kind::Validator.default_strategy = :instance_of
|
613
|
+
|
614
|
+
# Tip: Create an initializer if you are in a Rails application.
|
615
|
+
```
|
616
|
+
|
617
|
+
And these are the available options to define the default strategy:
|
618
|
+
- `kind_of` *(default)*
|
619
|
+
- `instance_of`
|
620
|
+
|
621
|
+
#### Using the `allow_nil` and `strict` options
|
622
|
+
|
623
|
+
You can use the `allow_nil` option with any of the kind validations. e.g.
|
624
|
+
|
625
|
+
```ruby
|
626
|
+
validates :name, kind: String, allow_nil: true
|
627
|
+
```
|
628
|
+
|
629
|
+
And as any active model validation, kind validations works with the `strict: true`
|
630
|
+
option and with the `validates!` method. e.g.
|
631
|
+
|
632
|
+
```ruby
|
633
|
+
validates :first_name, kind: String, strict: true
|
634
|
+
# or
|
635
|
+
validates! :last_name, kind: String
|
636
|
+
```
|
637
|
+
|
638
|
+
[⬆️ Back to Top](#table-of-contents-)
|
639
|
+
|
462
640
|
## Kind::Undefined
|
463
641
|
|
464
642
|
The [`Kind::Undefined`](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind/undefined.rb) constant is used as the default argument of type checkers. This is necessary [to know if no arguments were passed to the type check methods](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind.rb#L45-L48). But, you can use it in your codebase too, especially if you need to distinguish the usage of `nil` as a method argument.
|
@@ -543,7 +721,7 @@ end
|
|
543
721
|
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
544
722
|
```
|
545
723
|
|
546
|
-
### Kind::Maybe[] and Kind::Maybe#then method aliases
|
724
|
+
### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
|
547
725
|
|
548
726
|
You can use `Kind::Maybe[]` (brackets) instead of the `.new` to transform values in a `Kind::Maybe`. Another alias is `.then` to the `.map` method.
|
549
727
|
|
@@ -557,6 +735,19 @@ result =
|
|
557
735
|
puts result # 42
|
558
736
|
```
|
559
737
|
|
738
|
+
You can also use `Kind::Maybe.wrap()` instead of the `.new` method.
|
739
|
+
|
740
|
+
```ruby
|
741
|
+
result =
|
742
|
+
Kind::Maybe
|
743
|
+
.wrap(5)
|
744
|
+
.then { |value| value * 5 }
|
745
|
+
.then { |value| value + 17 }
|
746
|
+
.value_or(0)
|
747
|
+
|
748
|
+
puts result # 42
|
749
|
+
```
|
750
|
+
|
560
751
|
#### Replacing blocks by lambdas
|
561
752
|
|
562
753
|
```ruby
|
@@ -586,7 +777,7 @@ you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
|
586
777
|
Add = -> params do
|
587
778
|
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
588
779
|
|
589
|
-
return Kind::None unless Kind.of.Numeric
|
780
|
+
return Kind::None unless Kind.of.Numeric?(a, b)
|
590
781
|
|
591
782
|
Kind::Some(a + b)
|
592
783
|
end
|
@@ -695,10 +886,11 @@ In these scenarios, you could check the given input type as optional and avoid u
|
|
695
886
|
|
696
887
|
```ruby
|
697
888
|
def person_name(params)
|
698
|
-
Kind::Of::Hash
|
699
|
-
|
700
|
-
|
701
|
-
|
889
|
+
Kind::Of::Hash
|
890
|
+
.as_optional(params)
|
891
|
+
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
892
|
+
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
893
|
+
.value_or { 'John Doe' }
|
702
894
|
end
|
703
895
|
|
704
896
|
person_name('') # "John Doe"
|
@@ -719,9 +911,20 @@ def person_name(params)
|
|
719
911
|
'John Doe'
|
720
912
|
end
|
721
913
|
end
|
914
|
+
|
915
|
+
#
|
916
|
+
# You can also use Kind::Optional(<Type>) to achieve the same behavior
|
917
|
+
#
|
918
|
+
def person_name(params)
|
919
|
+
Kind::Optional(Hash)
|
920
|
+
.wrap(params)
|
921
|
+
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
922
|
+
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
923
|
+
.value_or { 'John Doe' }
|
924
|
+
end
|
722
925
|
```
|
723
926
|
|
724
|
-
> Note: You could use the `.as_optional` method (or it alias
|
927
|
+
> Note: You could use the `.as_optional` method (or it alias `.as_maybe`) with any [type checker](https://github.com/serradura/kind/blob/b177fed9cc2b3347d63963a2a2fd99f989c51a9a/README.md#type-checkers).
|
725
928
|
|
726
929
|
Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
|
727
930
|
|
@@ -795,193 +998,162 @@ end
|
|
795
998
|
|
796
999
|
[⬆️ Back to Top](#table-of-contents-)
|
797
1000
|
|
798
|
-
### Kind::Maybe
|
1001
|
+
### Kind::Maybe(<Type>)
|
799
1002
|
|
800
|
-
|
1003
|
+
There is an alternative to `Kind.of.\<Type\>.as_optional`, you can use `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
|
801
1004
|
|
802
1005
|
```ruby
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
p Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
|
810
|
-
|
811
|
-
p Kind::Maybe[object].try { |value| value.upcase } # "FOO"
|
1006
|
+
result1 =
|
1007
|
+
Kind::Maybe(Numeric)
|
1008
|
+
.wrap(5)
|
1009
|
+
.then { |value| value * 5 }
|
1010
|
+
.value_or { 0 }
|
812
1011
|
|
813
|
-
|
814
|
-
# Nil value #
|
815
|
-
#############
|
1012
|
+
puts result1 # 25
|
816
1013
|
|
817
|
-
|
1014
|
+
# ---
|
818
1015
|
|
819
|
-
|
1016
|
+
result2 =
|
1017
|
+
Kind::Optional(Numeric)
|
1018
|
+
.wrap('5')
|
1019
|
+
.then { |value| value * 5 }
|
1020
|
+
.value_or { 0 }
|
820
1021
|
|
821
|
-
|
1022
|
+
puts result2 # 0
|
1023
|
+
```
|
822
1024
|
|
823
|
-
|
824
|
-
# Kind::Undefined value #
|
825
|
-
#########################
|
1025
|
+
This typed maybe has the same methods of `Kind::Maybe` class. e.g:
|
826
1026
|
|
827
|
-
|
1027
|
+
```ruby
|
1028
|
+
Kind::Maybe(Numeric)[5]
|
1029
|
+
Kind::Maybe(Numeric).new(5)
|
1030
|
+
Kind::Maybe(Numeric).wrap(5)
|
828
1031
|
|
829
|
-
|
1032
|
+
# ---
|
830
1033
|
|
831
|
-
|
1034
|
+
Kind::Optional(Numeric)[5]
|
1035
|
+
Kind::Optional(Numeric).new(5)
|
1036
|
+
Kind::Optional(Numeric).wrap(5)
|
832
1037
|
```
|
833
1038
|
|
834
1039
|
[⬆️ Back to Top](#table-of-contents-)
|
835
1040
|
|
836
|
-
|
1041
|
+
### Kind::Maybe#try
|
837
1042
|
|
838
|
-
|
1043
|
+
If you don't want to use `#map/#then` to access the value, you could use the `#try` method to access it. So, if the value wasn't `nil` or `Kind::Undefined`, the some monad will be returned.
|
839
1044
|
|
840
1045
|
```ruby
|
841
|
-
|
842
|
-
include ActiveModel::Validations
|
843
|
-
|
844
|
-
attr_accessor :first_name, :last_name
|
845
|
-
|
846
|
-
validates :first_name, :last_name, kind: String
|
847
|
-
end
|
848
|
-
```
|
1046
|
+
object = 'foo'
|
849
1047
|
|
850
|
-
|
1048
|
+
Kind::Maybe[object].try(:upcase).value # "FOO"
|
851
1049
|
|
852
|
-
|
853
|
-
# In some Gemfile
|
854
|
-
gem 'kind', require: 'kind/active_model/validation'
|
1050
|
+
Kind::Maybe[{}].try(:fetch, :number, 0).value # 0
|
855
1051
|
|
856
|
-
|
857
|
-
require 'kind/active_model/validation'
|
858
|
-
```
|
1052
|
+
Kind::Maybe[{number: 1}].try(:fetch, :number).value # 1
|
859
1053
|
|
860
|
-
|
1054
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # "FOO"
|
861
1055
|
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
validates :name, kind: { of: String }
|
1056
|
+
#############
|
1057
|
+
# Nil value #
|
1058
|
+
#############
|
866
1059
|
|
867
|
-
|
868
|
-
# is an instance of one of the classes/modules.
|
1060
|
+
object = nil
|
869
1061
|
|
870
|
-
|
871
|
-
```
|
1062
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
872
1063
|
|
873
|
-
|
1064
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
874
1065
|
|
875
|
-
|
876
|
-
#
|
877
|
-
|
878
|
-
#
|
879
|
-
class Human; end
|
880
|
-
class Person < Human; end
|
881
|
-
class User < Human; end
|
882
|
-
|
883
|
-
validates :human_kind, kind: { is: Human }
|
1066
|
+
#########################
|
1067
|
+
# Kind::Undefined value #
|
1068
|
+
#########################
|
884
1069
|
|
885
|
-
|
886
|
-
# Verifying if the attribute value is the module or if it is a class that includes the module
|
887
|
-
#
|
888
|
-
module Human; end
|
889
|
-
class Person; include Human; end
|
890
|
-
class User; include Human; end
|
1070
|
+
object = Kind::Undefined
|
891
1071
|
|
892
|
-
|
1072
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
893
1073
|
|
894
|
-
#
|
895
|
-
|
896
|
-
#
|
897
|
-
module Human; end
|
898
|
-
module Person; extend Human; end
|
899
|
-
module User; extend Human; end
|
1074
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
1075
|
+
```
|
900
1076
|
|
901
|
-
|
1077
|
+
> **Note:** You can use the `#try` method with `Kind::Optional` objects.
|
902
1078
|
|
903
|
-
|
904
|
-
# is a kind of one those classes/modules.
|
1079
|
+
[⬆️ Back to Top](#table-of-contents-)
|
905
1080
|
|
906
|
-
|
907
|
-
```
|
1081
|
+
### Kind::Maybe#try!
|
908
1082
|
|
909
|
-
|
1083
|
+
Has the same behavior of its `#try`, but it will raise an error if the value doesn't respond to the expected method.
|
910
1084
|
|
911
1085
|
```ruby
|
912
|
-
|
913
|
-
|
914
|
-
# or use an array to verify if the attribute
|
915
|
-
# is an instance of one of the classes/modules.
|
1086
|
+
Kind::Maybe[{}].try(:upcase) # => #<Kind::Maybe::None:0x0000... @value=nil>
|
916
1087
|
|
917
|
-
|
1088
|
+
Kind::Maybe[{}].try!(:upcase) # => NoMethodError (undefined method `upcase' for {}:Hash)
|
918
1089
|
```
|
919
1090
|
|
1091
|
+
> **Note:** You can also use the `#try!` method with `Kind::Optional` objects.
|
920
1092
|
|
921
|
-
|
1093
|
+
[⬆️ Back to Top](#table-of-contents-)
|
922
1094
|
|
923
|
-
|
924
|
-
validates :handler, kind: { respond_to: :call }
|
925
|
-
```
|
1095
|
+
### Kind::Maybe#dig
|
926
1096
|
|
927
|
-
|
1097
|
+
Has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some value can't be digged.
|
928
1098
|
|
929
1099
|
```ruby
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
# is an instance of one of the classes
|
1100
|
+
[nil, 1, '', /x/].each do |value|
|
1101
|
+
p Kind::Maybe[value].dig(:foo).value # nil
|
1102
|
+
end
|
934
1103
|
|
935
|
-
|
936
|
-
```
|
1104
|
+
# --
|
937
1105
|
|
938
|
-
|
1106
|
+
a = [1, 2, 3]
|
939
1107
|
|
940
|
-
|
941
|
-
# Verifies if the attribute value
|
942
|
-
# is an array with some or all the expected values.
|
1108
|
+
Kind::Maybe[a].dig(0).value # 1
|
943
1109
|
|
944
|
-
|
945
|
-
```
|
1110
|
+
Kind::Maybe[a].dig(3).value # nil
|
946
1111
|
|
947
|
-
|
1112
|
+
# --
|
948
1113
|
|
949
|
-
|
1114
|
+
h = { foo: {bar: {baz: 1}}}
|
950
1115
|
|
951
|
-
|
952
|
-
|
953
|
-
#
|
954
|
-
validates :name, kind: [String, Symbol]
|
955
|
-
```
|
1116
|
+
Kind::Maybe[h].dig(:foo).value # {bar: {baz: 1}}
|
1117
|
+
Kind::Maybe[h].dig(:foo, :bar).value # {baz: 1}
|
1118
|
+
Kind::Maybe[h].dig(:foo, :bar, :baz).value # 1
|
956
1119
|
|
957
|
-
|
1120
|
+
Kind::Maybe[h].dig(:foo, :bar, 'baz').value # nil
|
958
1121
|
|
959
|
-
|
960
|
-
Kind::Validator.default_strategy = :instance_of
|
1122
|
+
# --
|
961
1123
|
|
962
|
-
|
963
|
-
```
|
1124
|
+
i = { foo: [{'bar' => [1, 2]}, {baz: [3, 4]}] }
|
964
1125
|
|
965
|
-
|
966
|
-
|
967
|
-
-
|
1126
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 0).value # 1
|
1127
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 1).value # 2
|
1128
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', -1).value # 2
|
968
1129
|
|
969
|
-
|
1130
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 2).value # nil
|
970
1131
|
|
971
|
-
|
1132
|
+
# --
|
972
1133
|
|
973
|
-
|
974
|
-
|
1134
|
+
s = Struct.new(:a, :b).new(101, 102)
|
1135
|
+
o = OpenStruct.new(c: 103, d: 104)
|
1136
|
+
b = { struct: s, ostruct: o, data: [s, o]}
|
1137
|
+
|
1138
|
+
Kind::Maybe[s].dig(:a).value # 101
|
1139
|
+
Kind::Maybe[b].dig(:struct, :b).value # 102
|
1140
|
+
Kind::Maybe[b].dig(:data, 0, :b).value # 102
|
1141
|
+
Kind::Maybe[b].dig(:data, 0, 'b').value # 102
|
1142
|
+
|
1143
|
+
Kind::Maybe[o].dig(:c).value # 103
|
1144
|
+
Kind::Maybe[b].dig(:ostruct, :d).value # 104
|
1145
|
+
Kind::Maybe[b].dig(:data, 1, :d).value # 104
|
1146
|
+
Kind::Maybe[b].dig(:data, 1, 'd').value # 104
|
1147
|
+
|
1148
|
+
Kind::Maybe[s].dig(:f).value # nil
|
1149
|
+
Kind::Maybe[o].dig(:f).value # nil
|
1150
|
+
Kind::Maybe[b].dig(:struct, :f).value # nil
|
1151
|
+
Kind::Maybe[b].dig(:ostruct, :f).value # nil
|
1152
|
+
Kind::Maybe[b].dig(:data, 0, :f).value # nil
|
1153
|
+
Kind::Maybe[b].dig(:data, 1, :f).value # nil
|
975
1154
|
```
|
976
1155
|
|
977
|
-
|
978
|
-
option and with the `validates!` method. e.g.
|
979
|
-
|
980
|
-
```ruby
|
981
|
-
validates :first_name, kind: String, strict: true
|
982
|
-
# or
|
983
|
-
validates! :last_name, kind: String
|
984
|
-
```
|
1156
|
+
> **Note:** You can also use the `#dig` method with `Kind::Optional` objects.
|
985
1157
|
|
986
1158
|
[⬆️ Back to Top](#table-of-contents-)
|
987
1159
|
|
data/lib/kind.rb
CHANGED
@@ -2,14 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'kind/version'
|
4
4
|
|
5
|
+
require 'ostruct'
|
6
|
+
|
5
7
|
require 'kind/empty'
|
6
8
|
require 'kind/undefined'
|
9
|
+
require 'kind/checker'
|
7
10
|
require 'kind/maybe'
|
8
11
|
|
9
12
|
require 'kind/error'
|
10
13
|
require 'kind/of'
|
11
14
|
require 'kind/is'
|
12
|
-
require 'kind/checker'
|
13
15
|
require 'kind/types'
|
14
16
|
|
15
17
|
module Kind
|
@@ -18,37 +20,19 @@ module Kind
|
|
18
20
|
private_constant :WRONG_NUMBER_OF_ARGUMENTS
|
19
21
|
|
20
22
|
def self.is(expected = Undefined, object = Undefined)
|
21
|
-
return Is if
|
23
|
+
return Is if Undefined == expected && Undefined == object
|
22
24
|
|
23
|
-
return Kind::Is.(expected, object) if
|
25
|
+
return Kind::Is.(expected, object) if Undefined != object
|
24
26
|
|
25
27
|
raise ArgumentError, WRONG_NUMBER_OF_ARGUMENTS
|
26
28
|
end
|
27
29
|
|
28
|
-
MODULE_OR_CLASS = 'Module/Class'.freeze
|
29
|
-
|
30
|
-
private_constant :MODULE_OR_CLASS
|
31
|
-
|
32
|
-
private_class_method def self.__checkers__
|
33
|
-
@__checkers__ ||= {}
|
34
|
-
end
|
35
|
-
|
36
|
-
__checkers__
|
37
|
-
|
38
30
|
def self.of(kind = Undefined, object = Undefined)
|
39
|
-
return Of if
|
40
|
-
|
41
|
-
return Kind::Of.(kind, object) if object != Undefined
|
31
|
+
return Of if Undefined == kind && Undefined == object
|
42
32
|
|
43
|
-
|
44
|
-
kind_name = kind.name
|
33
|
+
return Kind::Of.(kind, object) if Undefined != object
|
45
34
|
|
46
|
-
|
47
|
-
Kind::Of.const_get(kind_name)
|
48
|
-
else
|
49
|
-
Checker.new(Kind::Of.(Module, kind, MODULE_OR_CLASS))
|
50
|
-
end
|
51
|
-
end
|
35
|
+
Kind::Checker::Factory.create(kind)
|
52
36
|
end
|
53
37
|
|
54
38
|
def self.of?(kind, *args)
|
@@ -65,7 +49,7 @@ module Kind
|
|
65
49
|
end
|
66
50
|
|
67
51
|
def self.Module(value)
|
68
|
-
|
52
|
+
::Module == value || (value.is_a?(::Module) && !self.Class(value))
|
69
53
|
end
|
70
54
|
|
71
55
|
def self.Boolean(value)
|
@@ -81,13 +65,13 @@ module Kind
|
|
81
65
|
# -- Class
|
82
66
|
|
83
67
|
def self.Class(object = Undefined)
|
84
|
-
return Class if
|
68
|
+
return Class if Undefined == object
|
85
69
|
|
86
70
|
self.call(::Class, object)
|
87
71
|
end
|
88
72
|
|
89
73
|
const_set(:Class, ::Module.new do
|
90
|
-
extend
|
74
|
+
extend Checker::Protocol
|
91
75
|
|
92
76
|
def self.__kind; ::Class; end
|
93
77
|
|
@@ -103,16 +87,16 @@ module Kind
|
|
103
87
|
# -- Module
|
104
88
|
|
105
89
|
def self.Module(object = Undefined)
|
106
|
-
return Module if
|
90
|
+
return Module if Undefined == object
|
107
91
|
|
108
92
|
self.call(::Module, object)
|
109
93
|
end
|
110
94
|
|
111
95
|
const_set(:Module, ::Module.new do
|
112
|
-
extend
|
96
|
+
extend Checker::Protocol
|
113
97
|
|
114
98
|
def self.__kind_undefined(value)
|
115
|
-
__kind_error(Kind::Undefined) if
|
99
|
+
__kind_error(Kind::Undefined) if Kind::Undefined == value
|
116
100
|
|
117
101
|
yield
|
118
102
|
end
|
@@ -137,7 +121,7 @@ module Kind
|
|
137
121
|
if ::Kind::Maybe::Value.none?(default)
|
138
122
|
__kind_undefined(value) { __kind_of(value) }
|
139
123
|
else
|
140
|
-
return value if
|
124
|
+
return value if Kind::Undefined != value && instance?(value)
|
141
125
|
|
142
126
|
__kind_undefined(default) { __kind_of(default) }
|
143
127
|
end
|
@@ -155,7 +139,7 @@ module Kind
|
|
155
139
|
def self.Boolean(object = Undefined, options = Empty::HASH)
|
156
140
|
default = options[:or]
|
157
141
|
|
158
|
-
return Kind::Of::Boolean if
|
142
|
+
return Kind::Of::Boolean if Undefined == object && default.nil?
|
159
143
|
|
160
144
|
bool = object.nil? ? default : object
|
161
145
|
|
@@ -165,7 +149,7 @@ module Kind
|
|
165
149
|
end
|
166
150
|
|
167
151
|
const_set(:Boolean, ::Module.new do
|
168
|
-
extend
|
152
|
+
extend Checker::Protocol
|
169
153
|
|
170
154
|
def self.__kind; [TrueClass, FalseClass].freeze; end
|
171
155
|
|
@@ -177,14 +161,14 @@ module Kind
|
|
177
161
|
if ::Kind::Maybe::Value.none?(default)
|
178
162
|
__kind_undefined(value) { Kind::Of::Boolean(value) }
|
179
163
|
else
|
180
|
-
return value if
|
164
|
+
return value if Kind::Undefined != value && instance?(value)
|
181
165
|
|
182
166
|
__kind_undefined(default) { Kind::Of::Boolean(default) }
|
183
167
|
end
|
184
168
|
end
|
185
169
|
|
186
170
|
def self.__kind_undefined(value)
|
187
|
-
if
|
171
|
+
if Kind::Undefined == value
|
188
172
|
raise Kind::Error.new('Boolean'.freeze, Kind::Undefined)
|
189
173
|
else
|
190
174
|
yield
|
@@ -210,7 +194,7 @@ module Kind
|
|
210
194
|
def self.Lambda(object = Undefined, options = Empty::HASH)
|
211
195
|
default = options[:or]
|
212
196
|
|
213
|
-
return Kind::Of::Lambda if
|
197
|
+
return Kind::Of::Lambda if Undefined == object && default.nil?
|
214
198
|
|
215
199
|
func = object || default
|
216
200
|
|
@@ -220,7 +204,7 @@ module Kind
|
|
220
204
|
end
|
221
205
|
|
222
206
|
const_set(:Lambda, ::Module.new do
|
223
|
-
extend
|
207
|
+
extend Checker::Protocol
|
224
208
|
|
225
209
|
def self.__kind; ::Proc; end
|
226
210
|
|
@@ -230,14 +214,14 @@ module Kind
|
|
230
214
|
if ::Kind::Maybe::Value.none?(default)
|
231
215
|
__kind_undefined(value) { Kind::Of::Lambda(value) }
|
232
216
|
else
|
233
|
-
return value if
|
217
|
+
return value if Kind::Undefined != value && instance?(value)
|
234
218
|
|
235
219
|
__kind_undefined(default) { Kind::Of::Lambda(default) }
|
236
220
|
end
|
237
221
|
end
|
238
222
|
|
239
223
|
def self.__kind_undefined(value)
|
240
|
-
if
|
224
|
+
if Kind::Undefined == value
|
241
225
|
raise Kind::Error.new('Lambda'.freeze, Kind::Undefined)
|
242
226
|
else
|
243
227
|
yield
|
@@ -258,7 +242,7 @@ module Kind
|
|
258
242
|
def self.Callable(object = Undefined, options = Empty::HASH)
|
259
243
|
default = options[:or]
|
260
244
|
|
261
|
-
return Kind::Of::Callable if
|
245
|
+
return Kind::Of::Callable if Undefined == object && default.nil?
|
262
246
|
|
263
247
|
callable = object || default
|
264
248
|
|
@@ -268,7 +252,7 @@ module Kind
|
|
268
252
|
end
|
269
253
|
|
270
254
|
const_set(:Callable, ::Module.new do
|
271
|
-
extend
|
255
|
+
extend Checker::Protocol
|
272
256
|
|
273
257
|
def self.__kind; Object; end
|
274
258
|
|
@@ -282,14 +266,14 @@ module Kind
|
|
282
266
|
if ::Kind::Maybe::Value.none?(default)
|
283
267
|
__kind_undefined(value) { Kind::Of::Callable(value) }
|
284
268
|
else
|
285
|
-
return value if
|
269
|
+
return value if Kind::Undefined != value && instance?(value)
|
286
270
|
|
287
271
|
__kind_undefined(default) { Kind::Of::Callable(default) }
|
288
272
|
end
|
289
273
|
end
|
290
274
|
|
291
275
|
def self.__kind_undefined(value)
|
292
|
-
if
|
276
|
+
if Kind::Undefined == value
|
293
277
|
raise Kind::Error.new('Callable'.freeze, Kind::Undefined)
|
294
278
|
else
|
295
279
|
yield
|
@@ -312,7 +296,7 @@ module Kind
|
|
312
296
|
# -- Classes
|
313
297
|
[
|
314
298
|
String, Symbol, Numeric, Integer, Float, Regexp, Time,
|
315
|
-
Array, Range, Hash, Struct, Enumerator, Set,
|
299
|
+
Array, Range, Hash, Struct, Enumerator, Set, OpenStruct,
|
316
300
|
Method, Proc,
|
317
301
|
IO, File
|
318
302
|
].each { |klass| Types.add(klass) }
|
@@ -53,12 +53,12 @@ class KindValidator < ActiveModel::EachValidator
|
|
53
53
|
def kind_is_not(expected, value)
|
54
54
|
case expected
|
55
55
|
when Class
|
56
|
-
return if Kind.of.Class(value)
|
56
|
+
return if expected == Kind.of.Class(value) || value < expected
|
57
57
|
|
58
58
|
"must be the class or a subclass of `#{expected.name}`"
|
59
59
|
when Module
|
60
60
|
return if value.kind_of?(Class) && value <= expected
|
61
|
-
return if Kind.of.Module(value)
|
61
|
+
return if expected == Kind.of.Module(value) || value.kind_of?(expected)
|
62
62
|
|
63
63
|
"must include the `#{expected.name}` module"
|
64
64
|
else
|
data/lib/kind/checker.rb
CHANGED
@@ -1,78 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind/checker/factory'
|
4
|
+
require 'kind/checker/protocol'
|
3
5
|
module Kind
|
4
|
-
module Checkable
|
5
|
-
def class?(value)
|
6
|
-
Kind::Is.__call__(__kind, value)
|
7
|
-
end
|
8
|
-
|
9
|
-
def instance(value, options = Empty::HASH)
|
10
|
-
default = options[:or]
|
11
|
-
|
12
|
-
return Kind::Of.(__kind, value) if ::Kind::Maybe::Value.none?(default)
|
13
|
-
|
14
|
-
value != Kind::Undefined && instance?(value) ? value : Kind::Of.(__kind, default)
|
15
|
-
end
|
16
|
-
|
17
|
-
def [](value, options = options = Empty::HASH)
|
18
|
-
instance(value, options)
|
19
|
-
end
|
20
|
-
|
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
|
-
arg == Kind::Undefined ? is_instance_to_proc : __is_instance__(arg)
|
42
|
-
end
|
43
|
-
|
44
|
-
def or_nil(value)
|
45
|
-
return value if instance?(value)
|
46
|
-
end
|
47
|
-
|
48
|
-
def or_undefined(value)
|
49
|
-
or_nil(value) || Kind::Undefined
|
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 value != Kind::Undefined
|
63
|
-
|
64
|
-
as_maybe_to_proc
|
65
|
-
end
|
66
|
-
|
67
|
-
def as_optional(value = Kind::Undefined)
|
68
|
-
as_maybe(value)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
private_constant :Checkable
|
73
|
-
|
74
6
|
class Checker
|
75
|
-
include
|
7
|
+
include Protocol
|
76
8
|
|
77
9
|
attr_reader :__kind
|
78
10
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Kind
|
6
|
+
class Checker
|
7
|
+
class Factory
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def self.create(kind)
|
11
|
+
instance.create(kind)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@__checkers__ = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
MODULE_OR_CLASS = 'Module/Class'.freeze
|
19
|
+
|
20
|
+
private_constant :MODULE_OR_CLASS
|
21
|
+
|
22
|
+
def create(kind)
|
23
|
+
@__checkers__[kind] ||= begin
|
24
|
+
kind_name = kind.name
|
25
|
+
|
26
|
+
if Kind::Of.const_defined?(kind_name, false)
|
27
|
+
Kind::Of.const_get(kind_name)
|
28
|
+
else
|
29
|
+
Kind::Checker.new(Kind::Of.(Module, kind, MODULE_OR_CLASS))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
class Checker
|
5
|
+
module Protocol
|
6
|
+
def class?(value)
|
7
|
+
Kind::Is.__call__(__kind, value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def instance(value, options = Empty::HASH)
|
11
|
+
default = options[:or]
|
12
|
+
|
13
|
+
return Kind::Of.(__kind, value) if ::Kind::Maybe::Value.none?(default)
|
14
|
+
|
15
|
+
Kind::Undefined != value && instance?(value) ? value : Kind::Of.(__kind, default)
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](value, options = options = Empty::HASH)
|
19
|
+
instance(value, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_proc
|
23
|
+
@to_proc ||=
|
24
|
+
-> checker { -> value { checker.instance(value) } }.call(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def __is_instance__(value)
|
28
|
+
value.kind_of?(__kind)
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_instance_to_proc
|
32
|
+
@is_instance_to_proc ||=
|
33
|
+
-> checker { -> value { checker.__is_instance__(value) } }.call(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def instance?(*args)
|
37
|
+
return is_instance_to_proc if args.empty?
|
38
|
+
|
39
|
+
return args.all? { |object| __is_instance__(object) } if args.size > 1
|
40
|
+
|
41
|
+
arg = args[0]
|
42
|
+
Kind::Undefined == arg ? is_instance_to_proc : __is_instance__(arg)
|
43
|
+
end
|
44
|
+
|
45
|
+
def or_nil(value)
|
46
|
+
return value if instance?(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def or_undefined(value)
|
50
|
+
or_nil(value) || Kind::Undefined
|
51
|
+
end
|
52
|
+
|
53
|
+
def __as_maybe__(value)
|
54
|
+
Kind::Maybe.new(or_nil(value))
|
55
|
+
end
|
56
|
+
|
57
|
+
def as_maybe_to_proc
|
58
|
+
@as_maybe_to_proc ||=
|
59
|
+
-> checker { -> value { checker.__as_maybe__(value) } }.call(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
def as_maybe(value = Kind::Undefined)
|
63
|
+
return __as_maybe__(value) if Kind::Undefined != value
|
64
|
+
|
65
|
+
as_maybe_to_proc
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_optional(value = Kind::Undefined)
|
69
|
+
as_maybe(value)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/kind/dig.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module Dig
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(data, keys)
|
8
|
+
return unless keys.is_a?(Array)
|
9
|
+
|
10
|
+
keys.reduce(data) do |memo, key|
|
11
|
+
value = get(memo, key)
|
12
|
+
|
13
|
+
break if value.nil?
|
14
|
+
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get(data, key)
|
22
|
+
return data[key] if Hash === data
|
23
|
+
|
24
|
+
case data
|
25
|
+
when Array
|
26
|
+
data[key] if key.respond_to?(:to_int)
|
27
|
+
when OpenStruct
|
28
|
+
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
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/kind/error.rb
CHANGED
@@ -7,7 +7,7 @@ module Kind
|
|
7
7
|
private_constant :UNDEFINED_OBJECT
|
8
8
|
|
9
9
|
def initialize(arg, object = UNDEFINED_OBJECT)
|
10
|
-
if
|
10
|
+
if UNDEFINED_OBJECT == object
|
11
11
|
# Will be used when the exception was raised with a message. e.g:
|
12
12
|
# raise Kind::Error, "some message"
|
13
13
|
super(arg)
|
data/lib/kind/maybe.rb
CHANGED
@@ -1,10 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind/dig'
|
4
|
+
|
3
5
|
module Kind
|
4
6
|
module Maybe
|
7
|
+
class Typed
|
8
|
+
def initialize(kind)
|
9
|
+
@kind_checker = Kind::Checker::Factory.create(kind)
|
10
|
+
end
|
11
|
+
|
12
|
+
def wrap(value)
|
13
|
+
@kind_checker.as_maybe(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :new, :wrap
|
17
|
+
alias_method :[], :wrap
|
18
|
+
end
|
19
|
+
|
5
20
|
module Value
|
6
21
|
def self.none?(value)
|
7
|
-
value
|
22
|
+
value.nil? || Undefined == value
|
8
23
|
end
|
9
24
|
|
10
25
|
def self.some?(value)
|
@@ -42,9 +57,9 @@ module Kind
|
|
42
57
|
INVALID_DEFAULT_ARG = 'the default value must be defined as an argument or block'.freeze
|
43
58
|
|
44
59
|
def value_or(default = Undefined, &block)
|
45
|
-
raise ArgumentError, INVALID_DEFAULT_ARG if
|
60
|
+
raise ArgumentError, INVALID_DEFAULT_ARG if Undefined == default && !block
|
46
61
|
|
47
|
-
|
62
|
+
Undefined != default ? default : block.call
|
48
63
|
end
|
49
64
|
|
50
65
|
def none?; true; end
|
@@ -55,10 +70,16 @@ module Kind
|
|
55
70
|
|
56
71
|
alias_method :then, :map
|
57
72
|
|
58
|
-
def try(method_name = Undefined, &block)
|
59
|
-
Kind.of.Symbol(method_name) if
|
73
|
+
def try!(method_name = Undefined, *args, &block)
|
74
|
+
Kind.of.Symbol(method_name) if Undefined != method_name
|
60
75
|
|
61
|
-
|
76
|
+
NONE_WITH_NIL_VALUE
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :try, :try!
|
80
|
+
|
81
|
+
def dig(*keys)
|
82
|
+
NONE_WITH_NIL_VALUE
|
62
83
|
end
|
63
84
|
|
64
85
|
private_constant :INVALID_DEFAULT_ARG
|
@@ -79,22 +100,47 @@ module Kind
|
|
79
100
|
def map(&fn)
|
80
101
|
result = fn.call(@value)
|
81
102
|
|
82
|
-
|
83
|
-
return NONE_WITH_NIL_VALUE if result == nil
|
84
|
-
return NONE_WITH_UNDEFINED_VALUE if result == Undefined
|
85
|
-
|
86
|
-
Some.new(result)
|
103
|
+
resolve(result)
|
87
104
|
end
|
88
105
|
|
89
106
|
alias_method :then, :map
|
90
107
|
|
91
|
-
def try(method_name = Undefined, *args, &block)
|
92
|
-
|
108
|
+
def try!(method_name = Undefined, *args, &block)
|
109
|
+
Kind::Of::Symbol(method_name) if Undefined != method_name
|
110
|
+
|
111
|
+
__try__(method_name, args, block)
|
112
|
+
end
|
93
113
|
|
94
|
-
|
114
|
+
def try(method_name = Undefined, *args, &block)
|
115
|
+
if (Undefined != method_name && value.respond_to?(Kind::Of::Symbol(method_name))) ||
|
116
|
+
(Undefined == method_name && block)
|
117
|
+
__try__(method_name, args, block)
|
118
|
+
else
|
119
|
+
NONE_WITH_NIL_VALUE
|
120
|
+
end
|
121
|
+
end
|
95
122
|
|
96
|
-
|
123
|
+
def dig(*keys)
|
124
|
+
resolve(Kind::Dig.call(value, keys))
|
97
125
|
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def __try__(method_name = Undefined, args, block)
|
130
|
+
fn = Undefined == method_name ? block : method_name.to_proc
|
131
|
+
|
132
|
+
result = args.empty? ? fn.call(value) : fn.call(*args.unshift(value))
|
133
|
+
|
134
|
+
resolve(result)
|
135
|
+
end
|
136
|
+
|
137
|
+
def resolve(result)
|
138
|
+
return result if Maybe::None === result
|
139
|
+
return NONE_WITH_NIL_VALUE if result.nil?
|
140
|
+
return NONE_WITH_UNDEFINED_VALUE if Undefined == result
|
141
|
+
|
142
|
+
Some.new(result)
|
143
|
+
end
|
98
144
|
end
|
99
145
|
|
100
146
|
def self.new(value)
|
@@ -102,7 +148,11 @@ module Kind
|
|
102
148
|
result_type.new(value)
|
103
149
|
end
|
104
150
|
|
105
|
-
def self.[](value)
|
151
|
+
def self.[](value)
|
152
|
+
new(value)
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.wrap(value)
|
106
156
|
new(value)
|
107
157
|
end
|
108
158
|
|
@@ -132,4 +182,12 @@ module Kind
|
|
132
182
|
def self.Some(value)
|
133
183
|
Maybe.some(value)
|
134
184
|
end
|
185
|
+
|
186
|
+
def self.Maybe(kind)
|
187
|
+
Maybe::Typed.new(kind)
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.Optional(kind)
|
191
|
+
Maybe::Typed.new(kind)
|
192
|
+
end
|
135
193
|
end
|
data/lib/kind/types.rb
CHANGED
@@ -10,7 +10,7 @@ module Kind
|
|
10
10
|
def self.%{method_name}(object = Undefined, options = Empty::HASH)
|
11
11
|
default = options[:or]
|
12
12
|
|
13
|
-
return Kind::Of::%{kind_name} if
|
13
|
+
return Kind::Of::%{kind_name} if Undefined == object && default.nil?
|
14
14
|
|
15
15
|
is_instance = Kind::Of::%{kind_name}.__is_instance__(object)
|
16
16
|
|
@@ -28,7 +28,7 @@ module Kind
|
|
28
28
|
|
29
29
|
KIND_IS = <<-RUBY
|
30
30
|
def self.%{method_name}(value = Undefined)
|
31
|
-
return Kind::Is::%{kind_name} if
|
31
|
+
return Kind::Is::%{kind_name} if Undefined == value
|
32
32
|
|
33
33
|
Kind::Is.__call__(::%{kind_name_to_check}, value)
|
34
34
|
end
|
@@ -98,7 +98,7 @@ module Kind
|
|
98
98
|
kind_name = params[:kind_name]
|
99
99
|
params[:kind_name_to_check] ||= kind_name
|
100
100
|
|
101
|
-
kind_checker = ::Module.new { extend
|
101
|
+
kind_checker = ::Module.new { extend Checker::Protocol }
|
102
102
|
kind_checker.module_eval("def self.__kind; #{params[:kind_name_to_check]}; end")
|
103
103
|
|
104
104
|
kind_of_mod.instance_eval(KIND_OF % params)
|
data/lib/kind/undefined.rb
CHANGED
data/lib/kind/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple type system (at runtime) for Ruby - free of dependencies.
|
14
14
|
email:
|
@@ -32,6 +32,9 @@ files:
|
|
32
32
|
- lib/kind/active_model/kind_validator.rb
|
33
33
|
- lib/kind/active_model/validation.rb
|
34
34
|
- lib/kind/checker.rb
|
35
|
+
- lib/kind/checker/factory.rb
|
36
|
+
- lib/kind/checker/protocol.rb
|
37
|
+
- lib/kind/dig.rb
|
35
38
|
- lib/kind/empty.rb
|
36
39
|
- lib/kind/error.rb
|
37
40
|
- lib/kind/is.rb
|