cmdx 1.1.2 → 1.5.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
data/lib/cmdx/result.rb CHANGED
@@ -1,101 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Represents the execution result of a task, tracking state, status, and metadata.
4
+ # Represents the execution result of a CMDx task, tracking state transitions,
5
+ # status changes, and providing methods for handling different outcomes.
5
6
  #
6
- # Result objects encapsulate the outcome of task execution, providing detailed
7
- # information about execution state (initialized, executing, complete, interrupted),
8
- # status (success, skipped, failed), and associated metadata. They support
9
- # state transitions, status changes, and provide introspection capabilities
10
- # for debugging and monitoring task execution.
7
+ # The Result class manages the lifecycle of task execution from initialization
8
+ # through completion or interruption, offering a fluent interface for status
9
+ # checking and conditional handling.
11
10
  class Result
12
11
 
12
+ extend Forwardable
13
+
13
14
  STATES = [
14
15
  INITIALIZED = "initialized", # Initial state before execution
15
- EXECUTING = "executing", # Currently executing task logic
16
- COMPLETE = "complete", # Successfully completed execution
16
+ EXECUTING = "executing", # Currently executing task logic
17
+ COMPLETE = "complete", # Successfully completed execution
17
18
  INTERRUPTED = "interrupted" # Execution was halted due to failure
18
19
  ].freeze
19
20
  STATUSES = [
20
21
  SUCCESS = "success", # Task completed successfully
21
22
  SKIPPED = "skipped", # Task was skipped intentionally
22
- FAILED = "failed" # Task failed due to error or validation
23
+ FAILED = "failed" # Task failed due to error or validation
23
24
  ].freeze
24
25
 
25
- cmdx_attr_delegator :context, :chain,
26
- to: :task
27
-
28
- # @return [CMDx::Task] The task instance that generated this result
29
- attr_reader :task
30
-
31
- # @return [String] The current execution state (initialized, executing, complete, interrupted)
32
- attr_reader :state
26
+ STRIP_FAILURE = proc do |hash, result, key|
27
+ unless result.send(:"#{key}?")
28
+ # Strip caused/threw failures since its the same info as the log line
29
+ hash[key] = result.send(key).to_h.except(:caused_failure, :threw_failure)
30
+ end
31
+ end.freeze
32
+ private_constant :STRIP_FAILURE
33
33
 
34
- # @return [String] The current execution status (success, skipped, failed)
35
- attr_reader :status
34
+ attr_reader :task, :state, :status, :metadata, :reason, :cause
36
35
 
37
- # @return [Hash] Additional metadata associated with the result
38
- attr_reader :metadata
36
+ def_delegators :task, :context, :chain
39
37
 
40
- # Initializes a new result for the given task
41
- #
42
- # @param task [CMDx::Task] the task to create a result for
38
+ # @param task [CMDx::Task] The task instance this result represents
43
39
  #
44
- # @return [CMDx::Result] a new result instance
40
+ # @return [CMDx::Result] A new result instance for the task
45
41
  #
46
- # @raise [TypeError] if task is not a Task or Workflow
42
+ # @raise [TypeError] When task is not a CMDx::Task instance
47
43
  #
48
- # @example Create a result for a task
44
+ # @example
49
45
  # result = CMDx::Result.new(my_task)
50
- # result.state #=> "initialized"
51
- # result.status #=> "success"
46
+ # result.state # => "initialized"
52
47
  def initialize(task)
53
- raise TypeError, "must be a Task or Workflow" unless task.is_a?(Task)
48
+ raise TypeError, "must be a CMDx::Task" unless task.is_a?(CMDx::Task)
54
49
 
55
- @task = task
56
- @state = INITIALIZED
57
- @status = SUCCESS
50
+ @task = task
51
+ @state = INITIALIZED
52
+ @status = SUCCESS
58
53
  @metadata = {}
54
+ @reason = nil
55
+ @cause = nil
59
56
  end
60
57
 
61
58
  STATES.each do |s|
62
- # Checks if the result is in the specified state.
63
- #
64
- # @return [Boolean] true if the result matches the state
65
- #
66
- # @example Check if result is initialized
67
- # result.initialized? #=> true
68
- #
69
- # @example Check if result is executing
70
- # result.executing? #=> false
71
- #
72
- # @example Check if result is complete
73
- # result.complete? #=> false
59
+ # @return [Boolean] Whether the result is in the specified state
74
60
  #
