cuprum 0.8.0 → 0.10.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.
@@ -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
@@ -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,12 +61,8 @@ 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
- def call *args, &block
65
+ def call *args, **kwargs, &block
70
66
  reset! if called? # Clear reference to most recent result.
71
67
 
72
68
  @result = super
@@ -80,23 +76,18 @@ module Cuprum
80
76
  !result.nil?
81
77
  end # method called?
82
78
 
83
- # @return [Array] the errors from the most recent result, or nil if the
84
- # operation has not been called.
85
- def errors
86
- called? ? result.errors : nil
87
- end # method errors
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 errors, or false if
90
- # the most recent result had no errors or if the operation has not been
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 success?
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,31 @@ module Cuprum
110
101
  @result = nil
111
102
  end # method reset
112
103
 
113
- # @return [Boolean] true if the most recent result had no errors, or false
114
- # if the most recent result had errors or if the operation has not been
115
- # called.
104
+ # @return [Symbol, nil] the status of the most recent result, or nil if
105
+ # the operation has not been called.
106
+ def status
107
+ called? ? result.status : nil
108
+ end
109
+
110
+ # @return [Boolean] true if the most recent result had no error, or false
111
+ # if the most recent result had an error or if the operation has not
112
+ # been called.
116
113
  def success?
117
114
  called? ? result.success? : false
118
115
  end # method success?
119
116
 
117
+ # Returns the most result if the operation was previously called.
118
+ # Otherwise, returns a failing result.
119
+ #
120
+ # @return [Cuprum::Result] the most recent result or failing result.
121
+ def to_cuprum_result
122
+ return result if result
123
+
124
+ error = Cuprum::Errors::OperationNotCalled.new(operation: self)
125
+
126
+ Cuprum::Result.new(error: error)
127
+ end
128
+
120
129
  # @return [Object] the value of the most recent result, or nil if the
121
130
  # operation has not been called.
122
131
  def value
@@ -131,15 +140,12 @@ module Cuprum
131
140
  # @!method called?
132
141
  # (see Cuprum::Operation::Mixin#called?)
133
142
 
134
- # @!method errors
135
- # (see Cuprum::Operation::Mixin#errors)
143
+ # @!method error
144
+ # (see Cuprum::Operation::Mixin#error)
136
145
 
137
146
  # @!method failure?
138
147
  # (see Cuprum::Operation::Mixin#failure?)
139
148
 
140
- # @!method halted?
141
- # (see Cuprum::Operation::Mixin#halted?)
142
-
143
149
  # @!method reset!
144
150
  # (see Cuprum::Operation::Mixin#reset!)
145
151
 
@@ -149,6 +155,9 @@ module Cuprum
149
155
  # @!method success?
150
156
  # (see Cuprum::Operation::Mixin#success?)
151
157
 
158
+ # @!method to_cuprum_result
159
+ # (see Cuprum::Operation::Mixin#to_cuprum_result?)
160
+
152
161
  # @!method value
153
162
  # (see Cuprum::Operation::Mixin#value)
154
163
  end # class
@@ -1,5 +1,7 @@
1
- require 'cuprum/errors/process_not_implemented_error'
2
- require 'cuprum/utils/result_not_empty_warning'
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/errors/command_not_implemented'
4
+ require 'cuprum/result_helpers'
3
5
 
4
6
  module Cuprum
5
7
  # Functional implementation for creating a command object. Cuprum::Processing
@@ -12,14 +14,14 @@ module Cuprum
12
14
  #
13
15
  # def initialize addend
14
16
  # @addend = addend
15
- # end # constructor
17
+ # end
16
18
  #
17
19
  # private
18
20
  #
19
21
  # def process int
20
22
  # int + addend
21
- # end # method process
22
- # end # class AdderCommand
23
+ # end
24
+ # end
23
25
  #
24
26
  # adder = AdderCommand.new(2)
25
27
  # result = adder.call(3)
@@ -35,31 +37,28 @@ module Cuprum
35
37
  #
36
38
  # def process value
37
39
  # if value.negative?
