kind 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|