75
- # @example Check if result is interrupted
76
- # result.interrupted? #=> false
61
+ # @example
62
+ # result.initialized? # => true
63
+ # result.executing? # => false
77
64
  define_method(:"#{s}?") { state == s }
78
65
 
79
- # Executes the provided block if the result is in the specified state.
66
+ # @param block [Proc] Block to execute conditionally
80
67
  #
81
- # @param block [Proc] the block to execute if result matches the state
68
+ # @yield [self] Executes the block if result is in specified state
82
69
  #
83
- # @return [Result] returns self for method chaining
70
+ # @return [self] Returns self for method chaining
84
71
  #
85
- # @raise [ArgumentError] if no block is provided
72
+ # @raise [ArgumentError] When no block is provided
86
73
  #
87
- # @example Handle initialized state
88
- # result.on_initialized { |r| puts "Task is ready to start" }
89
- #
90
- # @example Handle executing state
91
- # result.on_executing { |r| puts "Task is currently running" }
92
- #
93
- # @example Handle complete state
94
- # result.on_complete { |r| puts "Task finished successfully" }
95
- #
96
- # @example Handle interrupted state
97
- # result.on_interrupted { |r| puts "Task was interrupted" }
98
- define_method(:"on_#{s}") do |&block|
74
+ # @example
75
+ # result.handle_initialized { |r| puts "Starting execution" }
76
+ # result.handle_complete { |r| puts "Task completed" }
77
+ define_method(:"handle_#{s}") do |&block|
99
78
  raise ArgumentError, "block required" unless block
100
79
 
101
80
  block.call(self) if send(:"#{s}?")
@@ -103,61 +82,43 @@ module CMDx
103
82
  end
104
83
  end
105
84
 
106
- # Marks the result as executed by transitioning to complete or interrupted state
107
- # based on the current status
108
- #
109
- # @return [void]
85
+ # @return [self] Returns self for method chaining
110
86
  #
111
- # @example Mark successful task as executed
112
- # result.executed!
113
- # result.complete? #=> true
114
- #
115
- # @example Mark failed task as executed
116
- # result.fail!
117
- # result.executed!
118
- # result.interrupted? #=> true
87
+ # @example
88
+ # result.executed! # Transitions to complete or interrupted
119
89
  def executed!
120
90
  success? ? complete! : interrupt!
121
91
  end
122
92
 
123
- # Checks if the result has been executed (either complete or interrupted)
124
- #
125
- # @return [Boolean] true if the result is complete or interrupted
93
+ # @return [Boolean] Whether the task has been executed (complete or interrupted)
126
94
  #
127
- # @example Check if result was executed
128
- # result.executed? #=> false
129
- # result.executed!
130
- # result.executed? #=> true
95
+ # @example
96
+ # result.executed? # => true if complete? || interrupted?
131
97
  def executed?
132
98
  complete? || interrupted?
133
99
  end
134
100
 
135
- # Executes the provided block if the result has been executed
101
+ # @param block [Proc] Block to execute conditionally
136
102
  #
137
- # @param block [Proc] the block to execute if result was executed
103
+ # @yield [self] Executes the block if task has been executed
138
104
  #
139
- # @return [Result] returns self for method chaining
105
+ # @return [self] Returns self for method chaining
140
106
  #
141
- # @raise [ArgumentError] if no block is provided
107
+ # @raise [ArgumentError] When no block is provided
142
108
  #
143
- # @example Handle executed results
144
- # result.on_executed { |r| puts "Task execution finished" }
145
- def on_executed(&)
109
+ # @example
110
+ # result.handle_executed { |r| puts "Task finished: #{r.outcome}" }
111
+ def handle_executed(&)
146
112
  raise ArgumentError, "block required" unless block_given?
147
113
 
148
114
  yield(self) if executed?
149
115
  self
150
116
  end
151
117
 
152
- # Transitions the result to executing state
153
- #
154
- # @return [void]
155
- #
156
- # @raise [RuntimeError] if not transitioning from initialized state
118
+ # @raise [RuntimeError] When attempting to transition from invalid state
157
119
  #
