kind 5.1.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.tool-versions +1 -1
- data/.travis.sh +35 -10
- data/CHANGELOG.md +364 -26
- data/README.md +37 -36
- data/lib/kind.rb +2 -49
- data/lib/kind/__lib__/attributes.rb +66 -0
- data/lib/kind/__lib__/kind.rb +71 -0
- data/lib/kind/__lib__/undefined.rb +14 -0
- data/lib/kind/action.rb +92 -0
- data/lib/kind/active_model/validation.rb +1 -1
- data/lib/kind/basic.rb +73 -0
- data/lib/kind/basic/error.rb +29 -0
- data/lib/kind/{core → basic}/undefined.rb +6 -1
- data/lib/kind/{core/dig.rb → dig.rb} +20 -4
- data/lib/kind/either.rb +30 -0
- data/lib/kind/either/left.rb +29 -0
- data/lib/kind/either/methods.rb +17 -0
- data/lib/kind/either/monad.rb +65 -0
- data/lib/kind/either/monad/wrapper.rb +19 -0
- data/lib/kind/either/right.rb +38 -0
- data/lib/kind/{core/empty.rb → empty.rb} +2 -0
- data/lib/kind/{core/empty → empty}/constant.rb +0 -0
- data/lib/kind/enum.rb +63 -0
- data/lib/kind/enum/item.rb +40 -0
- data/lib/kind/enum/methods.rb +72 -0
- data/lib/kind/function.rb +47 -0
- data/lib/kind/functional.rb +89 -0
- data/lib/kind/functional/action.rb +89 -0
- data/lib/kind/immutable_attributes.rb +34 -0
- data/lib/kind/immutable_attributes/initializer.rb +70 -0
- data/lib/kind/immutable_attributes/reader.rb +38 -0
- data/lib/kind/maybe.rb +69 -0
- data/lib/kind/maybe/methods.rb +21 -0
- data/lib/kind/maybe/monad.rb +82 -0
- data/lib/kind/maybe/monad/wrapper.rb +19 -0
- data/lib/kind/{core/maybe → maybe}/none.rb +11 -18
- data/lib/kind/maybe/some.rb +132 -0
- data/lib/kind/maybe/typed.rb +35 -0
- data/lib/kind/{core/maybe/wrappable.rb → maybe/wrapper.rb} +8 -4
- data/lib/kind/monad.rb +22 -0
- data/lib/kind/monads.rb +5 -0
- data/lib/kind/objects.rb +17 -0
- data/lib/kind/objects/basic_object.rb +45 -0
- data/lib/kind/objects/modules.rb +32 -0
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/array.rb +3 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/class.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/comparable.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/enumerable.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/enumerator.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/file.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/float.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/hash.rb +3 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/integer.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/io.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/method.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/module.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/numeric.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/proc.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/queue.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/range.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/regexp.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/string.rb +3 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/struct.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/symbol.rb +1 -1
- data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/time.rb +1 -1
- data/lib/kind/{core/type_checkers → objects/modules}/custom/boolean.rb +2 -2
- data/lib/kind/{core/type_checkers → objects/modules}/custom/callable.rb +1 -1
- data/lib/kind/{core/type_checkers → objects/modules}/custom/lambda.rb +1 -1
- data/lib/kind/{core/type_checkers → objects/modules}/stdlib/open_struct.rb +3 -1
- data/lib/kind/{core/type_checkers → objects/modules}/stdlib/set.rb +3 -1
- data/lib/kind/objects/nil.rb +17 -0
- data/lib/kind/objects/not_nil.rb +13 -0
- data/lib/kind/objects/object.rb +56 -0
- data/lib/kind/objects/respond_to.rb +30 -0
- data/lib/kind/objects/union_type.rb +44 -0
- data/lib/kind/{core/presence.rb → presence.rb} +4 -2
- data/lib/kind/result.rb +31 -0
- data/lib/kind/result/abstract.rb +53 -0
- data/lib/kind/result/failure.rb +31 -0
- data/lib/kind/result/methods.rb +17 -0
- data/lib/kind/result/monad.rb +69 -0
- data/lib/kind/result/monad/wrapper.rb +19 -0
- data/lib/kind/result/success.rb +40 -0
- data/lib/kind/try.rb +46 -0
- data/lib/kind/validator.rb +14 -10
- data/lib/kind/version.rb +1 -1
- metadata +81 -47
- data/lib/kind/core.rb +0 -15
- data/lib/kind/core/error.rb +0 -15
- data/lib/kind/core/maybe.rb +0 -42
- data/lib/kind/core/maybe/result.rb +0 -51
- data/lib/kind/core/maybe/some.rb +0 -90
- data/lib/kind/core/maybe/typed.rb +0 -29
- data/lib/kind/core/try.rb +0 -34
- data/lib/kind/core/type_checker.rb +0 -87
- data/lib/kind/core/type_checkers.rb +0 -30
- data/lib/kind/core/utils/kind.rb +0 -61
- data/lib/kind/core/utils/undefined.rb +0 -7
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
<p align="center">
|
2
2
|
<h1 align="center">🤷 kind</h1>
|
3
3
|
<p align="center"><i>A simple type system (at runtime) for Ruby - free of dependencies.</i></p>
|
4
|
-
<br>
|
5
4
|
</p>
|
6
5
|
|
7
6
|
<p align="center">
|
@@ -9,16 +8,18 @@
|
|
9
8
|
<img alt="Gem" src="https://img.shields.io/gem/v/kind.svg?style=flat-square">
|
10
9
|
</a>
|
11
10
|
|
12
|
-
<img src="https://img.shields.io/badge/ruby%20%3E=%202.1.0,%20%3C%203.1-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
13
|
-
|
14
|
-
<img src="https://img.shields.io/badge/rails%20%3E=%203.2.0,%20%3C%207-rails.svg?colorA=8B0000&colorB=FF0000" alt="Rails">
|
15
|
-
|
16
|
-
<br />
|
17
|
-
|
18
11
|
<a href="https://travis-ci.com/serradura/kind">
|
19
12
|
<img alt="Build Status" src="https://travis-ci.com/serradura/kind.svg?branch=master">
|
20
13
|
</a>
|
21
14
|
|
15
|
+
<br />
|
16
|
+
|
17
|
+
<img src="https://img.shields.io/badge/ruby%20%3E=%202.1,%20%3C%203.1-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
18
|
+
|
19
|
+
<img src="https://img.shields.io/badge/rails%20%3E=%203.2.0,%20%3C%207.0-rails.svg?colorA=8B0000&colorB=FF0000" alt="Rails">
|
20
|
+
|
21
|
+
<br />
|
22
|
+
|
22
23
|
<a href="https://codeclimate.com/github/serradura/kind/maintainability">
|
23
24
|
<img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability">
|
24
25
|
</a>
|
@@ -38,12 +39,12 @@ One of the goals of this project is to do simple type checking like `"some strin
|
|
38
39
|
|
39
40
|
Version | Documentation
|
40
41
|
---------- | -------------
|
41
|
-
unreleased | https://github.com/serradura/
|
42
|
-
5.
|
43
|
-
4.1.0 | https://github.com/serradura/
|
44
|
-
3.1.0 | https://github.com/serradura/
|
45
|
-
2.3.0 | https://github.com/serradura/
|
46
|
-
1.9.0 | https://github.com/serradura/
|
42
|
+
unreleased | https://github.com/serradura/kind/blob/main/README.md
|
43
|
+
5.2.0 | https://github.com/serradura/kind/blob/v5.x/README.md
|
44
|
+
4.1.0 | https://github.com/serradura/kind/blob/v4.x/README.md
|
45
|
+
3.1.0 | https://github.com/serradura/kind/blob/v3.x/README.md
|
46
|
+
2.3.0 | https://github.com/serradura/kind/blob/v2.x/README.md
|
47
|
+
1.9.0 | https://github.com/serradura/kind/blob/v1.x/README.md
|
47
48
|
|
48
49
|
## Table of Contents <!-- omit in toc -->
|
49
50
|
- [Compatibility](#compatibility)
|
@@ -58,16 +59,16 @@ unreleased | https://github.com/serradura/u-case/blob/main/README.md
|
|
58
59
|
- [Kind::\<Type\>.value()](#kindtypevalue-1)
|
59
60
|
- [Kind::\<Type\>.maybe](#kindtypemaybe)
|
60
61
|
- [Kind::\<Type\>?](#kindtype-2)
|
61
|
-
- [Kind::{Array,Hash,String,Set}.
|
62
|
-
- [List of all type
|
62
|
+
- [Kind::{Array,Hash,String,Set}.empty_or()](#kindarrayhashstringsetempty_or)
|
63
|
+
- [List of all type handlers](#list-of-all-type-handlers)
|
63
64
|
- [Core](#core)
|
64
65
|
- [Stdlib](#stdlib)
|
65
66
|
- [Custom](#custom)
|
66
|
-
- [Creating type
|
67
|
+
- [Creating type handlers](#creating-type-handlers)
|
67
68
|
- [Dynamic creation](#dynamic-creation)
|
68
69
|
- [Using a class or a module](#using-a-class-or-a-module)
|
69
70
|
- [Using an object which responds to ===](#using-an-object-which-responds-to-)
|
70
|
-
- [Kind::<Type>
|
71
|
+
- [Kind::<Type> object](#kindtype-object)
|
71
72
|
- [Utility methods](#utility-methods)
|
72
73
|
- [Kind.of_class?()](#kindof_class)
|
73
74
|
- [Kind.of_module?()](#kindof_module)
|
@@ -119,10 +120,10 @@ unreleased | https://github.com/serradura/u-case/blob/main/README.md
|
|
119
120
|
|
120
121
|
## Compatibility
|
121
122
|
|
122
|
-
|
|
123
|
+
| kind | branch | ruby | activemodel |
|
123
124
|
| -------------- | ------- | -------- | -------------- |
|
124
125
|
| unreleased | main | >= 2.1.0 | >= 3.2, <= 6.1 |
|
125
|
-
| 5.
|
126
|
+
| 5.2.0 | v5.x | >= 2.1.0 | >= 3.2, <= 6.1 |
|
126
127
|
| 4.1.0 | v4.x | >= 2.2.0 | >= 3.2, <= 6.1 |
|
127
128
|
| 3.1.0 | v3.x | >= 2.2.0 | >= 3.2, <= 6.1 |
|
128
129
|
| 2.3.0 | v2.x | >= 2.2.0 | >= 3.2, <= 6.0 |
|
@@ -327,17 +328,17 @@ collection.select(&Kind::Enumerable?) # [{:number=>1}, {:number=>3}, [:number, 5
|
|
327
328
|
|
328
329
|
[⬆️ Back to Top](#table-of-contents-)
|
329
330
|
|
330
|
-
### Kind::{Array,Hash,String,Set}.
|
331
|
+
### Kind::{Array,Hash,String,Set}.empty_or()
|
331
332
|
|
332
|
-
This method is available for some type
|
333
|
+
This method is available for some type handlers (`Kind::Array`, `Kind::Hash`, `Kind::String`, `Kind::Set`), and it will return an empty frozen value if the given one hasn't the expected kind.
|
333
334
|
```ruby
|
334
|
-
Kind::Array.
|
335
|
-
Kind::Array.
|
335
|
+
Kind::Array.empty_or({}) # []
|
336
|
+
Kind::Array.empty_or({}).frozen? # true
|
336
337
|
```
|
337
338
|
|
338
339
|
[⬆️ Back to Top](#table-of-contents-)
|
339
340
|
|
340
|
-
### List of all type
|
341
|
+
### List of all type handlers
|
341
342
|
|
342
343
|
#### Core
|
343
344
|
|
@@ -376,9 +377,9 @@ Kind::Array.value_or_empty({}).frozen? # true
|
|
376
377
|
|
377
378
|
[⬆️ Back to Top](#table-of-contents-)
|
378
379
|
|
379
|
-
### Creating type
|
380
|
+
### Creating type handlers
|
380
381
|
|
381
|
-
There are two ways to do this, you can create type
|
382
|
+
There are two ways to do this, you can create type handlers dynamically or defining a module.
|
382
383
|
|
383
384
|
#### Dynamic creation
|
384
385
|
|
@@ -394,7 +395,7 @@ kind_of_user = Kind::Of(User)
|
|
394
395
|
|
395
396
|
# kind_of_user.name
|
396
397
|
# kind_of_user.kind
|
397
|
-
# The type
|
398
|
+
# The type handler can return its kind and its name
|
398
399
|
kind_of_user.name # "User"
|
399
400
|
kind_of_user.kind # User
|
400
401
|
|
@@ -464,7 +465,7 @@ PositiveInteger = Kind::Of(
|
|
464
465
|
|
465
466
|
# PositiveInteger.name
|
466
467
|
# PositiveInteger.kind
|
467
|
-
# The type
|
468
|
+
# The type handler can return its kind and its name
|
468
469
|
PositiveInteger.name # "PositiveInteger"
|
469
470
|
PositiveInteger.kind # #<Proc:0x0000.... >
|
470
471
|
|
@@ -525,9 +526,9 @@ PositiveInteger.maybe(2).value_or(1) # 2
|
|
525
526
|
|
526
527
|
[⬆️ Back to Top](#table-of-contents-)
|
527
528
|
|
528
|
-
#### Kind::<Type>
|
529
|
+
#### Kind::<Type> object
|
529
530
|
|
530
|
-
The idea here is to create a type
|
531
|
+
The idea here is to create a type handler inside of the `Kind` namespace and to do this you will only need to use pure Ruby. e.g:
|
531
532
|
|
532
533
|
```ruby
|
533
534
|
class User
|
@@ -535,9 +536,9 @@ end
|
|
535
536
|
|
536
537
|
module Kind
|
537
538
|
module User
|
538
|
-
extend self,
|
539
|
+
extend self, ::Kind::Object
|
539
540
|
|
540
|
-
# Define the expected kind of this type
|
541
|
+
# Define the expected kind of this type handler.
|
541
542
|
def kind; ::User; end
|
542
543
|
end
|
543
544
|
|
@@ -547,7 +548,7 @@ module Kind
|
|
547
548
|
end
|
548
549
|
end
|
549
550
|
|
550
|
-
# Doing this you will have the same methods of a standard type
|
551
|
+
# Doing this you will have the same methods of a standard type handler (like: `Kind::Symbol`).
|
551
552
|
|
552
553
|
user = User.new
|
553
554
|
|
@@ -565,7 +566,7 @@ The advantages of this approach are:
|
|
565
566
|
|
566
567
|
The disadvantage is:
|
567
568
|
|
568
|
-
1. You could overwrite some standard type
|
569
|
+
1. You could overwrite some standard type handler or constant. I believe that this will be hard to happen, but must be your concern if you decide to use this kind of approach.
|
569
570
|
|
570
571
|
[⬆️ Back to Top](#table-of-contents-)
|
571
572
|
|
@@ -1459,7 +1460,7 @@ Follows the list of constants if the alias was defined:
|
|
1459
1460
|
|
1460
1461
|
## Kind::Validator (ActiveModel::Validations)
|
1461
1462
|
|
1462
|
-
This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, <
|
1463
|
+
This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, < 7.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
|
1463
1464
|
|
1464
1465
|
```ruby
|
1465
1466
|
class Person
|
@@ -1497,7 +1498,7 @@ Use an array to verify if the attribute is an instance of one of the classes/mod
|
|
1497
1498
|
validates :status, kind: { of: [String, Symbol]}
|
1498
1499
|
```
|
1499
1500
|
|
1500
|
-
Because of kind verification be made via `===` you can use type
|
1501
|
+
Because of kind verification be made via `===` you can use type handlers as the expected kinds.
|
1501
1502
|
|
1502
1503
|
```ruby
|
1503
1504
|
validates :alive, kind: Kind::Boolean
|
data/lib/kind.rb
CHANGED
@@ -1,51 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
|
6
|
-
module Kind
|
7
|
-
require 'kind/version'
|
8
|
-
require 'kind/core'
|
9
|
-
|
10
|
-
extend self
|
11
|
-
|
12
|
-
def is?(kind, arg)
|
13
|
-
KIND.is?(kind, arg)
|
14
|
-
end
|
15
|
-
|
16
|
-
alias is is?
|
17
|
-
|
18
|
-
def of?(kind, *args)
|
19
|
-
KIND.of?(kind, args)
|
20
|
-
end
|
21
|
-
|
22
|
-
def of_class?(value)
|
23
|
-
KIND.of_class?(value)
|
24
|
-
end
|
25
|
-
|
26
|
-
def of_module?(value)
|
27
|
-
KIND.of_module?(value)
|
28
|
-
end
|
29
|
-
|
30
|
-
def respond_to(value, *method_names)
|
31
|
-
method_names.each { |method_name| KIND.respond_to!(method_name, value) }
|
32
|
-
|
33
|
-
value
|
34
|
-
end
|
35
|
-
|
36
|
-
def of_module_or_class(value)
|
37
|
-
KIND.of_module_or_class!(value)
|
38
|
-
end
|
39
|
-
|
40
|
-
def of(kind, object)
|
41
|
-
KIND.of!(kind, object)
|
42
|
-
end
|
43
|
-
|
44
|
-
def value(kind, value, default:)
|
45
|
-
KIND.value(kind, value, of(kind, default))
|
46
|
-
end
|
47
|
-
|
48
|
-
def Of(kind, opt = Empty::HASH)
|
49
|
-
TypeChecker::Object.new(kind, opt)
|
50
|
-
end
|
51
|
-
end
|
3
|
+
require 'kind/basic'
|
4
|
+
require 'kind/objects'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/__lib__/kind'
|
4
|
+
require 'kind/__lib__/undefined'
|
5
|
+
|
6
|
+
module Kind
|
7
|
+
module ATTRIBUTES
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def name!(name)
|
11
|
+
KIND.of!(::Symbol, name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def value(kind, default, visibility = :private)
|
15
|
+
[kind, default, visibility]
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_to_assign(kind, default, hash, name)
|
19
|
+
raw_value = hash[name]
|
20
|
+
|
21
|
+
return raw_value if kind.nil? && UNDEFINED == default
|
22
|
+
|
23
|
+
value = resolve_value_to_assign(kind, default, raw_value)
|
24
|
+
|
25
|
+
(kind.nil? || kind === value) ? value : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def value!(kind, default)
|
29
|
+
return value(kind, default) unless kind.nil?
|
30
|
+
|
31
|
+
raise Error.new("kind expected to not be nil")
|
32
|
+
end
|
33
|
+
|
34
|
+
def value_to_assign!(kind, default, hash, name)
|
35
|
+
value = resolve_value_to_assign(kind, default, hash[name])
|
36
|
+
|
37
|
+
Kind.of(kind, value, label: name)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def resolve_value_to_assign(kind, default, value)
|
43
|
+
if kind == ::Proc
|
44
|
+
UNDEFINED == default ? value : KIND.value(kind, value, default)
|
45
|
+
else
|
46
|
+
default_is_a_callable = default.respond_to?(:call)
|
47
|
+
|
48
|
+
default_value =
|
49
|
+
if default_is_a_callable
|
50
|
+
default_fn = Proc === default ? default : default.method(:call)
|
51
|
+
|
52
|
+
default_fn.arity != 0 ? default_fn.call(value) : default_fn.call
|
53
|
+
else
|
54
|
+
default
|
55
|
+
end
|
56
|
+
|
57
|
+
return value if UNDEFINED == default_value
|
58
|
+
return default_value || value if kind.nil?
|
59
|
+
|
60
|
+
default_is_a_callable ? KIND.value(kind, default_value, value) : KIND.value(kind, value, default_value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private_constant :ATTRIBUTES
|
66
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kind
|
4
|
+
module KIND
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def null?(value) # :nodoc:
|
8
|
+
value.nil? || Undefined == value
|
9
|
+
end
|
10
|
+
|
11
|
+
def of?(kind, values) # :nodoc:
|
12
|
+
of_kind = -> value { kind === value }
|
13
|
+
|
14
|
+
values.empty? ? of_kind : values.all?(&of_kind)
|
15
|
+
end
|
16
|
+
|
17
|
+
def of!(kind, value, kind_name = nil) # :nodoc:
|
18
|
+
return value if kind === value
|
19
|
+
|
20
|
+
error!(kind_name || kind.name, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def error!(kind_name, value, label = nil) # :nodoc:
|
24
|
+
raise Error.new(kind_name, value, label: label)
|
25
|
+
end
|
26
|
+
|
27
|
+
def of_class?(value) # :nodoc:
|
28
|
+
value.kind_of?(::Class)
|
29
|
+
end
|
30
|
+
|
31
|
+
def of_module?(value) # :nodoc:
|
32
|
+
::Module == value || (value.kind_of?(::Module) && !of_class?(value))
|
33
|
+
end
|
34
|
+
|
35
|
+
def of_module_or_class!(value) # :nodoc:
|
36
|
+
of!(::Module, value, 'Module/Class')
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to!(method_name, value) # :nodoc:
|
40
|
+
return value if value.respond_to?(method_name)
|
41
|
+
|
42
|
+
raise Error.new("expected #{value} to respond to :#{method_name}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def interface?(method_names, value) # :nodoc:
|
46
|
+
method_names.all? { |method_name| value.respond_to?(method_name) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def value(kind, arg, default) # :nodoc:
|
50
|
+
kind === arg ? arg : default
|
51
|
+
end
|
52
|
+
|
53
|
+
def is?(expected, value) # :nodoc:
|
54
|
+
is(of_module_or_class!(expected), value)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def is(expected_kind, value) # :nodoc:
|
60
|
+
kind = of_module_or_class!(value)
|
61
|
+
|
62
|
+
if of_class?(kind)
|
63
|
+
kind <= expected_kind || expected_kind == ::Class
|
64
|
+
else
|
65
|
+
kind == expected_kind || kind.kind_of?(expected_kind)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private_constant :KIND
|
71
|
+
end
|
data/lib/kind/action.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kind/basic'
|
4
|
+
require 'kind/result'
|
5
|
+
require 'kind/immutable_attributes'
|
6
|
+
|
7
|
+
module Kind
|
8
|
+
module Action
|
9
|
+
CALL_TMPL = [
|
10
|
+
'def self.call(arg)',
|
11
|
+
' new(Kind.of!(::Hash, arg)).call',
|
12
|
+
'end',
|
13
|
+
'',
|
14
|
+
'def call',
|
15
|
+
' result = call!',
|
16
|
+
'',
|
17
|
+
' return result if Kind::Result::Monad === result',
|
18
|
+
'',
|
19
|
+
' raise Kind::Error, "#{self.class.name}#call! must return a Success() or Failure()"',
|
20
|
+
'end'
|
21
|
+
].join("\n").freeze
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
include ImmutableAttributes::ClassMethods
|
25
|
+
|
26
|
+
def to_proc
|
27
|
+
@to_proc ||= ->(arg) { call(arg) }
|
28
|
+
end
|
29
|
+
|
30
|
+
ATTRIBUTE_METHODS = [
|
31
|
+
:attributes, :attribute,
|
32
|
+
:attribute?, :attribute!,
|
33
|
+
:with_attribute, :with_attributes,
|
34
|
+
:nil_attributes, :nil_attributes?
|
35
|
+
]
|
36
|
+
|
37
|
+
private_constant :ATTRIBUTE_METHODS
|
38
|
+
|
39
|
+
def kind_action!
|
40
|
+
return self if respond_to?(:call)
|
41
|
+
|
42
|
+
public_methods = self.public_instance_methods - ::Object.new.methods
|
43
|
+
|
44
|
+
remaining_methods = public_methods - (__attributes__.keys + ATTRIBUTE_METHODS)
|
45
|
+
|
46
|
+
unless remaining_methods.include?(:call!)
|
47
|
+
raise Kind::Error.new("expected #{self} to implement `#call!`")
|
48
|
+
end
|
49
|
+
|
50
|
+
if remaining_methods.size > 1
|
51
|
+
raise Kind::Error.new("#{self} can only have `#call!` as its public method")
|
52
|
+
end
|
53
|
+
|
54
|
+
call_parameters = public_instance_method(:call!).parameters
|
55
|
+
|
56
|
+
unless call_parameters.empty?
|
57
|
+
raise ArgumentError, "#{self.name}#call! must receive no arguments"
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.inherited(_)
|
61
|
+
raise RuntimeError, "#{self.name} is a Kind::Action and it can't be inherited"
|
62
|
+
end
|
63
|
+
|
64
|
+
self.send(:private_class_method, :new)
|
65
|
+
|
66
|
+
self.class_eval(CALL_TMPL)
|
67
|
+
|
68
|
+
self.send(:alias_method, :[], :call)
|
69
|
+
self.send(:alias_method, :===, :call)
|
70
|
+
self.send(:alias_method, :yield, :call)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.included(base)
|
75
|
+
KIND.of!(::Class, base).extend(ClassMethods)
|
76
|
+
|
77
|
+
base.send(:include, ImmutableAttributes::Reader)
|
78
|
+
end
|
79
|
+
|
80
|
+
include ImmutableAttributes::Initializer
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def Failure(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
85
|
+
Result::Failure[arg1, arg2, value_must_be_a: ::Hash]
|
86
|
+
end
|
87
|
+
|
88
|
+
def Success(arg1 = UNDEFINED, arg2 = UNDEFINED)
|
89
|
+
Result::Success[arg1, arg2, value_must_be_a: ::Hash]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|