kind 2.1.0 → 3.1.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.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
|