158
- # @example Start task execution
159
- # result.executing!
160
- # result.executing? #=> true
120
+ # @example
121
+ # result.executing! # Transitions from initialized to executing
161
122
  def executing!
162
123
  return if executing?
163
124
 
@@ -166,16 +127,10 @@ module CMDx
166
127
  @state = EXECUTING
167
128
  end
168
129
 
169
- # Transitions the result to complete state
130
+ # @raise [RuntimeError] When attempting to transition from invalid state
170
131
  #
171
- # @return [void]
172
- #
173
- # @raise [RuntimeError] if not transitioning from executing state
174
- #
175
- # @example Complete task execution
176
- # result.executing!
177
- # result.complete!
178
- # result.complete? #=> true
132
+ # @example
133
+ # result.complete! # Transitions from executing to complete
179
134
  def complete!
180
135
  return if complete?
181
136
 
@@ -184,16 +139,10 @@ module CMDx
184
139
  @state = COMPLETE
185
140
  end
186
141
 
187
- # Transitions the result to interrupted state
188
- #
189
- # @return [void]
142
+ # @raise [RuntimeError] When attempting to transition from invalid state
190
143
  #
191
- # @raise [RuntimeError] if transitioning from complete state
192
- #
193
- # @example Interrupt task execution
194
- # result.executing!
195
- # result.interrupt!
196
- # result.interrupted? #=> true
144
+ # @example
145
+ # result.interrupt! # Transitions from executing to interrupted
197
146
  def interrupt!
198
147
  return if interrupted?
199
148
 
@@ -203,37 +152,25 @@ module CMDx
203
152
  end
204
153
 
205
154
  STATUSES.each do |s|
206
- # Checks if the result has the specified status.
207
- #
208
- # @return [Boolean] true if the result matches the status
155
+ # @return [Boolean] Whether the result has the specified status
209
156
  #
210
- # @example Check if result is successful
211
- # result.success? #=> true
212
- #
213
- # @example Check if result is skipped
214
- # result.skipped? #=> false
215
- #
216
- # @example Check if result is failed
217
- # result.failed? #=> false
157
+ # @example
158
+ # result.success? # => true
159
+ # result.failed? # => false
218
160
  define_method(:"#{s}?") { status == s }
219
161
 
220
- # Executes the provided block if the result has the specified status.
221
- #
222
- # @param block [Proc] the block to execute if result matches the status
223
- #
224
- # @return [Result] returns self for method chaining
162
+ # @param block [Proc] Block to execute conditionally
225
163
  #
226
- # @raise [ArgumentError] if no block is provided
164
+ # @yield [self] Executes the block if result has specified status
227
165
  #
228
- # @example Handle successful status
229
- # result.on_success { |r| puts "Task completed successfully" }
166
+ # @return [self] Returns self for method chaining
230
167
  #
231
- # @example Handle skipped status
232
- # result.on_skipped { |r| puts "Task was skipped: #{r.metadata[:reason]}" }
168
+ # @raise [ArgumentError] When no block is provided
233
169
  #
234
- # @example Handle failed status
235
- # result.on_failed { |r| puts "Task failed: #{r.metadata[:error]}" }
236
- define_method(:"on_#{s}") do |&block|
170
+ # @example
171
+ # result.handle_success { |r| puts "Task succeeded" }
172
+ # result.handle_failed { |r| puts "Task failed: #{r.reason}" }
173
+ define_method(:"handle_#{s}") do |&block|
237
174
  raise ArgumentError, "block required" unless block
238
175
 
239
176
  block.call(self) if send(:"#{s}?")
@@ -241,298 +178,280 @@ module CMDx
241
178
  end
242
179
  end
243
180
 
244
- # Checks if the result has a good outcome (not failed)
181
+ # @return [Boolean] Whether the task execution was successful (not failed)
245
182
  #
246
- # @return [Boolean] true if the result is not failed
247
- #
248
- # @example Check for good outcome
249
- # result.good? #=> true (initially successful)
250
- # result.fail!
251
- # result.good? #=> false
183
+ # @example
184
+ # result.good? # => true if !failed?
252
185
  def good?
253
186
  !failed?
254
187
  end
255
188
 