38
- # result.errors << 'value cannot be negative'
39
- #
40
- # return nil
41
- # end # if
40
+ # return Cuprum::Result.new(error: 'value cannot be negative')
41
+ # end
42
42
  #
43
43
  # Math.sqrt(value)
44
- # end # method process
45
- # end # class
44
+ # end
45
+ # end
46
46
  #
47
47
  # result = SquareRootCommand.new.call(2)
48
48
  # result.value #=> 1.414
49
49
  # result.success? #=> true
50
50
  # result.failure? #=> false
51
- # result.errors #=> []
51
+ # result.error #=> nil
52
52
  #
53
53
  # result = SquareRootCommand.new.call(-1)
54
54
  # result.value #=> nil
55
55
  # result.success? #=> false
56
56
  # result.failure? #=> true
57
- # result.errors #=> ['value cannot be negative']
57
+ # result.error #=> 'value cannot be negative'
58
58
  #
59
59
  # @see Cuprum::Command
60
60
  module Processing
61
- VALUE_METHODS = %i[to_result value success?].freeze
62
- private_constant :VALUE_METHODS
61
+ include Cuprum::ResultHelpers
63
62
 
64
63
  # Returns a nonnegative integer for commands that take a fixed number of
65
64
  # arguments. For commands that take a variable number of arguments, returns
@@ -68,7 +67,7 @@ module Cuprum
68
67
  # @return [Integer] The number of arguments.
69
68
  def arity
70
69
  method(:process).arity
71
- end # method arity
70
+ end
72
71
 
73
72
  # @overload call(*arguments, **keywords, &block)
74
73
  # Executes the command implementation and returns a Cuprum::Result or
@@ -76,14 +75,12 @@ module Cuprum
76
75
  #
77
76
  # Each time #call is invoked, the object performs the following steps:
78
77
  #
79
- # 1. Creates a result object, typically an instance of Cuprum::Result.
80
- # The result is assigned to the command as the private #result reader.
81
- # 2. The #process method is called, passing the arguments, keywords, and
82
- # block that were passed to #call. The #process method can set errors,
83
- # set the status, or halt the result via the #result reader method.
84
- # 3. If #process returns a result, that result is returned by #call.
85
- # Otherwise, the return value of #process is assigned to the #value
86
- # property of the result, and the result is returned by #call.
78
+ # 1. The #process method is called, passing the arguments, keywords, and
79
+ # block that were passed to #call.
80
+ # 2. If the value returned by #process is a Cuprum::Result or compatible
81
+ # object, that result is directly returned by #call.
82
+ # 3. Otherwise, the value returned by #process will be wrapped in a
83
+ # successful result, which will be returned by #call.
87
84
  #
88
85
  # @param arguments [Array] Arguments to be passed to the implementation.
89
86
  #
@@ -93,43 +90,28 @@ module Cuprum
93
90
  #
94
91
  # @yield If a block argument is given, it will be passed to the
95
92
  # implementation.
96
- #
97
- # @raise [Cuprum::Errors::ProcessNotImplementedError] Unless the #process
98
- # method was overriden.
99
- def call *args, &block
100
- process_with_result(build_result, *args, &block)
101
- end # method call
102
-
103
- private
104
-
105
- # @return [Cuprum::Result] The current result. Only available while #process
106
- # is being called.
107
- attr_reader :result
108
-
109
- def build_result value = nil, **options
110
- Cuprum::Result.new(value, options)
111
- end # method build_result
93
+ def call(*args, **kwargs, &block)
94
+ value =
95
+ if kwargs.empty?
96
+ process(*args, &block)
97
+ else
98
+ process(*args, **kwargs, &block)
99
+ end
112
100
 
113
- def merge_results result, other
114
- if value_is_result?(other)
115
- return result if result == other
101
+ return value.to_cuprum_result if value_is_result?(value)
116
102
 
117
- warn_unless_empty!(result)
103
+ build_result(value: value)
104
+ end
118
105
 
