kind 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
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