256
- # Executes the provided block if the result has a good outcome
189
+ # @param block [Proc] Block to execute conditionally
257
190
  #
258
- # @param block [Proc] the block to execute if result is good
191
+ # @yield [self] Executes the block if task execution was successful
259
192
  #
260
- # @return [Result] returns self for method chaining
193
+ # @return [self] Returns self for method chaining
261
194
  #
262
- # @raise [ArgumentError] if no block is provided
195
+ # @raise [ArgumentError] When no block is provided
263
196
  #
264
- # @example Handle good results
265
- # result.on_good { |r| puts "Task had good outcome" }
266
- def on_good(&)
197
+ # @example
198
+ # result.handle_good { |r| puts "Task completed successfully" }
199
+ def handle_good(&)
267
200
  raise ArgumentError, "block required" unless block_given?
268
201
 
269
202
  yield(self) if good?
270
203
  self
271
204
  end
272
205
 
273
- # Checks if the result has a bad outcome (not successful)
274
- #
275
- # @return [Boolean] true if the result is not successful
206
+ # @return [Boolean] Whether the task execution was unsuccessful (not success)
276
207
  #
277
- # @example Check for bad outcome
278
- # result.bad? #=> false (initially successful)
279
- # result.skip!
280
- # result.bad? #=> true
208
+ # @example
209
+ # result.bad? # => true if !success?
281
210
  def bad?
282
211
  !success?
283
212
  end
284
213
 
285
- # Executes the provided block if the result has a bad outcome
214
+ # @param block [Proc] Block to execute conditionally
286
215
  #
287
- # @param block [Proc] the block to execute if result is bad
216
+ # @yield [self] Executes the block if task execution was unsuccessful
288
217
  #
289
- # @return [Result] returns self for method chaining
218
+ # @return [self] Returns self for method chaining
290
219
  #
291
- # @raise [ArgumentError] if no block is provided
220
+ # @raise [ArgumentError] When no block is provided
292
221
  #
293
- # @example Handle bad outcome
294
- # result.on_bad { |r| puts "Task had bad outcome: #{r.status}" }
295
- def on_bad(&)
222
+ # @example
223
+ # result.handle_bad { |r| puts "Task had issues: #{r.reason}" }
224
+ def handle_bad(&)
296
225
  raise ArgumentError, "block required" unless block_given?
297
226
 
298
227
  yield(self) if bad?
299
228
  self
300
229
  end
301
230
 
302
- # Transitions the result to skipped status and sets metadata
303
- #
304
- # @param metadata [Hash] additional metadata about why the task was skipped
305
- # @option metadata [String] :reason the reason for skipping
306
- # @option metadata [Exception] :original_exception the original exception that caused skipping
307
- #
308
- # @return [void]
231
+ # @param reason [String, nil] Reason for skipping the task
232
+ # @param halt [Boolean] Whether to halt execution after skipping
233
+ # @param cause [Exception, nil] Exception that caused the skip
234
+ # @param metadata [Hash] Additional metadata about the skip
309
235
  #
310
- # @raise [RuntimeError] if not transitioning from success status
311
- # @raise [CMDx::Fault] if no original_exception in metadata (via halt!)
236
+ # @raise [RuntimeError] When attempting to skip from invalid status
312
237
  #
313
- # @example Skip a task with reason
314
- # result.skip!(reason: "Dependencies not met")
315
- # result.skipped? #=> true
316
- # result.metadata[:reason] #=> "Dependencies not met"
317
- def skip!(**metadata)
238
+ # @example
239
+ # result.skip!("Dependencies not met", cause: dependency_error)
240
+ # result.skip!("Already processed", halt: false)
241
+ def skip!(reason = nil, halt: true, cause: nil, **metadata)
318
242
  return if skipped?
319
243
 
320
244
  raise "can only transition to #{SKIPPED} from #{SUCCESS}" unless success?
321
245
 
322
- @status = SKIPPED
246
+ @state = INTERRUPTED
247
+ @status = SKIPPED
248
+ @reason = reason || Locale.t("cmdx.faults.unspecified")
249
+ @cause = cause
323
250
  @metadata = metadata
324
251
 
325
- halt! unless metadata[:original_exception]
252
+ halt! if halt
326
253
  end