119
- other.to_result
120
- else
121
- result.value = other
122
-
123
- result
124
- end # if-else
125
- end # method merge_results
106
+ private
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. Can add errors to or set the status of the result, and the
131
- # value of the result will be set to the value returned by #process. Do
132
- # not call this method directly.
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,15 @@ 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 *_args
148
- raise Cuprum::Errors::ProcessNotImplementedError, nil, caller(1..-1)
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
163
-
164
- def warn_unless_empty! result
165
- return unless result.respond_to?(:empty?) && !result.empty?
125
+ def process(*_args)
126
+ error = Cuprum::Errors::CommandNotImplemented.new(command: self)
166
127
 
167
- not_empty = Cuprum::Utils::ResultNotEmptyWarning.new(result)
128
+ build_result(error: error)
129
+ end
168
130
 
169
- Cuprum.warn(not_empty.message) if not_empty.warning?
170
- end # method warn_unless_empty!
171
- end # module
172
- end # module
131
+ def value_is_result?(value)
132
+ value.respond_to?(:to_cuprum_result)
133
+ end
134
+ end
135
+ end
@@ -1,166 +1,91 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cuprum'
2
4
 
3
5
  module Cuprum
4
6
  # Data object that encapsulates the result of calling a Cuprum command.
5
7
  class Result
8
+ STATUSES = %i[success failure].freeze
9
+
6
10
  # @param value [Object] The value returned by calling the command.
7
- # @param errors [Array] The errors (if any) generated when the command was
8
- # called.
9
- def initialize value = nil, errors: nil
11
+ # @param error [Object] The error (if any) generated when the command was
12
+ # called. Can be a Cuprum::Error, a model errors object, etc.
13
+ # @param status [String, Symbol] The status of the result. Must be :success,
14
+ # :failure, or nil.
15
+ def initialize(value: nil, error: nil, status: nil)
10
16
  @value = value
11
- @errors = errors.nil? ? build_errors : errors
12
- @status = nil
13
- @halted = false
14
- end # constructor
17
+ @error = error
18
+ @status = resolve_status(status)
19
+ end
15
20
 
16
21
  # @return [Object] the value returned by calling the command.
17
- attr_accessor :value
22
+ attr_reader :value
18
23
 
19
- # @return [Array] the errors (if any) generated when the command was
24
+ # @return [Object] the error (if any) generated when the command was
20
25
  # called.
21
- attr_accessor :errors
26
+ attr_reader :error
27
+
28
+ # @return [Symbol] the status of the result, either :success or :failure.
29
+ attr_reader :status
22
30
 
23
- # rubocop:disable Metrics/AbcSize
24
31
  # rubocop:disable Metrics/CyclomaticComplexity
25
- # rubocop:disable Metrics/MethodLength
26
- # rubocop:disable Metrics/PerceivedComplexity
27
32
 
28
33
  # Compares the other object to the result.
29
34
  #
30
35
  # @param other [#value, #success?] An object responding to, at minimum,
31
- # #value and #success?. If present, the #failure?, #errors and #halted?
32
- # values will also be compared.
36
+ # #value and #success?. If present, the #failure? and #error values
37
+ # will also be compared.
33
38
  #
34
39
  # @return [Boolean] True if all present values match the result, otherwise
35
40
  # false.
36
- def == other
37
- return false unless other.respond_to?(:value) && other.value == value
38
-
39
- unless other.respond_to?(:success?) && other.success? == success?
40
- return false
41
- end # unless
42
-
43
- if other.respond_to?(:failure?) && other.failure? != failure?
44
- return false
45
- end # if
46
-
47
- if other.respond_to?(:errors) && other.errors != errors
48
- return false
49
- end # if
50
-
51
- if other.respond_to?(:halted?) && other.halted? != halted?
52
- return false
53
- end # if
41
+ def ==(other)
42
+ return false unless other.respond_to?(:value) && other.value == value
43
+ return false unless other.respond_to?(:status) && other.status == status
44
+ return false unless other.respond_to?(:error) && other.error == error
54
45
 
55
46
  true
56
- end # method ==
57
- # rubocop:enable Metrics/AbcSize
47
+ end
58
48
  # rubocop:enable Metrics/CyclomaticComplexity
