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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/.travis.sh +35 -10
  4. data/CHANGELOG.md +364 -26
  5. data/README.md +37 -36
  6. data/lib/kind.rb +2 -49
  7. data/lib/kind/__lib__/attributes.rb +66 -0
  8. data/lib/kind/__lib__/kind.rb +71 -0
  9. data/lib/kind/__lib__/undefined.rb +14 -0
  10. data/lib/kind/action.rb +92 -0
  11. data/lib/kind/active_model/validation.rb +1 -1
  12. data/lib/kind/basic.rb +73 -0
  13. data/lib/kind/basic/error.rb +29 -0
  14. data/lib/kind/{core → basic}/undefined.rb +6 -1
  15. data/lib/kind/{core/dig.rb → dig.rb} +20 -4
  16. data/lib/kind/either.rb +30 -0
  17. data/lib/kind/either/left.rb +29 -0
  18. data/lib/kind/either/methods.rb +17 -0
  19. data/lib/kind/either/monad.rb +65 -0
  20. data/lib/kind/either/monad/wrapper.rb +19 -0
  21. data/lib/kind/either/right.rb +38 -0
  22. data/lib/kind/{core/empty.rb → empty.rb} +2 -0
  23. data/lib/kind/{core/empty → empty}/constant.rb +0 -0
  24. data/lib/kind/enum.rb +63 -0
  25. data/lib/kind/enum/item.rb +40 -0
  26. data/lib/kind/enum/methods.rb +72 -0
  27. data/lib/kind/function.rb +47 -0
  28. data/lib/kind/functional.rb +89 -0
  29. data/lib/kind/functional/action.rb +89 -0
  30. data/lib/kind/immutable_attributes.rb +34 -0
  31. data/lib/kind/immutable_attributes/initializer.rb +70 -0
  32. data/lib/kind/immutable_attributes/reader.rb +38 -0
  33. data/lib/kind/maybe.rb +69 -0
  34. data/lib/kind/maybe/methods.rb +21 -0
  35. data/lib/kind/maybe/monad.rb +82 -0
  36. data/lib/kind/maybe/monad/wrapper.rb +19 -0
  37. data/lib/kind/{core/maybe → maybe}/none.rb +11 -18
  38. data/lib/kind/maybe/some.rb +132 -0
  39. data/lib/kind/maybe/typed.rb +35 -0
  40. data/lib/kind/{core/maybe/wrappable.rb → maybe/wrapper.rb} +8 -4
  41. data/lib/kind/monad.rb +22 -0
  42. data/lib/kind/monads.rb +5 -0
  43. data/lib/kind/objects.rb +17 -0
  44. data/lib/kind/objects/basic_object.rb +45 -0
  45. data/lib/kind/objects/modules.rb +32 -0
  46. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/array.rb +3 -1
  47. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/class.rb +1 -1
  48. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/comparable.rb +1 -1
  49. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/enumerable.rb +1 -1
  50. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/enumerator.rb +1 -1
  51. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/file.rb +1 -1
  52. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/float.rb +1 -1
  53. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/hash.rb +3 -1
  54. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/integer.rb +1 -1
  55. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/io.rb +1 -1
  56. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/method.rb +1 -1
  57. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/module.rb +1 -1
  58. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/numeric.rb +1 -1
  59. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/proc.rb +1 -1
  60. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/queue.rb +1 -1
  61. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/range.rb +1 -1
  62. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/regexp.rb +1 -1
  63. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/string.rb +3 -1
  64. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/struct.rb +1 -1
  65. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/symbol.rb +1 -1
  66. data/lib/kind/{core/type_checkers/ruby → objects/modules/core}/time.rb +1 -1
  67. data/lib/kind/{core/type_checkers → objects/modules}/custom/boolean.rb +2 -2
  68. data/lib/kind/{core/type_checkers → objects/modules}/custom/callable.rb +1 -1
  69. data/lib/kind/{core/type_checkers → objects/modules}/custom/lambda.rb +1 -1
  70. data/lib/kind/{core/type_checkers → objects/modules}/stdlib/open_struct.rb +3 -1
  71. data/lib/kind/{core/type_checkers → objects/modules}/stdlib/set.rb +3 -1
  72. data/lib/kind/objects/nil.rb +17 -0
  73. data/lib/kind/objects/not_nil.rb +13 -0
  74. data/lib/kind/objects/object.rb +56 -0
  75. data/lib/kind/objects/respond_to.rb +30 -0
  76. data/lib/kind/objects/union_type.rb +44 -0
  77. data/lib/kind/{core/presence.rb → presence.rb} +4 -2
  78. data/lib/kind/result.rb +31 -0
  79. data/lib/kind/result/abstract.rb +53 -0
  80. data/lib/kind/result/failure.rb +31 -0
  81. data/lib/kind/result/methods.rb +17 -0
  82. data/lib/kind/result/monad.rb +69 -0
  83. data/lib/kind/result/monad/wrapper.rb +19 -0
  84. data/lib/kind/result/success.rb +40 -0
  85. data/lib/kind/try.rb +46 -0
  86. data/lib/kind/validator.rb +14 -10
  87. data/lib/kind/version.rb +1 -1
  88. metadata +81 -47
  89. data/lib/kind/core.rb +0 -15
  90. data/lib/kind/core/error.rb +0 -15
  91. data/lib/kind/core/maybe.rb +0 -42
  92. data/lib/kind/core/maybe/result.rb +0 -51
  93. data/lib/kind/core/maybe/some.rb +0 -90
  94. data/lib/kind/core/maybe/typed.rb +0 -29
  95. data/lib/kind/core/try.rb +0 -34
  96. data/lib/kind/core/type_checker.rb +0 -87
  97. data/lib/kind/core/type_checkers.rb +0 -30
  98. data/lib/kind/core/utils/kind.rb +0 -61
  99. 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/u-case/blob/main/README.md