327
254
 
328
- # Transitions the result to failed status and sets metadata
329
- #
330
- # @param metadata [Hash] additional metadata about the failure
331
- # @option metadata [String] :error the error message
332
- # @option metadata [Exception] :original_exception the original exception that caused failure
255
+ # @param reason [String, nil] Reason for task failure
256
+ # @param halt [Boolean] Whether to halt execution after failure
257
+ # @param cause [Exception, nil] Exception that caused the failure
258
+ # @param metadata [Hash] Additional metadata about the failure
333
259
  #
334
- # @return [void]
260
+ # @raise [RuntimeError] When attempting to fail from invalid status
335
261
  #
336
- # @raise [RuntimeError] if not transitioning from success status
337
- # @raise [CMDx::Fault] if no original_exception in metadata (via halt!)
338
- #
339
- # @example Fail a task with error message
340
- # result.fail!(reason: "Database connection failed")
341
- # result.failed? #=> true
342
- # result.metadata[:error] #=> "Database connection failed"
343
- def fail!(**metadata)
262
+ # @example
263
+ # result.fail!("Validation failed", cause: validation_error)
264
+ # result.fail!("Network timeout", halt: false, timeout: 30)
265
+ def fail!(reason = nil, halt: true, cause: nil, **metadata)
344
266
  return if failed?
345
267
 
346
268
  raise "can only transition to #{FAILED} from #{SUCCESS}" unless success?
347
269
 
348
- @status = FAILED
270
+ @state = INTERRUPTED
271
+ @status = FAILED
272
+ @reason = reason || Locale.t("cmdx.faults.unspecified")
273
+ @cause = cause
349
274
  @metadata = metadata
350
275
 
351
- halt! unless metadata[:original_exception]
276
+ halt! if halt
352
277
  end
353
278
 
354
- # Halts execution by raising a fault if the result is not successful
279
+ # @raise [SkipFault] When task was skipped
280
+ # @raise [FailFault] When task failed
355
281
  #
356
- # @return [void]
357
- #
358
- # @raise [CMDx::Fault] if the result is not successful
359
- #
360
- # @example Halt on failed result
361
- # result.fail!(reason: "Something went wrong")
362
- # result.halt! # raises CMDx::Fault
282
+ # @example
283
+ # result.halt! # Raises appropriate fault based on status
363
284
  def halt!
364
285
  return if success?
365
286
 
366
- raise Fault.build(self)
287
+ klass = skipped? ? SkipFault : FailFault
288
+ fault = klass.new(self)
289
+
290
+ # Strip the first two frames (this method and the delegator)
291
+ frames = caller_locations(3..-1)
292
+ fault.set_backtrace(frames.map(&:to_s)) unless frames.empty?
293
+
294
+ raise(fault)
367
295
  end
368
296
 
369
- # Throws the status and metadata from another result to this result
370
- #
371
- # @param result [CMDx::Result] the result to throw from
372
- # @param local_metadata [Hash] additional metadata to merge
297
+ # @param result [CMDx::Result] Result to throw from current result
298
+ # @param halt [Boolean] Whether to halt execution after throwing
299
+ # @param cause [Exception, nil] Exception that caused the throw
300
+ # @param metadata [Hash] Additional metadata to merge
373
301
  #
374
- # @return [void]
302
+ # @raise [TypeError] When result is not a CMDx::Result instance
375
303
  #
376
- # @raise [TypeError] if result is not a Result instance
377
- #
378
- # @example Throw from a failed result
379
- # failed_result = Result.new(task)
380
- # failed_result.fail!(reason: "network timeout")
381
- # current_result.throw!(failed_result)
382
- # current_result.failed? #=> true
383
- def throw!(result, local_metadata = {})
384
- raise TypeError, "must be a Result" unless result.is_a?(Result)
304
+ # @example
305
+ # other_result = OtherTask.execute
306
+ # result.throw!(other_result, cause: upstream_error)
307
+ def throw!(result, halt: true, cause: nil, **metadata)
308
+ raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
385
309
 
386
- md = result.metadata.merge(local_metadata)
310
+ @state = result.state
311
+ @status = result.status
312
+ @reason = result.reason
313
+ @cause = cause || result.cause
314
+ @metadata = result.metadata.merge(metadata)
387
315
 
