cuprum 0.8.0 → 0.9.0.beta.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 +5 -5
- data/CHANGELOG.md +41 -0
- data/DEVELOPMENT.md +14 -10
- data/README.md +197 -337
- data/lib/cuprum.rb +0 -25
- data/lib/cuprum/built_in/identity_command.rb +4 -4
- data/lib/cuprum/chaining.rb +119 -149
- data/lib/cuprum/command.rb +16 -14
- data/lib/cuprum/error.rb +37 -0
- data/lib/cuprum/errors/command_not_implemented.rb +35 -0
- data/lib/cuprum/errors/operation_not_called.rb +35 -0
- data/lib/cuprum/operation.rb +30 -27
- data/lib/cuprum/processing.rb +47 -80
- data/lib/cuprum/result.rb +52 -127
- data/lib/cuprum/rspec.rb +8 -0
- data/lib/cuprum/rspec/be_a_result.rb +19 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +271 -0
- data/lib/cuprum/version.rb +3 -3
- metadata +11 -9
- data/lib/cuprum/errors/process_not_implemented_error.rb +0 -14
- data/lib/cuprum/result_helpers.rb +0 -113
- data/lib/cuprum/utils/result_not_empty_warning.rb +0 -72
data/lib/cuprum/command.rb
CHANGED
@@ -32,7 +32,7 @@ module Cuprum
|
|
32
32
|
#
|
33
33
|
# result.value #=> 15
|
34
34
|
#
|
35
|
-
# @example A Command with
|
35
|
+
# @example A Command with an error state
|
36
36
|
# class DivideCommand < Cuprum::Command
|
37
37
|
# def initialize divisor
|
38
38
|
# @divisor = divisor
|
@@ -42,10 +42,8 @@ module Cuprum
|
|
42
42
|
#
|
43
43
|
# def process int
|
44
44
|
# if @divisor.zero?
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# return
|
48
|
-
# end # if
|
45
|
+
# return Cuprum::Result.new(error: 'errors.messages.divide_by_zero')
|
46
|
+
# end
|
49
47
|
#
|
50
48
|
# int / @divisor
|
51
49
|
# end # method process
|
@@ -54,14 +52,14 @@ module Cuprum
|
|
54
52
|
# halve_command = DivideCommand.new(2)
|
55
53
|
# result = halve_command.call(10)
|
56
54
|
#
|
57
|
-
# result.
|
58
|
-
# result.value
|
55
|
+
# result.error #=> nil
|
56
|
+
# result.value #=> 5
|
59
57
|
#
|
60
|
-
#
|
61
|
-
# result
|
58
|
+
# divide_command = DivideCommand.new(0)
|
59
|
+
# result = divide_command.call(10)
|
62
60
|
#
|
63
|
-
# result.
|
64
|
-
# result.value
|
61
|
+
# result.error #=> 'errors.messages.divide_by_zero'
|
62
|
+
# result.value #=> nil
|
65
63
|
#
|
66
64
|
# @example Command Chaining
|
67
65
|
# class AddCommand < Cuprum::Command
|
@@ -86,9 +84,9 @@ module Cuprum
|
|
86
84
|
# private
|
87
85
|
#
|
88
86
|
# def process int
|
89
|
-
#
|
87
|
+
# return int if int.even?
|
90
88
|
#
|
91
|
-
#
|
89
|
+
# Cuprum::Errors.new(error: 'errors.messages.not_even')
|
92
90
|
# end # method process
|
93
91
|
# end # class
|
94
92
|
#
|
@@ -122,7 +120,11 @@ module Cuprum
|
|
122
120
|
# value of the block. This overrides the implementation in #process, if
|
123
121
|
# any.
|
124
122
|
def initialize &implementation
|
125
|
-
|
123
|
+
return unless implementation
|
124
|
+
|
125
|
+
define_singleton_method :process, &implementation
|
126
|
+
|
127
|
+
singleton_class.send(:private, :process)
|
126
128
|
end # method initialize
|
127
129
|
end # class
|
128
130
|
end # module
|
data/lib/cuprum/error.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum'
|
4
|
+
|
5
|
+
module Cuprum
|
6
|
+
# Wrapper class for encapsulating an error state for a failed Cuprum result.
|
7
|
+
# Additional details can be passed by setting the #message or by using a
|
8
|
+
# subclass of Cuprum::Error.
|
9
|
+
class Error
|
10
|
+
COMPARABLE_PROPERTIES = %i[message].freeze
|
11
|
+
private_constant :COMPARABLE_PROPERTIES
|
12
|
+
|
13
|
+
# @param message [String] Optional message describing the nature of the
|
14
|
+
# error.
|
15
|
+
def initialize(message: nil)
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] Optional message describing the nature of the error.
|
20
|
+
attr_reader :message
|
21
|
+
|
22
|
+
# @param other [Cuprum::Error] The other object to compare.
|
23
|
+
#
|
24
|
+
# @return [Boolean] true if the other object has the same class and message;
|
25
|
+
# otherwise false.
|
26
|
+
def ==(other)
|
27
|
+
other.instance_of?(self.class) &&
|
28
|
+
comparable_properties.all? { |prop| send(prop) == other.send(prop) }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def comparable_properties
|
34
|
+
self.class.const_get(:COMPARABLE_PROPERTIES)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
require 'cuprum/errors'
|
5
|
+
|
6
|
+
module Cuprum::Errors
|
7
|
+
# Error class to be used when a Command is called without defining a #process
|
8
|
+
# method.
|
9
|
+
class CommandNotImplemented < Cuprum::Error
|
10
|
+
COMPARABLE_PROPERTIES = %i[command].freeze
|
11
|
+
private_constant :COMPARABLE_PROPERTIES
|
12
|
+
|
13
|
+
# Format for generating error message.
|
14
|
+
MESSAGE_FORMAT = 'no implementation defined for %s'
|
15
|
+
|
16
|
+
# @param command [Cuprum::Command] The command called without a definition.
|
17
|
+
def initialize(command:)
|
18
|
+
@command = command
|
19
|
+
|
20
|
+
class_name = command&.class&.name || 'command'
|
21
|
+
message = MESSAGE_FORMAT % class_name
|
22
|
+
|
23
|
+
super(message: message)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Cuprum::Command] The command called without a definition.
|
27
|
+
attr_reader :command
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def comparable_properties
|
32
|
+
COMPARABLE_PROPERTIES
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
require 'cuprum/errors'
|
5
|
+
|
6
|
+
module Cuprum::Errors
|
7
|
+
# Error class to be used when trying to access the result of an uncalled
|
8
|
+
# Operation.
|
9
|
+
class OperationNotCalled < Cuprum::Error
|
10
|
+
COMPARABLE_PROPERTIES = %i[operation].freeze
|
11
|
+
private_constant :COMPARABLE_PROPERTIES
|
12
|
+
|
13
|
+
# Format for generating error message.
|
14
|
+
MESSAGE_FORMAT = '%s was not called and does not have a result'
|
15
|
+
|
16
|
+
# @param operation [Cuprum::Operation] The uncalled operation.
|
17
|
+
def initialize(operation:)
|
18
|
+
@operation = operation
|
19
|
+
|
20
|
+
class_name = operation&.class&.name || 'operation'
|
21
|
+
message = MESSAGE_FORMAT % class_name
|
22
|
+
|
23
|
+
super(message: message)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Cuprum::Operation] The uncalled operation.
|
27
|
+
attr_reader :operation
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def comparable_properties
|
32
|
+
COMPARABLE_PROPERTIES
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/cuprum/operation.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'cuprum/command'
|
2
|
+
require 'cuprum/errors/operation_not_called'
|
2
3
|
|
3
4
|
module Cuprum
|
4
5
|
# Functional object with syntactic sugar for tracking the last result.
|
@@ -45,7 +46,6 @@ module Cuprum
|
|
45
46
|
# @return [Cuprum::Result] The result from the most recent call of the
|
46
47
|
# operation.
|
47
48
|
attr_reader :result
|
48
|
-
alias_method :to_result, :result
|
49
49
|
|
50
50
|
# @overload call(*arguments, **keywords, &block)
|
51
51
|
# Executes the logic encoded in the constructor block, or the #process
|
@@ -61,10 +61,6 @@ module Cuprum
|
|
61
61
|
# @yield If a block argument is given, it will be passed to the
|
62
62
|
# implementation.
|
63
63
|
#
|
64
|
-
# @raise [Cuprum::Errors::ProcessNotImplementedError] Unless a block was
|
65
|
-
# passed to the constructor or the #process method was overriden by a
|
66
|
-
# Command subclass.
|
67
|
-
#
|
68
64
|
# @see Cuprum::Command#call
|
69
65
|
def call *args, &block
|
70
66
|
reset! if called? # Clear reference to most recent result.
|
@@ -80,23 +76,18 @@ module Cuprum
|
|
80
76
|
!result.nil?
|
81
77
|
end # method called?
|
82
78
|
|
83
|
-
# @return [
|
84
|
-
# operation has not been called.
|
85
|
-
def
|
86
|
-
called? ? result.
|
87
|
-
end # method
|
79
|
+
# @return [Object] the error (if any) from the most recent result, or nil
|
80
|
+
# if the operation has not been called.
|
81
|
+
def error
|
82
|
+
called? ? result.error : nil
|
83
|
+
end # method error
|
88
84
|
|
89
|
-
# @return [Boolean] true if the most recent result had
|
90
|
-
# the most recent result had no
|
91
|
-
# called.
|
85
|
+
# @return [Boolean] true if the most recent result had an error, or false
|
86
|
+
# if the most recent result had no error or if the operation has not
|
87
|
+
# been called.
|
92
88
|
def failure?
|
93
89
|
called? ? result.failure? : false
|
94
|
-
end # method
|
95
|
-
|
96
|
-
# @return [Boolean] true if the most recent was halted, otherwise false.
|
97
|
-
def halted?
|
98
|
-
called? ? result.halted? : false
|
99
|
-
end # method halted?
|
90
|
+
end # method failure?
|
100
91
|
|
101
92
|
# Clears the reference to the most recent call of the operation, if any.
|
102
93
|
# This allows the result and any referenced data to be garbage collected.
|
@@ -110,13 +101,25 @@ module Cuprum
|
|
110
101
|
@result = nil
|
111
102
|
end # method reset
|
112
103
|
|
113
|
-
# @return [Boolean] true if the most recent result had no
|
114
|
-
# if the most recent result had
|
115
|
-
# called.
|
104
|
+
# @return [Boolean] true if the most recent result had no error, or false
|
105
|
+
# if the most recent result had an error or if the operation has not
|
106
|
+
# been called.
|
116
107
|
def success?
|
117
108
|
called? ? result.success? : false
|
118
109
|
end # method success?
|
119
110
|
|
111
|
+
# Returns the most result if the operation was previously called.
|
112
|
+
# Otherwise, returns a failing result.
|
113
|
+
#
|
114
|
+
# @return [Cuprum::Result] the most recent result or failing result.
|
115
|
+
def to_cuprum_result
|
116
|
+
return result if result
|
117
|
+
|
118
|
+
error = Cuprum::Errors::OperationNotCalled.new(operation: self)
|
119
|
+
|
120
|
+
Cuprum::Result.new(error: error)
|
121
|
+
end
|
122
|
+
|
120
123
|
# @return [Object] the value of the most recent result, or nil if the
|
121
124
|
# operation has not been called.
|
122
125
|
def value
|
@@ -131,15 +134,12 @@ module Cuprum
|
|
131
134
|
# @!method called?
|
132
135
|
# (see Cuprum::Operation::Mixin#called?)
|
133
136
|
|
134
|
-
# @!method
|
135
|
-
# (see Cuprum::Operation::Mixin#
|
137
|
+
# @!method error
|
138
|
+
# (see Cuprum::Operation::Mixin#error)
|
136
139
|
|
137
140
|
# @!method failure?
|
138
141
|
# (see Cuprum::Operation::Mixin#failure?)
|
139
142
|
|
140
|
-
# @!method halted?
|
141
|
-
# (see Cuprum::Operation::Mixin#halted?)
|
142
|
-
|
143
143
|
# @!method reset!
|
144
144
|
# (see Cuprum::Operation::Mixin#reset!)
|
145
145
|
|
@@ -149,6 +149,9 @@ module Cuprum
|
|
149
149
|
# @!method success?
|
150
150
|
# (see Cuprum::Operation::Mixin#success?)
|
151
151
|
|
152
|
+
# @!method to_cuprum_result
|
153
|
+
# (see Cuprum::Operation::Mixin#to_cuprum_result?)
|
154
|
+
|
152
155
|
# @!method value
|
153
156
|
# (see Cuprum::Operation::Mixin#value)
|
154
157
|
end # class
|
data/lib/cuprum/processing.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/errors/command_not_implemented'
|
3
4
|
|
4
5
|
module Cuprum
|
5
6
|
# Functional implementation for creating a command object. Cuprum::Processing
|
@@ -12,14 +13,14 @@ module Cuprum
|
|
12
13
|
#
|
13
14
|
# def initialize addend
|
14
15
|
# @addend = addend
|
15
|
-
# end
|
16
|
+
# end
|
16
17
|
#
|
17
18
|
# private
|
18
19
|
#
|
19
20
|
# def process int
|
20
21
|
# int + addend
|
21
|
-
# end
|
22
|
-
# end
|
22
|
+
# end
|
23
|
+
# end
|
23
24
|
#
|
24
25
|
# adder = AdderCommand.new(2)
|
25
26
|
# result = adder.call(3)
|
@@ -35,32 +36,27 @@ module Cuprum
|
|
35
36
|
#
|
36
37
|
# def process value
|
37
38
|
# if value.negative?
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# return nil
|
41
|
-
# end # if
|
39
|
+
# return Cuprum::Result.new(error: 'value cannot be negative')
|
40
|
+
# end
|
42
41
|
#
|
43
42
|
# Math.sqrt(value)
|
44
|
-
# end
|
45
|
-
# end
|
43
|
+
# end
|
44
|
+
# end
|
46
45
|
#
|
47
46
|
# result = SquareRootCommand.new.call(2)
|
48
47
|
# result.value #=> 1.414
|
49
48
|
# result.success? #=> true
|
50
49
|
# result.failure? #=> false
|
51
|
-
# result.
|
50
|
+
# result.error #=> nil
|
52
51
|
#
|
53
52
|
# result = SquareRootCommand.new.call(-1)
|
54
53
|
# result.value #=> nil
|
55
54
|
# result.success? #=> false
|
56
55
|
# result.failure? #=> true
|
57
|
-
# result.
|
56
|
+
# result.error #=> 'value cannot be negative'
|
58
57
|
#
|
59
58
|
# @see Cuprum::Command
|
60
59
|
module Processing
|
61
|
-
VALUE_METHODS = %i[to_result value success?].freeze
|
62
|
-
private_constant :VALUE_METHODS
|
63
|
-
|
64
60
|
# Returns a nonnegative integer for commands that take a fixed number of
|
65
61
|
# arguments. For commands that take a variable number of arguments, returns
|
66
62
|
# -n-1, where n is the number of required arguments.
|
@@ -68,7 +64,7 @@ module Cuprum
|
|
68
64
|
# @return [Integer] The number of arguments.
|
69
65
|
def arity
|
70
66
|
method(:process).arity
|
71
|
-
end
|
67
|
+
end
|
72
68
|
|
73
69
|
# @overload call(*arguments, **keywords, &block)
|
74
70
|
# Executes the command implementation and returns a Cuprum::Result or
|
@@ -76,14 +72,12 @@ module Cuprum
|
|
76
72
|
#
|
77
73
|
# Each time #call is invoked, the object performs the following steps:
|
78
74
|
#
|
79
|
-
# 1.
|
80
|
-
#
|
81
|
-
# 2.
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# Otherwise, the return value of #process is assigned to the #value
|
86
|
-
# property of the result, and the result is returned by #call.
|
75
|
+
# 1. The #process method is called, passing the arguments, keywords, and
|
76
|
+
# block that were passed to #call.
|
77
|
+
# 2. If the value returned by #process is a Cuprum::Result or compatible
|
78
|
+
# object, that result is directly returned by #call.
|
79
|
+
# 3. Otherwise, the value returned by #process will be wrapped in a
|
80
|
+
# successful result, which will be returned by #call.
|
87
81
|
#
|
88
82
|
# @param arguments [Array] Arguments to be passed to the implementation.
|
89
83
|
#
|
@@ -93,43 +87,31 @@ module Cuprum
|
|
93
87
|
#
|
94
88
|
# @yield If a block argument is given, it will be passed to the
|
95
89
|
# implementation.
|
96
|
-
|
97
|
-
|
98
|
-
# method was overriden.
|
99
|
-
def call *args, &block
|
100
|
-
process_with_result(build_result, *args, &block)
|
101
|
-
end # method call
|
102
|
-
|
103
|
-
private
|
90
|
+
def call(*args, &block)
|
91
|
+
value = process(*args, &block)
|
104
92
|
|
105
|
-
|
106
|
-
# is being called.
|
107
|
-
attr_reader :result
|
93
|
+
return value.to_cuprum_result if value_is_result?(value)
|
108
94
|
|
109
|
-
|
110
|
-
|
111
|
-
end # method build_result
|
95
|
+
build_result(value: value)
|
96
|
+
end
|
112
97
|
|
113
|
-
|
114
|
-
if value_is_result?(other)
|
115
|
-
return result if result == other
|
116
|
-
|
117
|
-
warn_unless_empty!(result)
|
98
|
+
private
|
118
99
|
|
119
|
-
|
120
|
-
|
121
|
-
|
100
|
+
def build_result(error: nil, status: nil, value: nil)
|
101
|
+
Cuprum::Result.new(error: error, status: status, value: value)
|
102
|
+
end
|
122
103
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
104
|
+
def failure(error)
|
105
|
+
build_result(error: error)
|
106
|
+
end
|
126
107
|
|
127
108
|
# @!visibility public
|
128
109
|
# @overload process(*arguments, **keywords, &block)
|
129
110
|
# The implementation of the command, to be executed when the #call method
|
130
|
-
# is called.
|
131
|
-
#
|
132
|
-
#
|
111
|
+
# is called. If #process returns a result, that result will be returned by
|
112
|
+
# #call; otherwise, the value returned by #process will be wrapped in a
|
113
|
+
# successful Cuprum::Result object. This method should not be called
|
114
|
+
# directly.
|
133
115
|
#
|
134
116
|
# @param arguments [Array] The arguments, if any, passed from #call.
|
135
117
|
#
|
@@ -139,34 +121,19 @@ module Cuprum
|
|
139
121
|
#
|
140
122
|
# @return [Object] the value of the result object to be returned by #call.
|
141
123
|
#
|
142
|
-
# @raise [Cuprum::Errors::ProcessNotImplementedError] Unless a block was
|
143
|
-
# passed to the constructor or the #process method was overriden by a
|
144
|
-
# Command subclass.
|
145
|
-
#
|
146
124
|
# @note This is a private method.
|
147
|
-
def process
|
148
|
-
|
149
|
-
end # method process
|
150
|
-
|
151
|
-
def process_with_result result, *args, &block
|
152
|
-
@result = result
|
153
|
-
value = process(*args, &block)
|
154
|
-
|
155
|
-
merge_results(result, value)
|
156
|
-
ensure
|
157
|
-
@result = nil
|
158
|
-
end # method process_with_result
|
159
|
-
|
160
|
-
def value_is_result? value
|
161
|
-
VALUE_METHODS.all? { |method_name| value.respond_to?(method_name) }
|
162
|
-
end # method value
|
125
|
+
def process(*_args)
|
126
|
+
error = Cuprum::Errors::CommandNotImplemented.new(command: self)
|
163
127
|
|
164
|
-
|
165
|
-
|
128
|
+
build_result(error: error)
|
129
|
+
end
|
166
130
|
|
167
|
-
|
131
|
+
def success(value)
|
132
|
+
build_result(value: value)
|
133
|
+
end
|
168
134
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
135
|
+
def value_is_result?(value)
|
136
|
+
value.respond_to?(:to_cuprum_result)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|