hati-command 0.1.0.beta1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8a7695ebc8436769523738139c4283bd648475092fbd41ee4ffbc6ae3c721a5c
4
+ data.tar.gz: 075c0911c8855a4a811302fac864fe2fda8c55a1cfc358eb3c1eb3918050e180
5
+ SHA512:
6
+ metadata.gz: a7c10d976e359912c6a6ef0da1b9b0fc88de2d84ac3d084a2155a6a9437114207254bdc36eefe423938e477f151400e7868dd473ad3fe82c7e677815fdfe6185
7
+ data.tar.gz: c530a89f9eed5dfa5d2d5b1e25ece68fd422ceb5139a94b98c374af259816f685396a06b8d49974f7170744872f09561efdad4b84589ff65f3bc8f771572cdca
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 hackico.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,439 @@
1
+ # ![Ruby](icon.svg) hati-command
2
+
3
+ The `hati-command` gem is designed to simplify command execution, emphasizing effective handling of success and failure outcomes. It can be employed as a service object or interactor, fostering clean and organized code for managing complex operations.
4
+
5
+ - **hati-command** provides a simple and flexible way to handle command execution and result handling.
6
+
7
+ - **hati-command** offers a `Success` and `Failure` result classes to handle command execution results.
8
+
9
+ - **hati-command** It provides a `Result` object to access the result value, success, and failure, along with options to attach custom messages and metadata for better context.
10
+
11
+ ## Features
12
+
13
+ - **Command Execution**: Execute commands seamlessly, allowing for optional parameters.
14
+ - **Success Handling**: Provides success responses with transformed messages and additional metadata.
15
+ - **Failure Handling**: Handles failures gracefully, returning informative messages and context.
16
+
17
+ ## Table of Contents
18
+
19
+ - [Introduction](#introduction)
20
+ - [Features](#features)
21
+ - [Installation](#installation)
22
+ - [Basic Usage](#basic-usage)
23
+ - [Handling Success](#handling-success)
24
+ - [Handling Failure](#handling-failure)
25
+ - [Transactional Behavior](#transactional-behavior-fail-fast-with-failure)
26
+ - [Advanced Usage](#advanced-usage)
27
+ - [Result Customization](#result-customization)
28
+ - [meta](#meta)
29
+ - [error](#error)
30
+ - [trace](#trace)
31
+ - [Command Configurations](#command-configurations)
32
+ - [failure](#failure)
33
+ - [fail_fast](#fail_fast)
34
+ - [unexpected_err](#unexpected_err)
35
+ - [result_inference](#result_inference)
36
+ - [Development](#development)
37
+ - [Contributing](#contributing)
38
+ - [License](#license)
39
+ - [Code of Conduct](#code-of-conduct)
40
+
41
+ ## Installation
42
+
43
+ Install the gem and add to the application's Gemfile by executing:
44
+
45
+ ```bash
46
+ bundle add hati-command
47
+ ```
48
+
49
+ If bundler is not being used to manage dependencies, install the gem by executing:
50
+
51
+ ```bash
52
+ gem install hati-command
53
+ ```
54
+
55
+ ## Basic Usage
56
+
57
+ To use the `hati-command` gem, you can create a command class that includes the `HatiCommand::Cmd` module. Here’s a simple example:
58
+
59
+ ```ruby
60
+ require 'hati_command'
61
+
62
+ class GreetingCommand
63
+ include HatiCommand::Cmd
64
+
65
+ def call(greeting = nil)
66
+ message = build_greeting(greeting)
67
+ return message if message.failure?
68
+
69
+ process_message(message)
70
+ end
71
+
72
+ def build_greeting(greeting)
73
+ greeting ? Success(greeting) : Failure("No greeting provided")
74
+ end
75
+
76
+ def process_message(message)
77
+ message.success? ? Success(message.upcase) : Failure("No message provided")
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### Handling `Success`
83
+
84
+ ```ruby
85
+ result = GreetingCommand.call("Hello, World!")
86
+
87
+ puts result.success? # Outputs: true
88
+ puts result.failure? # Outputs: false
89
+
90
+ puts result.success # Outputs: "HELLO, WORLD!"
91
+ puts result.failure # Outputs: nil
92
+
93
+ puts result.value # Outputs: "HELLO, WORLD!"
94
+ puts result.result # Outputs: HatiCommand::Success
95
+
96
+ ```
97
+
98
+ ### Handling `Failure`
99
+
100
+ ```ruby
101
+ result = GreetingCommand.call
102
+
103
+ puts result.failure? # Outputs: true
104
+ puts result.success? # Outputs: false
105
+
106
+ puts result.failure # Outputs: "No message provided"
107
+ puts result.success # Outputs: nil
108
+
109
+ puts result.value # Outputs: "No message provided"
110
+ puts result.result # Outputs: HatiCommand::Failure
111
+ ```
112
+
113
+ ### Transactional Behavior: Fail Fast with `Failure!`
114
+
115
+ ```ruby
116
+ class GreetingCommand
117
+ include HatiCommand::Cmd
118
+
119
+ # NOTE: Will catch unexpected and wrap to HatiCommand::Failure object
120
+ # Requires true || ErrorObject
121
+ command do
122
+ unexpected_err true
123
+ end
124
+
125
+ def call(params)
126
+ message = process_message(params[:message])
127
+ msg = normalize_message(message, params[:recipients])
128
+
129
+ Success(msg)
130
+ end
131
+
132
+ # NOTE: No message passed - auto break an execution
133
+ def process_message(message)
134
+ message ? message.upcase : Failure!("No message provided")
135
+ end
136
+
137
+ def normalize_message(message, recipients)
138
+ Failure!("No recipients provided") if recipients.empty?
139
+
140
+ recipients.map { |recipient| "#{recipient}: #{message}" }
141
+ end
142
+ end
143
+ ```
144
+
145
+ ```ruby
146
+ # NOTE: No message passed - command exited
147
+ # Returns Result (Failure) object
148
+ result = GreetingCommand.call
149
+
150
+ puts result.failure? # Outputs: true
151
+ puts result.failure # Outputs: "No message provided"
152
+ puts result.value # Outputs: "No message provided"
153
+ ```
154
+
155
+ ```ruby
156
+
157
+ result = GreetingCommand.call(params.merge(message: "Hello!"))
158
+
159
+ puts result.failure? # Outputs: true
160
+ puts result.failure # Outputs: "No recipients provided"
161
+ puts result.value # Outputs: "No recipients provided"
162
+ ```
163
+
164
+ ```ruby
165
+ result = GreetingCommand.call(params.merge(recipients: ["Alice", "Bob"]))
166
+
167
+ puts result.failure? # Outputs: false
168
+ puts result.success # Outputs: true
169
+ puts result.value # Outputs: ["Alice: Hello!", "Bob: Hello!"]
170
+ ```
171
+
172
+ ## Advanced Usage
173
+
174
+ Configurations and customization allow users to tailor the command to meet their specific needs and preferences
175
+
176
+ ### `Result` Customization
177
+
178
+ Here are some advanced examples of result customization. Available options are
179
+
180
+ - `meta` - Hash to attach custom metadata
181
+ - `err` - Message or Error access via `error` method
182
+ - `trace` - By design `Failure!` and `unexpected_err` error's stack top entry
183
+
184
+ ### .meta
185
+
186
+ ```ruby
187
+ class GreetingCommand
188
+ include HatiCommand::Cmd
189
+ # ...
190
+ def process_message(message)
191
+ Success(message.upcase, meta: { lang: :eng, length: message.length })
192
+ end
193
+ # ...
194
+ end
195
+ ```
196
+
197
+ ```ruby
198
+ result = GreetingCommand.("Hello, Advanced World!")
199
+ puts result.value # Outputs: "HELLO, ADVANCED WORLD!"
200
+
201
+ puts result.meta[:lang] # Outputs: :eng
202
+ puts result.meta[:length] # Outputs: 22
203
+ puts result.meta # Outputs: {:lang=>:eng, :length=>22}
204
+ ```
205
+
206
+ ### .error
207
+
208
+ ##### set via `err` access via `error` method. Availiable as param for `#Success` as well (ex. partial success)
209
+
210
+ ```ruby
211
+ class GreetingCommand
212
+ include HatiCommand::Cmd
213
+ # ...
214
+ def process_message(message)
215
+ Failure(message, err: "No message provided")
216
+ end
217
+ end
218
+ ```
219
+
220
+ ```ruby
221
+ result = GreetingCommand.call
222
+ puts result.value # Outputs: nil
223
+ puts result.error # Outputs: "No message provided"
224
+ puts result.trace # Outputs:
225
+ ```
226
+
227
+ ### .trace
228
+
229
+ ##### Available as accessor on `Result` object
230
+
231
+ ```ruby
232
+ 1| class DoomedCommand
233
+ 2| include HatiCommand::Cmd
234
+ 3|
235
+ 4| def call
236
+ 5| Failure!
237
+ 6| end
238
+ 7| # ...
239
+ 8| end
240
+ ```
241
+
242
+ ```ruby
243
+ result = GreetingCommand.call
244
+ puts result.failure? # Outputs: true
245
+ puts result.trace # Outputs: path/to/cmds/doomed_command.rb:5:in `call'
246
+ ```
247
+
248
+ ### Command `Configurations`
249
+
250
+ Provides options for default failure message or errors. Available configs are:
251
+
252
+ - `failure` - Message or Error
253
+ - `fail_fast` - Message or Error
254
+ - `unexpected_err` - Bool(true) or Message or Error
255
+ - `result_inference` - Bool(true) | implicit Result wrapper
256
+ - `call_as` - Symbol | Main call method name
257
+
258
+ ### failure
259
+
260
+ ```ruby
261
+ 1 | class DoomedCommand
262
+ 2 | include HatiCommand::Cmd
263
+ 3 |
264
+ 4 | command do
265
+ 5 | failure "Default Error"
266
+ 6 | end
267
+ 7 |
268
+ 8 | def call(error = nil, fail_fast: false)
269
+ 9 | Failure! if fail_fast
270
+ 10|
271
+ 11| return Failure("Foo") unless option
272
+ 12|
273
+ 13| Failure(error, err: "Insufficient funds")
274
+ 14| end
275
+ 15| # ...
276
+ 16| end
277
+ ```
278
+
279
+ ```ruby
280
+ result = DoomedCommand.call(fail_fast: true)
281
+ # NOTE: not configured fail fast uses default error
282
+ puts result.failure # Outputs: nil
283
+ puts result.error # Outputs: "Default Error"
284
+ puts result.trace # Outputs: path/to/cmds/doomed_command.rb:5:in `call'
285
+
286
+
287
+ result = DoomedCommand.call
288
+ puts result.failure # Outputs: "Foo"
289
+ puts result.error # Outputs: "Default Error"
290
+
291
+ result = DoomedCommand.call('Buzz')
292
+ puts result.failure # Outputs: "Buzz"
293
+ puts result.error # Outputs: "Insufficient funds"
294
+ ```
295
+
296
+ ### fail_fast
297
+
298
+ ```ruby
299
+ 1 | class DoomedCommand
300
+ 2 | include HatiCommand::Cmd
301
+ 3 |
302
+ 4 | command do
303
+ 5 | fail_fast "Default Fail Fast Error"
304
+ 6 | end
305
+ 7 |
306
+ 8 | def call
307
+ 9 | Failure!
308
+ 10| end
309
+ 11| # ...
310
+ 12| end
311
+ ```
312
+
313
+ ```ruby
314
+ result = DoomedCommand.call
315
+ puts result.failure # Outputs: nil
316
+ puts result.error # Outputs: "Default Fail Fast Error"
317
+ puts result.trace # Outputs: path/to/cmds/doomed_command.rb:9:in `call'
318
+ ```
319
+
320
+ ### unexpected_err
321
+
322
+ ```ruby
323
+ 1 | class GreetingCommand
324
+ 2 | include HatiCommand::Cmd
325
+ 3 |
326
+ 4 | command do
327
+ 5 | unexpected_err true
328
+ 5 | end
329
+ 6 |
330
+ 7 | def call
331
+ 8 | 1 + "2"
332
+ 9 | end
333
+ 10| # ...
334
+ 11| end
335
+ ```
336
+
337
+ ```ruby
338
+ result = GreetingCommand.call
339
+ puts result.failure # Outputs: nil
340
+ puts result.error # Outputs: TypeError: no implicit conversion of Integer into String
341
+ puts result.trace # Outputs: path/to/cmds/greeting_command.rb:9:in `call'
342
+ ```
343
+
344
+ ### unexpected_err (wrapped)
345
+
346
+ ```ruby
347
+ 1 | class GreetingCommand
348
+ 2 | include HatiCommand::Cmd
349
+ 3 |
350
+ 4 | class GreetingError < StandardError; end
351
+ 5 |
352
+ 6 | command do
353
+ 7 | unexpected_err GreetingError
354
+ 8 | end
355
+ 9 |
356
+ 10| def call
357
+ 11| 1 + "2"
358
+ 12| end
359
+ 13| # ...
360
+ 14| end
361
+ ```
362
+
363
+ ```ruby
364
+ result = GreetingCommand.call
365
+ # NOTE: Original error becomes value (failure)
366
+ puts result.failure # Outputs: TypeError: no implicit conversion of Integer into String
367
+
368
+ puts result.error # Outputs: GreetingError
369
+ puts result.trace # Outputs: path/to/cmds/greeting_command.rb:12:in `call'
370
+ ```
371
+
372
+ ### result_inference
373
+
374
+ ```ruby
375
+ 1 | class GreetingCommand
376
+ 2 | include HatiCommand::Cmd
377
+ 3 |
378
+ 4 | command do
379
+ 5 | result_inference true # Implicitly wraps non-Result as Success
380
+ 5 | end
381
+ 6 |
382
+ 7 | def call
383
+ 8 | 42
384
+ 9 | end
385
+ 10| # ...
386
+ 11| end
387
+ ```
388
+
389
+ ```ruby
390
+ result = GreetingCommand.call
391
+ puts result.success # Outputs: 42
392
+ puts result.failure? # Outputs: false
393
+ ```
394
+
395
+ ### call_as
396
+
397
+ ```ruby
398
+ 1 | class GreetingCommand
399
+ 2 | include HatiCommand::Cmd
400
+ 3 |
401
+ 4 | command do
402
+ 5 | call_as :execute # E.q. :perform, :run, etc.
403
+ 5 | end
404
+ 6 |
405
+ 7 | def execute
406
+ 8 | Success(42)
407
+ 9 | end
408
+ 10| # ...
409
+ 11| end
410
+ ```
411
+
412
+ ```ruby
413
+ result = GreetingCommand.execute
414
+ puts result.success # Outputs: 42
415
+ puts result.failure? # Outputs: false
416
+ ```
417
+
418
+ ## Authors
419
+
420
+ - [Yuri Gi](https://github.com/yurigitsu)
421
+ - [Marie Giy](https://github.com/mariegiy)
422
+
423
+ ## Development
424
+
425
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
426
+
427
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
428
+
429
+ ## Contributing
430
+
431
+ Bug reports and pull requests are welcome on GitHub at https://github.com/hackico-ai/hati-command. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/hackico-ai/hati-command/blob/main/CODE_OF_CONDUCT.md).
432
+
433
+ ## License
434
+
435
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
436
+
437
+ ## Code of Conduct
438
+
439
+ Everyone interacting in the HatCommand project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hackico-ai/hati-command/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'hati_command/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'hati-command'
10
+ spec.version = HatiCommand::VERSION
11
+ spec.authors = %w[yurigitsu MarieGiy]
12
+ spec.email = %w[yurigi.pro@gmail.com giy.mariya@gmail.com]
13
+ spec.license = 'MIT'
14
+
15
+ spec.summary = 'A Ruby gem for creating command objects with result handling.'
16
+ spec.description = 'hati-command is a Ruby gem for implementing the Command pattern with result handling.'
17
+ spec.homepage = "https://github.com/hackico-ai/#{spec.name}"
18
+
19
+ spec.required_ruby_version = '>= 3.0.0'
20
+
21
+ spec.files = Dir['CHANGELOG.md', 'LICENSE', 'README.md', 'hati-command.gemspec', 'lib/**/*']
22
+ spec.bindir = 'bin'
23
+ spec.executables = []
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.metadata['repo_homepage'] = spec.homepage
27
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
28
+
29
+ spec.metadata['homepage_uri'] = spec.homepage
30
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
31
+ spec.metadata['source_code_uri'] = spec.homepage
32
+ spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
33
+
34
+ spec.metadata['rubygems_mfa_required'] = 'true'
35
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @module HatiCommand
4
+ # Provides command handling functionalities for creating and managing commands.
5
+ # This module serves as the main namespace for all command-related operations.
6
+ module HatiCommand
7
+ # @module Befehl
8
+ # Core module for command handling that provides the base functionality for creating commands.
9
+ # This module is designed to be extended by classes that need command handling capabilities.
10
+ module Befehl
11
+ # Extends the base class with command handling functionality
12
+ # @param base [Class] the class that is extending this module
13
+ # @return [void]
14
+ def self.extended(base)
15
+ base.extend(BefehlClassMethods)
16
+ def base.inherited(subclass)
17
+ super
18
+ subclass.instance_variable_set(:@__command_config, @__command_config.dup)
19
+ end
20
+ end
21
+
22
+ # @module BefehlClassMethods
23
+ # Provides class methods for command configuration and execution.
24
+ # These methods are automatically added to any class that extends Befehl.
25
+ module BefehlClassMethods
26
+ # Configures a command block with specific settings
27
+ # @yield [void] The configuration block
28
+ # @return [Hash] The command configuration
29
+ # @example
30
+ # command do
31
+ # failure :my_failure_handler
32
+ # fail_fast true
33
+ # end
34
+ def command(&block)
35
+ @__command_config ||= { call_as: :call }
36
+ instance_eval(&block) if block_given?
37
+ end
38
+
39
+ # Retrieves the current command configuration
40
+ # @return [Hash] The current command configuration settings
41
+ def command_config
42
+ @__command_config
43
+ end
44
+
45
+ # Sets the result inference behavior for the command.
46
+ # @param value [Boolean] Indicates whether to enable result inference.
47
+ # @return [void]
48
+ def result_inference(value)
49
+ @__command_config[:result_inference] = value
50
+ end
51
+
52
+ # Sets the failure handler for the command
53
+ # @param value [Symbol, Proc] The failure handler to be used
54
+ # @return [void]
55
+ def failure(value)
56
+ @__command_config[:failure] = value
57
+ end
58
+
59
+ # Sets the fail-fast behavior for the command
60
+ # @param value [Boolean] Whether to enable fail-fast behavior
61
+ # @return [void]
62
+ def fail_fast(value)
63
+ @__command_config[:fail_fast] = value
64
+ end
65
+
66
+ # Sets the unexpected error handler for the command
67
+ # @param value [Symbol, Proc, Boolean] The error handler to be used
68
+ # @return [void]
69
+ def unexpected_err(value)
70
+ @__command_config[:unexpected_err] = value
71
+ end
72
+
73
+ # This method checks if a caller method has been set; if not, it defaults to `:call`.
74
+ # @return [Symbol] The name of the method to call.
75
+ def call_as(value = :call)
76
+ @__command_config[:call_as] = value
77
+
78
+ singleton_class.send(:alias_method, value, :call)
79
+ end
80
+
81
+ # Executes the command with the given arguments.
82
+ #
83
+ # This method creates a new instance of the command class, yields it to an optional block,
84
+ # and then calls the instance method with the provided arguments. It handles the result
85
+ # of the command execution, returning a success or failure result based on the outcome.
86
+ #
87
+ # @param args [Array] Arguments to be passed to the instance method.
88
+ # @yield [Object] Optional block that yields the new instance for additional configuration.
89
+ # @return [HatiCommand::Result, Object] The result of the command execution, wrapped in a Result object if applicable.
90
+ # @raise [HatiCommand::Errors::FailFastError] If a fail-fast condition is triggered.
91
+ # @raise [StandardError] If an unexpected error occurs and no handler is configured.
92
+ def call(...)
93
+ obj = new
94
+
95
+ yield(obj) if block_given?
96
+
97
+ result = obj.send(command_config[:call_as], ...)
98
+ return result unless command_config[:result_inference]
99
+ return result if result.is_a?(HatiCommand::Result)
100
+
101
+ HatiCommand::Success.new(result)
102
+ rescue HatiCommand::Errors::FailFastError => e
103
+ handle_fail_fast_error(e)
104
+ rescue StandardError => e
105
+ handle_standard_error(e)
106
+ end
107
+
108
+ module_function
109
+
110
+ # Handles fail-fast errors during command execution
111
+ # @param error [HatiCommand::Errors::FailFastError] The fail-fast error to handle
112
+ # @return [HatiCommand::Failure] A failure object containing error details
113
+ # @api private
114
+ def handle_fail_fast_error(error)
115
+ err_obj = error.err_obj
116
+ return HatiCommand::Failure.new(error, trace: error.backtrace.first) unless err_obj
117
+
118
+ err_obj.tap { |err| err.trace = error.backtrace[1] }
119
+ end
120
+
121
+ # Handles standard errors during command execution
122
+ # @param error [StandardError] The error to handle
123
+ # @return [HatiCommand::Failure] A failure object containing error details
124
+ # @raise [StandardError] If no unexpected error handler is configured
125
+ # @api private
126
+ def handle_standard_error(error)
127
+ internal_err = command_config[:unexpected_err]
128
+ raise error unless internal_err
129
+
130
+ err = internal_err.is_a?(TrueClass) ? error : internal_err
131
+ HatiCommand::Failure.new(error, err: err, trace: error.backtrace.first)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+
5
+ # @module HatiCommand
6
+ # Provides command handling functionalities and callable patterns.
7
+ module HatiCommand
8
+ # @module Callee
9
+ # Module for adding callable functionality to a class.
10
+ # This module implements the callable pattern, allowing classes to be called like functions
11
+ # while maintaining object-oriented principles.
12
+ #
13
+ # @example
14
+ # class MyCallable
15
+ # include HatiCommand::Callee
16
+ #
17
+ # def call(input)
18
+ # # Process input
19
+ # input.upcase
20
+ # end
21
+ # end
22
+ #
23
+ # # Can be used as:
24
+ # result = MyCallable.call("hello") # => "HELLO"
25
+ #
26
+ # # Or with a block:
27
+ # MyCallable.call("hello") do |instance|
28
+ # instance.configure(some: :option)
29
+ # end
30
+ module Callee
31
+ # Extends the including class with callable functionality
32
+ # @param base [Class] The class including this module
33
+ # @return [void]
34
+ # @api private
35
+ def self.included(base)
36
+ base.extend(CalleeClassMethods)
37
+ end
38
+
39
+ # Returns the identity of the module
40
+ # @note This is a work in progress method
41
+ # @return [String] The module's identity string
42
+ # @api public
43
+ def self.whoami
44
+ 'My Name is Callee'
45
+ end
46
+
47
+ # @module CalleeClassMethods
48
+ # Class methods that are extended to classes including Callee.
49
+ # Provides the callable interface at the class level.
50
+ module CalleeClassMethods
51
+ # This method checks if a caller method has been set; if not, it defaults to `:call`.
52
+ #
53
+ # @return [Symbol] The name of the method to call.
54
+ def __caller_method
55
+ @__caller_method || :call
56
+ end
57
+
58
+ # Creates a new instance and calls its `call` method with the given arguments.
59
+ # This method implements the callable pattern, allowing the class to be used
60
+ # like a function while maintaining object-oriented principles.
61
+ #
62
+ # @param args [Array] Arguments to be passed to the instance's call method
63
+ # @yield [Object] Optional block that yields the new instance before calling
64
+ # @yieldparam instance [Object] The newly created instance
65
+ # @return [Object] The result of the instance method call
66
+ #
67
+ # @example Without block
68
+ # MyCallable.call(arg1, arg2)
69
+ # @example With configuration block
70
+ # MyCallable.call(input) do |instance|
71
+ # instance.configure(option: value)
72
+ # end
73
+ def call(...)
74
+ obj = new
75
+
76
+ yield(obj) if block_given?
77
+
78
+ obj.send(__caller_method, ...)
79
+ end
80
+
81
+ # This method allows you to configure command call method name such as: :execute, :perform, etc.
82
+ # Note: method call_as and command main instance method should much
83
+ # @param method_name [Symbol] The name of the alias to create for the `call` method.
84
+ # @return [void]
85
+ #
86
+ # @example
87
+ # class MyCallable
88
+ # include HatiCommand::Callee
89
+ #
90
+ # call_as :execute # :run, :perform, etc.
91
+ #
92
+ # def execute(input) # :run, :perform, etc.
93
+ # input.upcase
94
+ # end
95
+ #
96
+ # end
97
+ # MyCallable.execute("hello") # => "HELLO"
98
+ # MyCallable.perform("hello") # => "HELLO"
99
+ # MyCallable.run("hello") # => "HELLO"
100
+ def call_as(method_name)
101
+ @__caller_method = method_name
102
+
103
+ singleton_class.send(:alias_method, method_name, :call)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @module HatiCommand
4
+ # Provides command handling functionalities with a focus on Railway-oriented programming.
5
+ # This module implements the Railway pattern for better error handling and command flow control.
6
+ module HatiCommand
7
+ # @module Cmd
8
+ # Dev-friendly extension of the Befehl core module with Railway track API.
9
+ # This module provides a Railway-oriented programming interface for handling success and failure states
10
+ # in a functional way, making it easier to chain operations and handle errors gracefully.
11
+ #
12
+ # @example
13
+ # class MyCommand
14
+ # include HatiCommand::Cmd
15
+ #
16
+ # def call(input)
17
+ # if valid?(input)
18
+ # Success(input)
19
+ # else
20
+ # Failure("Invalid input")
21
+ # end
22
+ # end
23
+ # end
24
+ module Cmd
25
+ # Includes the module in the base class and sets up necessary configurations
26
+ # @param base [Class] The class including this module
27
+ # @return [void]
28
+ def self.included(base)
29
+ base.extend(HatiCommand::Befehl)
30
+ base.private_class_method :new
31
+ end
32
+
33
+ # Returns the identity of the module
34
+ # @note This is a work in progress method
35
+ # @return [String] The module's identity string
36
+ # @api public
37
+ def self.whoami
38
+ 'My Name is Cmd'
39
+ end
40
+
41
+ # Creates a Success monad representing a successful operation
42
+ # @param value [Object, nil] The value to wrap in the Success monad
43
+ # @param err [Object, nil] Optional error object
44
+ # @param meta [Hash] Additional metadata for the success state
45
+ # @return [HatiCommand::Success] A Success monad containing the result
46
+ # @example
47
+ # Success("Operation completed", meta: { time: Time.now })
48
+ def Success(value = nil, err: nil, meta: {}) # rubocop:disable Naming/MethodName
49
+ HatiCommand::Success.new(value, err: err, meta: meta)
50
+ end
51
+
52
+ # Creates a Failure monad representing a failed operation
53
+ # @param value [Object, nil] The value to wrap in the Failure monad
54
+ # @param err [Object, nil] Optional error object (falls back to configured default)
55
+ # @param meta [Hash] Additional metadata for the failure state
56
+ # @return [HatiCommand::Failure] A Failure monad containing the error details
57
+ # @example
58
+ # Failure("Operation failed", err: StandardError.new, meta: { reason: "invalid_input" })
59
+ def Failure(value = nil, err: nil, meta: {}) # rubocop:disable Naming/MethodName
60
+ default_err = self.class.command_config[:failure]
61
+ HatiCommand::Failure.new(value, err: err || default_err, meta: meta)
62
+ end
63
+
64
+ # Creates a Failure monad and immediately raises a FailFastError
65
+ # @param value [Object, nil] The value to wrap in the Failure monad
66
+ # @param err [Object, nil] Optional error object (falls back to configured defaults)
67
+ # @param meta [Hash] Additional metadata for the failure state
68
+ # @param _opts [Hash] Additional options (currently unused)
69
+ # @raise [HatiCommand::Errors::FailFastError] Always raises with the created Failure monad
70
+ # @example
71
+ # Failure!("Critical error", err: FatalError.new)
72
+ def Failure!(value = nil, err: nil, meta: {}, **_opts) # rubocop:disable Naming/MethodName
73
+ default_error = self.class.command_config[:fail_fast] || self.class.command_config[:failure]
74
+ error = err || default_error
75
+
76
+ err_obj = HatiCommand::Failure.new(value, err: error, meta: meta)
77
+ raise HatiCommand::Errors::FailFastError.new('Fail Fast Triggered', err_obj: err_obj)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HatiCommand
4
+ module Errors
5
+ # Custom error class for fail-fast scenarios in HatiCommand
6
+ #
7
+ # @example Raising a FailFastError with a message
8
+ # raise HatiCommand::FailFastError.new("Operation failed")
9
+ #
10
+ # @example Raising a FailFastError with a message and error object
11
+ # begin
12
+ # # Some operation that might fail
13
+ # rescue StandardError => e
14
+ # raise HatiCommand::FailFastError.new("Operation failed", err_obj: e)
15
+ # end
16
+ class FailFastError < StandardError
17
+ # @return [Object] The associated error object
18
+ attr_reader :err_obj
19
+
20
+ # Initialize a new FailFastError
21
+ #
22
+ # @param message [String] The error message
23
+ # @param err_obj [Object] An optional error object associated with this error
24
+ def initialize(message, err_obj: nil)
25
+ super(message)
26
+ @err_obj = err_obj
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @module HatiCommand
4
+ # Provides command handling functionalities and result objects.
5
+ module HatiCommand
6
+ # @class Failure
7
+ # Represents a failure result in the Result pattern.
8
+ # This class is used to wrap failure values and provide a consistent interface
9
+ # for handling both successful and failed operations.
10
+ #
11
+ # The Failure class is part of the Result pattern implementation, working alongside
12
+ # the Success class to provide a type-safe way to handle operation outcomes.
13
+ #
14
+ # @example Basic usage
15
+ # failure = HatiCommand::Failure.new("Operation failed")
16
+ # failure.failure? # => true
17
+ # failure.success? # => false
18
+ #
19
+ # @example With error and metadata
20
+ # error = StandardError.new("Database connection failed")
21
+ # failure = HatiCommand::Failure.new(
22
+ # "Could not save record",
23
+ # err: error,
24
+ # meta: { attempted_at: Time.now }
25
+ # )
26
+ #
27
+ # @example Pattern matching
28
+ # case result
29
+ # when HatiCommand::Failure
30
+ # handle_error(result.failure)
31
+ # end
32
+ #
33
+ # @see HatiCommand::Success
34
+ # @see HatiCommand::Result
35
+ class Failure < Result
36
+ # Returns the failure value wrapped by this Failure instance.
37
+ # This method provides access to the actual error value or message
38
+ # that describes why the operation failed.
39
+ #
40
+ # @return [Object] The wrapped failure value
41
+ # @example
42
+ # failure = Failure.new("Database error")
43
+ # failure.failure # => "Database error"
44
+ def failure
45
+ value
46
+ end
47
+
48
+ # Indicates that this is a failure result.
49
+ # This method is part of the Result pattern interface and always
50
+ # returns true for Failure instances.
51
+ #
52
+ # @return [Boolean] Always returns true
53
+ # @example
54
+ # failure = Failure.new("Error")
55
+ # failure.failure? # => true
56
+ def failure?
57
+ true
58
+ end
59
+
60
+ # Returns nil since a Failure has no success value.
61
+ # This method is part of the Result pattern interface and always
62
+ # returns nil for Failure instances.
63
+ #
64
+ # @return [nil] Always returns nil
65
+ # @example
66
+ # failure = Failure.new("Error")
67
+ # failure.success # => nil
68
+ def success
69
+ nil
70
+ end
71
+
72
+ # Indicates that this is not a success result.
73
+ # This method is part of the Result pattern interface and always
74
+ # returns false for Failure instances.
75
+ #
76
+ # @return [Boolean] Always returns false
77
+ # @example
78
+ # failure = Failure.new("Error")
79
+ # failure.success? # => false
80
+ def success?
81
+ false
82
+ end
83
+
84
+ # Returns the symbolic representation of this result type.
85
+ # Useful for pattern matching and result type checking.
86
+ #
87
+ # @return [Symbol] Always returns :failure
88
+ # @api public
89
+ # @example
90
+ # failure = Failure.new("Error")
91
+ # failure.to_sym # => :failure
92
+ def to_sym
93
+ :failure
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @module HatiCommand
4
+ # Provides command handling functionalities and result objects.
5
+ module HatiCommand
6
+ # @class Result
7
+ # Base class for the Result pattern implementation.
8
+ # This class serves as the foundation for Success and Failure result types,
9
+ # providing common functionality and a consistent interface for handling
10
+ # operation outcomes.
11
+ #
12
+ # The Result pattern helps in handling operation outcomes in a type-safe way,
13
+ # making it explicit whether an operation succeeded or failed, and carrying
14
+ # additional context like error messages and metadata.
15
+ #
16
+ # @abstract Subclass and override {#to_sym} to implement a concrete result type
17
+ #
18
+ # @example Basic usage
19
+ # result = HatiCommand::Result.new("Operation output")
20
+ # result.value # => "Operation output"
21
+ #
22
+ # @example With error and metadata
23
+ # result = HatiCommand::Result.new(
24
+ # "Operation output",
25
+ # err: "Warning: partial completion",
26
+ # meta: { duration_ms: 150 }
27
+ # )
28
+ #
29
+ # @example Using trace information
30
+ # result = HatiCommand::Result.new("Output", trace: caller(1..1))
31
+ # result.trace # => ["path/to/file.rb:42:in `method_name'"]
32
+ #
33
+ # @see HatiCommand::Success
34
+ # @see HatiCommand::Failure
35
+ #
36
+ # @!attribute [r] value
37
+ # @return [Object] The wrapped value representing the operation's output
38
+ #
39
+ # @!attribute [r] meta
40
+ # @return [Hash] Additional metadata associated with the result
41
+ #
42
+ # @!attribute [rw] trace
43
+ # @return [Array<String>, nil] Execution trace information for debugging
44
+ class Result
45
+ attr_reader :value, :meta
46
+ attr_accessor :trace
47
+
48
+ # Initializes a new Result instance with a value and optional context.
49
+ #
50
+ # @param value [Object] The value to be wrapped in the result
51
+ # @param err [String, nil] Optional error message or error object
52
+ # @param meta [Hash] Optional metadata for additional context
53
+ # @param trace [Array<String>, nil] Optional execution trace for debugging
54
+ #
55
+ # @example Basic initialization
56
+ # result = Result.new("Success")
57
+ #
58
+ # @example With full context
59
+ # result = Result.new(
60
+ # "Partial success",
61
+ # err: "Some records failed",
62
+ # meta: { processed: 10, failed: 2 },
63
+ # trace: caller
64
+ # )
65
+ def initialize(value, err: nil, meta: {}, trace: nil)
66
+ @value = value
67
+ @err = err
68
+ @meta = meta
69
+ @trace = trace
70
+ end
71
+
72
+ # Returns self to provide a consistent interface across result types.
73
+ # This method ensures that all result objects can be treated uniformly
74
+ # when chaining operations.
75
+ #
76
+ # @return [HatiCommand::Result] The result instance itself
77
+ # @api public
78
+ def result
79
+ self
80
+ end
81
+
82
+ # Returns the error associated with this result.
83
+ # This can be used to check for warnings or errors even in successful results.
84
+ #
85
+ # @return [String, nil] The error message or object, if any
86
+ # @raise [StandardError] If accessing the error triggers an error condition
87
+ # @api public
88
+ # @example
89
+ # result = Result.new("Value", err: "Warning message")
90
+ # result.error # => "Warning message"
91
+ def error
92
+ @err
93
+ end
94
+
95
+ # Returns the symbolic representation of this result type.
96
+ # This is an abstract method that should be overridden by concrete result types.
97
+ #
98
+ # @return [Symbol] Returns :undefined for the base class
99
+ # @abstract Subclasses must override this method
100
+ # @api public
101
+ # @example
102
+ # Result.new("value").to_sym # => :undefined
103
+ def to_sym
104
+ :undefined
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @module HatiCommand
4
+ # Provides command handling functionalities and result objects.
5
+ module HatiCommand
6
+ # @class Success
7
+ # Represents a successful result in the Result pattern.
8
+ # This class is used to wrap successful operation values and provide a consistent interface
9
+ # for handling both successful and failed operations.
10
+ #
11
+ # The Success class is part of the Result pattern implementation, working alongside
12
+ # the Failure class to provide a type-safe way to handle operation outcomes.
13
+ #
14
+ # @example Basic usage
15
+ # success = HatiCommand::Success.new("Operation completed")
16
+ # success.success? # => true
17
+ # success.failure? # => false
18
+ #
19
+ # @example With metadata
20
+ # success = HatiCommand::Success.new(
21
+ # { id: 123, name: "Example" },
22
+ # meta: { duration_ms: 50 }
23
+ # )
24
+ # success.success # => { id: 123, name: "Example" }
25
+ #
26
+ # @example Pattern matching
27
+ # case result
28
+ # when HatiCommand::Success
29
+ # process_data(result.success)
30
+ # end
31
+ #
32
+ # @see HatiCommand::Failure
33
+ # @see HatiCommand::Result
34
+ class Success < Result
35
+ # Returns the success value wrapped by this Success instance.
36
+ # This method provides access to the actual value or result
37
+ # that was produced by the successful operation.
38
+ #
39
+ # @return [Object] The wrapped success value
40
+ # @example
41
+ # success = Success.new("Operation output")
42
+ # success.success # => "Operation output"
43
+ def success
44
+ value
45
+ end
46
+
47
+ # Indicates that this is a success result.
48
+ # This method is part of the Result pattern interface and always
49
+ # returns true for Success instances.
50
+ #
51
+ # @return [Boolean] Always returns true
52
+ # @example
53
+ # success = Success.new("Result")
54
+ # success.success? # => true
55
+ def success?
56
+ true
57
+ end
58
+
59
+ # Returns nil since a Success has no failure value.
60
+ # This method is part of the Result pattern interface and always
61
+ # returns nil for Success instances.
62
+ #
63
+ # @return [nil] Always returns nil
64
+ # @example
65
+ # success = Success.new("Result")
66
+ # success.failure # => nil
67
+ def failure
68
+ nil
69
+ end
70
+
71
+ # Indicates that this is not a failure result.
72
+ # This method is part of the Result pattern interface and always
73
+ # returns false for Success instances.
74
+ #
75
+ # @return [Boolean] Always returns false
76
+ # @example
77
+ # success = Success.new("Result")
78
+ # success.failure? # => false
79
+ def failure?
80
+ false
81
+ end
82
+
83
+ # Returns the symbolic representation of this result type.
84
+ # Useful for pattern matching and result type checking.
85
+ #
86
+ # @return [Symbol] Always returns :success
87
+ # @api public
88
+ # @example
89
+ # success = Success.new("Result")
90
+ # success.to_sym # => :success
91
+ def to_sym
92
+ :success
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HatiCommand
4
+ VERSION = '0.1.0.beta1'
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hati_command/version'
4
+ # errors
5
+ require 'hati_command/errors/fail_fast_error'
6
+ # result
7
+ require 'hati_command/result'
8
+ require 'hati_command/success'
9
+ require 'hati_command/failure'
10
+ # core
11
+ require 'hati_command/callee'
12
+ require 'hati_command/befehl'
13
+ require 'hati_command/cmd'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hati-command
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta1
5
+ platform: ruby
6
+ authors:
7
+ - yurigitsu
8
+ - MarieGiy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2025-06-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: hati-command is a Ruby gem for implementing the Command pattern with
15
+ result handling.
16
+ email:
17
+ - yurigi.pro@gmail.com
18
+ - giy.mariya@gmail.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - LICENSE
24
+ - README.md
25
+ - hati-command.gemspec
26
+ - lib/hati_command.rb
27
+ - lib/hati_command/befehl.rb
28
+ - lib/hati_command/callee.rb
29
+ - lib/hati_command/cmd.rb
30
+ - lib/hati_command/errors/fail_fast_error.rb
31
+ - lib/hati_command/failure.rb
32
+ - lib/hati_command/result.rb
33
+ - lib/hati_command/success.rb
34
+ - lib/hati_command/version.rb
35
+ homepage: https://github.com/hackico-ai/hati-command
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ repo_homepage: https://github.com/hackico-ai/hati-command
40
+ allowed_push_host: https://rubygems.org
41
+ homepage_uri: https://github.com/hackico-ai/hati-command
42
+ changelog_uri: https://github.com/hackico-ai/hati-command/blob/main/CHANGELOG.md
43
+ source_code_uri: https://github.com/hackico-ai/hati-command
44
+ bug_tracker_uri: https://github.com/hackico-ai/hati-command/issues
45
+ rubygems_mfa_required: 'true'
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.5.22
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: A Ruby gem for creating command objects with result handling.
65
+ test_files: []