388
- skip!(**md) if result.skipped?
389
- fail!(**md) if result.failed?
316
+ halt! if halt
390
317
  end
391
318
 
392
- # Finds the result that originally caused a failure in the chain
393
- #
394
- # @return [CMDx::Result, nil] the result that caused the failure, or nil if not failed
319
+ # @return [CMDx::Result, nil] The result that caused this failure, or nil
395
320
  #
396
- # @example Find the original failure cause
397
- # result.caused_failure #=> #<Result task=OriginalTask status=failed>
321
+ # @example
322
+ # cause = result.caused_failure
323
+ # puts "Caused by: #{cause.task.id}" if cause
398
324
  def caused_failure
399
325
  return unless failed?
400
326
 
401
327
  chain.results.reverse.find(&:failed?)
402
328
  end
403
329
 
404
- # Checks if this result caused a failure in the chain
330
+ # @return [Boolean] Whether this result caused the failure
405
331
  #
406
- # @return [Boolean] true if this result caused the failure
407
- #
408
- # @example Check if result caused failure
409
- # result.caused_failure? #=> true
332
+ # @example
333
+ # if result.caused_failure?
334
+ # puts "This task caused the failure"
335
+ # end
410
336
  def caused_failure?
411
337
  return false unless failed?
412
338
 
413
339
  caused_failure == self
414
340
  end
415
341
 
416
- # Finds the result that this failure was thrown to
342
+ # @return [CMDx::Result, nil] The result that threw this failure, or nil
417
343
  #
418
- # @return [CMDx::Result, nil] the result that received the thrown failure, or nil if not failed
419
- #
420
- # @example Find where failure was thrown
421
- # result.threw_failure #=> #<Result task=PropagatorTask status=failed>
344
+ # @example
345
+ # thrown = result.threw_failure
346
+ # puts "Thrown by: #{thrown.task.id}" if thrown
422
347
  def threw_failure
423
348
  return unless failed?
424
349
 
350
+ current = index
425
351
  results = chain.results.select(&:failed?)
426
- results.find { |r| r.index > index } || results.last
352
+ results.find { |r| r.index > current } || results.last
427
353
  end
428
354
 
429
- # Checks if this result threw a failure to another result
430
- #
431
- # @return [Boolean] true if this result threw a failure
355
+ # @return [Boolean] Whether this result threw the failure
432
356
  #
433
- # @example Check if result threw failure
434
- # result.threw_failure? #=> false
357
+ # @example
358
+ # if result.threw_failure?
359
+ # puts "This task threw the failure"
360
+ # end
435
361
  def threw_failure?
436
362
  return false unless failed?
437
363
 
438
364
  threw_failure == self
439
365
  end
440
366
 
441
- # Checks if this result received a thrown failure from another result
442
- #
443
- # @return [Boolean] true if this result received a thrown failure
367
+ # @return [Boolean] Whether this result is a thrown failure
444
368
  #
445
- # @example Check if result received thrown failure
446
- # result.thrown_failure? #=> true
369
+ # @example
370
+ # if result.thrown_failure?
371
+ # puts "This failure was thrown from another task"
372
+ # end
447
373
  def thrown_failure?
448
374
  failed? && !caused_failure?
449
375
  end
450
376
 
451
- # Returns the index of this result in the chain
452
- #
453
- # @return [Integer] the zero-based index of this result
377
+ # @return [Integer] Index of this result in the chain
454
378
  #
455
- # @example Get result index
456
- # result.index #=> 2
379
+ # @example
380
+ # position = result.index
381
+ # puts "Task #{position + 1} of #{chain.results.count}"
457
382
  def index
458
383
  chain.index(self)
459
384
  end
460
385
 
461
- # Returns the outcome of the result (state for certain cases, status otherwise)
386
+ # @return [String] The outcome of the task execution
462
387
  #
463
- # @return [String] the outcome (state or status)
464
- #
465
- # @example Get result outcome
466
- # result.outcome #=> "success"
467
- # result.fail!
468
- # result.outcome #=> "failed"
388
+ # @example
389
+ # result.outcome # => "success" or "interrupted"
469
390
  def outcome
470
391
  initialized? || thrown_failure? ? state : status