59
- # rubocop:enable Metrics/MethodLength
60
- # rubocop:enable Metrics/PerceivedComplexity
61
-
62
- # @return [Boolean] true if the result is empty, i.e. has no value or errors
63
- # and does not have its status set or is halted.
64
- def empty?
65
- value.nil? && errors.empty? && @status.nil? && !halted?
66
- end # method empty?
67
49
 
68
- # Marks the result as a failure, whether or not the command generated any
69
- # errors.
70
- #
71
- # @return [Cuprum::Result] The result.
72
- def failure!
73
- @status = :failure
74
-
75
- self
76
- end # method failure!
77
-
78
- # @return [Boolean] false if the command did not generate any errors,
79
- # otherwise true.
50
+ # @return [Boolean] true if the result status is :failure, otherwise false.
80
51
  def failure?
81
- @status == :failure || (@status.nil? && !errors.empty?)
82
- end # method failure?
52
+ @status == :failure
53
+ end
83
54
 
84
- # Marks the result as halted. Any subsequent chained commands will not be
85
- # run.
86
- #
87
- # @return [Cuprum::Result] The result.
88
- def halt!
89
- @halted = true
90
-
91
- self
92
- end # method halt!
93
-
94
- # @return [Boolean] true if the command has been halted, and will not run
95
- # any subsequent chained commands.
96
- def halted?
97
- @halted
98
- end # method halted?
99
-
100
- # Marks the result as a success, whether or not the command generated any
101
- # errors.
102
- #
103
- # @return [Cuprum::Result] The result.
104
- def success!
105
- @status = :success
106
-
107
- self
108
- end # method success!
109
-
110
- # @return [Boolean] true if the command did not generate any errors,
111
- # otherwise false.
55
+ # @return [Boolean] true if the result status is :success, otherwise false.
112
56
  def success?
113
- @status == :success || (@status.nil? && errors.empty?)
114
- end # method success?
57
+ @status == :success
58
+ end
115
59
 
116
60
  # @return [Cuprum::Result] The result.
117
- def to_result
61
+ def to_cuprum_result
118
62
  self
119
- end # method to_result
120
-
121
- # @api private
122
- def update other_result
123
- return self if other_result.nil?
124
-
125
- self.value = other_result.value
63
+ end
126
64
 
127
- update_status(other_result)
128
-
129
- update_errors(other_result)
65
+ private
130
66
 
131
- halt! if other_result.halted?
67
+ def defined_statuses
68
+ self.class::STATUSES
69
+ end
132
70
 
133
- self
134
- end # method update
71
+ def normalize_status(status)
72
+ return status unless status.is_a?(String) || status.is_a?(Symbol)
135
73
 
136
- protected
137
-
138
- attr_reader :status
74
+ tools.string_tools.underscore(status).intern
75
+ end
139
76
 
140
- private
141
-
142
- # @!visibility public
143
- #
144
- # Generates an empty errors object. When the command is called, the result
145
- # will have its #errors property initialized to the value returned by
146
- # #build_errors. By default, this is an array. If you want to use a custom
147
- # errors object type, override this method in a subclass.
148
- #
149
- # @return [Array] An empty errors object.
150
- def build_errors
151
- []
152
- end # method build_errors
77
+ def resolve_status(status)
78
+ return error.nil? ? :success : :failure if status.nil?
153
79
 
154
- def update_errors other_result
155
- return if other_result.errors.empty?
80
+ normalized = normalize_status(status)
156
81
 
157
- @errors += other_result.errors
158
- end # method update_errors
82
+ return normalized if defined_statuses.include?(normalized)
159
83
 
160
- def update_status other_result
161
- return if status || !errors.empty?
84
+ raise ArgumentError, "invalid status #{status.inspect}"
85
+ end
162
86
 
163
- @status = other_result.status
164
- end # method update_status
165
- end # class
166
- end # module
87
+ def tools
88
+ SleepingKingStudios::Tools::Toolbelt.instance
89
+ end
90
+ end
91
+ end