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 +7 -0
- data/LICENSE +21 -0
- data/README.md +439 -0
- data/hati-command.gemspec +35 -0
- data/lib/hati_command/befehl.rb +135 -0
- data/lib/hati_command/callee.rb +107 -0
- data/lib/hati_command/cmd.rb +80 -0
- data/lib/hati_command/errors/fail_fast_error.rb +30 -0
- data/lib/hati_command/failure.rb +96 -0
- data/lib/hati_command/result.rb +107 -0
- data/lib/hati_command/success.rb +95 -0
- data/lib/hati_command/version.rb +5 -0
- data/lib/hati_command.rb +13 -0
- metadata +65 -0
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
|
+
#  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
|
data/lib/hati_command.rb
ADDED
|
@@ -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: []
|