471
392
  end
472
393
 
473
- # Gets or measures the runtime of the result
474
- #
475
- # @param block [Proc] optional block to measure runtime for
394
+ # @return [Hash] Hash representation of the result
476
395
  #
477
- # @return [Float, nil] the runtime in seconds, or nil if not measured
478
- #
479
- # @example Get existing runtime
480
- # result.runtime #=> 1.234
481
- #
482
- # @example Measure runtime with block
483
- # result.runtime { sleep 1 } #=> 1.0
484
- def runtime(&)
485
- return @runtime unless block_given?
486
-
487
- @runtime = Utils::MonotonicRuntime.call(&)
488
- end
489
-
490
- # Converts the result to a hash representation
491
- #
492
- # @return [Hash] serialized representation of the result
493
- #
494
- # @example Convert to hash
495
- # result.to_h #=> { state: "complete", status: "success", ... }
396
+ # @example
397
+ # result.to_h
398
+ # # => {state: "complete", status: "success", outcome: "success", metadata: {}}
496
399
  def to_h
497
- ResultSerializer.call(self)
400
+ task.to_h.merge!(
401
+ state:,
402
+ status:,
403
+ outcome:,
404
+ metadata:
405
+ ).tap do |hash|
406
+ if interrupted?
407
+ hash[:reason] = reason
408
+ hash[:cause] = cause
409
+ end
410
+
411
+ if failed?
412
+ STRIP_FAILURE.call(hash, self, :threw_failure)
413
+ STRIP_FAILURE.call(hash, self, :caused_failure)
414
+ end
415
+ end
498
416
  end
499
417
 
500
- # Returns a string representation of the result
418
+ # @return [String] String representation of the result
501
419
  #
502
- # @return [String] formatted string representation
503
- #
504
- # @example Convert to string
505
- # result.to_s #=> "Result[complete/success]"
420
+ # @example
421
+ # result.to_s # => "task_id=my_task state=complete status=success"
506
422
  def to_s
507
- ResultInspector.call(to_h)
423
+ Utils::Format.to_str(to_h) do |key, value|
424
+ case key
425
+ when /failure/ then "#{key}=<[#{value[:index]}] #{value[:class]}: #{value[:id]}>"
426
+ else "#{key}=#{value.inspect}"
427
+ end
428
+ end
508
429
  end
509
430
 
510
- # Deconstructs the result for pattern matching
431
+ # @param keys [Array] Array of keys to deconstruct
511
432
  #
512
- # @return [Array<String>] array containing state and status
433
+ # @return [Array] Array containing state and status
513
434
  #
514
- # @example Pattern matching with deconstruct
515
- # case result
516
- # in ["complete", "success"]
517
- # puts "Task completed successfully"
518
- # end
519
- def deconstruct
435
+ # @example
436
+ # state, status = result.deconstruct
437
+ # puts "State: #{state}, Status: #{status}"
438
+ def deconstruct(*)
520
439
  [state, status]
521
440
  end
522
441
 
523
- # Deconstructs the result with keys for pattern matching
442
+ # @param keys [Array] Array of keys to deconstruct
524
443
  #
525
- # @param keys [Array<Symbol>, nil] specific keys to include in deconstruction
444
+ # @return [Hash] Hash with key-value pairs for pattern matching
526
445
  #
527
- # @return [Hash] hash with requested attributes
528
- #
529
- # @example Pattern matching with specific keys
530
- # case result
531
- # in { state: "complete", good: true }
532
- # puts "Task finished well"
446
+ # @example
447
+ # case result.deconstruct_keys
448
+ # in {state: "complete", good: true}
449
+ # puts "Task completed successfully"
450
+ # in {bad: true}
451
+ # puts "Task had issues"
533
452
  # end
534
- def deconstruct_keys(keys)
535
- attributes = {
453
+ def deconstruct_keys(*)
454
+ {
536
455
  state: state,
537
456
  status: status,
538
457
  metadata: metadata,
@@ -540,10 +459,6 @@ module CMDx
540
459
  good: good?,
541
460
  bad: bad?
542
461
  }
543
-
544
- return attributes if keys.nil?
545
-
546
- attributes.slice(*keys)
547
462
  end
548
463
 
549
464
  end