42
- 5.1.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
43
- 4.1.0 | https://github.com/serradura/u-case/blob/v4.x/README.md
44
- 3.1.0 | https://github.com/serradura/u-case/blob/v3.x/README.md
45
- 2.3.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
46
- 1.9.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
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}.value_or_empty()](#kindarrayhashstringsetvalue_or_empty)
62
- - [List of all type checkers](#list-of-all-type-checkers)
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 checkers](#creating-type-checkers)
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> module](#kindtype-module)
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
- | u-case | branch | ruby | activemodel |
123
+ | kind | branch | ruby | activemodel |
123
124
  | -------------- | ------- | -------- | -------------- |
124
125
  | unreleased | main | >= 2.1.0 | >= 3.2, <= 6.1 |
125
- | 5.1.0 | v5.x | >= 2.1.0 | >= 3.2, <= 6.1 |
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
  [⬆️ &nbsp;Back to Top](#table-of-contents-)
329
330
 
330
- ### Kind::{Array,Hash,String,Set}.value_or_empty()
331
+ ### Kind::{Array,Hash,String,Set}.empty_or()
331
332
 
332
- This method is available for some type checkers (`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
+ 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.value_or_empty({}) # []
335
- Kind::Array.value_or_empty({}).frozen? # true
335
+ Kind::Array.empty_or({}) # []
336
+ Kind::Array.empty_or({}).frozen? # true
336
337
  ```
337
338
 
338
339
  [⬆️ &nbsp;Back to Top](#table-of-contents-)
339
340
 
340
- ### List of all type checkers
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
  [⬆️ &nbsp;Back to Top](#table-of-contents-)
378
379
 
379
- ### Creating type checkers
380
+ ### Creating type handlers
380
381
 
381
- There are two ways to do this, you can create type checkers dynamically or defining a module.
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 checker can return its kind and its name
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 checker can return its kind and its name
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
  [⬆️ &nbsp;Back to Top](#table-of-contents-)
527
528
 
528
- #### Kind::<Type> module
529
+ #### Kind::<Type> object
529
530
 
530
- The idea here is to create a type checker inside of the `Kind` namespace, so to do this, you will only need to use pure Ruby. e.g:
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, TypeChecker
539
+ extend self, ::Kind::Object
539
540
 
540
- # Define the expected kind of this type checker.
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 checker (like: `Kind::Symbol`).
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 checker 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
+ 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
  [⬆️ &nbsp;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, < 6.1.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
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 checkers as the expected kinds.
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 'set'
4
- require 'ostruct'
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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ UNDEFINED = Object.new.tap do |undefined|
5
+ def undefined.inspect
6
+ @inspect ||= 'UNDEFINED'.freeze
7
+ end
8
+
9
+ undefined.inspect
10
+ undefined.freeze
11
+ end
12
+
13
+ private_constant :UNDEFINED
14
+ end
@@ -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