lite-command 2.0.1 → 2.0.3
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/lib/lite/command/base.rb +10 -13
- data/lib/lite/command/context.rb +5 -10
- data/lib/lite/command/fault.rb +6 -30
- data/lib/lite/command/internals/callable.rb +18 -91
- data/lib/lite/command/internals/executable.rb +12 -18
- data/lib/lite/command/internals/faultable.rb +83 -0
- data/lib/lite/command/internals/resultable.rb +3 -3
- data/lib/lite/command/version.rb +1 -1
- data/lib/lite/command.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b21cf35a72b7a37760d250919ee052e3800bfe09083709e9d9f65593612d8826
|
4
|
+
data.tar.gz: b2b55e457b95ce878aab140537ba1f28aa5310c4e257d0e2b11948ed9ee7f9c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8ab004280e1c00026dacc92a7d652dc2859c68f6648e31210ccfa1c16a1aba9ba31e57843910bf90d4b94677e8cd8c4c651989c2e2f1983556f33ff786e6514
|
7
|
+
data.tar.gz: aed6da43c95926071114ef9692b17c4fa2b5a3215629e473c8bd70a552fdd624f189095cfa1744da6aa427cbcf80cfb6adb6db81c5b754f4ff81bfee500d046d
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.0.3] - 2024-09-30
|
10
|
+
### Changed
|
11
|
+
- Simplify error building
|
12
|
+
- Reduced recalling error since we can just throw it once
|
13
|
+
- Rename `fault_name` to `type`
|
14
|
+
|
15
|
+
## [2.0.2] - 2024-09-29
|
16
|
+
### Added
|
17
|
+
- faultable module
|
18
|
+
### Changed
|
19
|
+
- Simplified status variable check
|
20
|
+
- Simplified context merge
|
21
|
+
- Fixed invalid looking at wrong variable
|
22
|
+
- Renamed `fault` and `thrower` to `caused_by` and `thrown_by` respectively
|
23
|
+
- Removed unused `additional_result_data` method
|
24
|
+
### Removed
|
25
|
+
- Removed context init
|
26
|
+
|
9
27
|
## [2.0.1] - 2024-09-27
|
10
28
|
### Removed
|
11
29
|
- Activemodel dependency
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -94,7 +94,7 @@ command.context.result #=> 8
|
|
94
94
|
|
95
95
|
#### States
|
96
96
|
State represents the state of the executable code. Once `execute`
|
97
|
-
is ran, it will always `complete` or `
|
97
|
+
is ran, it will always `complete` or `interrupted` if a fault is thrown by a
|
98
98
|
child command.
|
99
99
|
|
100
100
|
- `pending`
|
@@ -103,7 +103,7 @@ child command.
|
|
103
103
|
- Command objects actively executing code.
|
104
104
|
- `complete`
|
105
105
|
- Command objects that executed to completion.
|
106
|
-
- `
|
106
|
+
- `interrupted`
|
107
107
|
- Command objects that could NOT be executed to completion.
|
108
108
|
This could be as a result of a fault/exception on the
|
109
109
|
object itself or one of its children.
|
data/lib/lite/command/base.rb
CHANGED
@@ -9,22 +9,23 @@ module Lite
|
|
9
9
|
|
10
10
|
base.include Lite::Command::Internals::Callable
|
11
11
|
base.include Lite::Command::Internals::Executable
|
12
|
+
base.include Lite::Command::Internals::Faultable
|
12
13
|
base.include Lite::Command::Internals::Resultable
|
13
14
|
|
14
15
|
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
15
|
-
# eg: Users::ResetPassword::Fault
|
16
|
-
|
16
|
+
# eg: Users::ResetPassword::Fault < Lite::Command::Fault
|
17
|
+
#{base}::Fault = Class.new(Lite::Command::Fault)
|
18
|
+
|
19
|
+
# eg: Users::ResetPassword::Noop < Users::ResetPassword::Fault
|
20
|
+
#{base}::Noop = Class.new(#{base}::Fault)
|
21
|
+
#{base}::Invalid = Class.new(#{base}::Fault)
|
22
|
+
#{base}::Failure = Class.new(#{base}::Fault)
|
23
|
+
#{base}::Error = Class.new(#{base}::Fault)
|
17
24
|
RUBY
|
18
|
-
|
19
|
-
FAULTS.each do |f|
|
20
|
-
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
21
|
-
# eg: Users::ResetPassword::Noop < Users::ResetPassword::Fault
|
22
|
-
class #{base}::#{f.capitalize} < #{base}::Fault; end
|
23
|
-
RUBY
|
24
|
-
end
|
25
25
|
end
|
26
26
|
|
27
27
|
attr_reader :context
|
28
|
+
alias ctx context
|
28
29
|
|
29
30
|
def initialize(context = {})
|
30
31
|
@context = Lite::Command::Context.build(context)
|
@@ -32,10 +33,6 @@ module Lite
|
|
32
33
|
|
33
34
|
private
|
34
35
|
|
35
|
-
def additional_result_data
|
36
|
-
{} # Define in your class to add additional info to result hash
|
37
|
-
end
|
38
|
-
|
39
36
|
def on_before_execution
|
40
37
|
# Define in your class to run code before execution
|
41
38
|
end
|
data/lib/lite/command/context.rb
CHANGED
@@ -6,25 +6,20 @@ module Lite
|
|
6
6
|
module Command
|
7
7
|
class Context < OpenStruct
|
8
8
|
|
9
|
-
def self.
|
9
|
+
def self.build(attributes = {})
|
10
|
+
return attributes if attributes.is_a?(self) && !attributes.frozen?
|
11
|
+
|
10
12
|
# To save memory and speed up the access to an attribute, the accessor methods
|
11
13
|
# of an attribute are lazy loaded at certain points. This means that the methods
|
12
14
|
# are defined only when a set of defined actions are triggered. This allows context
|
13
15
|
# to only define the minimum amount of required methods to make your data structure work
|
14
|
-
os = new(attributes)
|
16
|
+
os = new(attributes.to_h)
|
15
17
|
os.methods(false)
|
16
18
|
os
|
17
19
|
end
|
18
20
|
|
19
|
-
def self.build(attributes = {})
|
20
|
-
return attributes if attributes.is_a?(self) && !attributes.frozen?
|
21
|
-
|
22
|
-
init(attributes.to_h)
|
23
|
-
end
|
24
|
-
|
25
21
|
def merge!(attributes = {})
|
26
|
-
|
27
|
-
attrs.each { |k, v| self[k] = v }
|
22
|
+
attributes.to_h.each { |k, v| self[k] = v }
|
28
23
|
end
|
29
24
|
|
30
25
|
end
|
data/lib/lite/command/fault.rb
CHANGED
@@ -3,51 +3,27 @@
|
|
3
3
|
module Lite
|
4
4
|
module Command
|
5
5
|
|
6
|
-
# Fault represent a stoppage of a call execution. This error should
|
7
|
-
# not be raised directly since it wont provide any context. Use
|
8
|
-
# `Noop`, `Invalid`, `Failure`, and `Error` to signify severity.
|
9
6
|
class Fault < StandardError
|
10
7
|
|
11
|
-
attr_reader :
|
8
|
+
attr_reader :reason, :caused_by, :thrown_by
|
12
9
|
|
13
|
-
def initialize(
|
10
|
+
def initialize(reason, caused_by, thrown_by)
|
14
11
|
super(reason)
|
15
12
|
|
16
|
-
@faulter = faulter
|
17
|
-
@thrower = thrower
|
18
13
|
@reason = reason
|
14
|
+
@caused_by = caused_by
|
15
|
+
@thrown_by = thrown_by
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
@
|
23
|
-
end
|
24
|
-
|
25
|
-
def fault_name
|
26
|
-
@fault_name ||= fault_klass.downcase
|
18
|
+
def type
|
19
|
+
@type ||= self.class.name.split("::").last.downcase
|
27
20
|
end
|
28
21
|
|
29
22
|
end
|
30
23
|
|
31
|
-
# Noop represents skipping completion of call execution early
|
32
|
-
# an unsatisfied condition or logic check where there is no
|
33
|
-
# point on proceeding.
|
34
|
-
# eg: account is sample: skip since its a non-alterable record
|
35
24
|
class Noop < Fault; end
|
36
|
-
|
37
|
-
# Invalid represents a stoppage of call execution due to
|
38
|
-
# missing, bad, or corrupt data.
|
39
|
-
# eg: user not found: stop since rest of the call cant be executed
|
40
25
|
class Invalid < Fault; end
|
41
|
-
|
42
|
-
# Failure represents a stoppage of call execution due to
|
43
|
-
# an unsatisfied condition or logic check where it blocks
|
44
|
-
# proceeding any further.
|
45
|
-
# eg: record not found: stop since there is nothing todo
|
46
26
|
class Failure < Fault; end
|
47
|
-
|
48
|
-
# Error represents a caught exception for a call execution
|
49
|
-
# that could not complete.
|
50
|
-
# eg: ApiServerError: stop since there was a 3rd party issue
|
51
27
|
class Error < Fault; end
|
52
28
|
|
53
29
|
end
|
@@ -3,28 +3,20 @@
|
|
3
3
|
module Lite
|
4
4
|
module Command
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
FAULTS = [
|
10
|
-
NOOP = "noop",
|
6
|
+
STATUSES = [
|
7
|
+
SUCCESS = "success",
|
8
|
+
NOOP = "noop",
|
11
9
|
INVALID = "invalid",
|
12
10
|
FAILURE = "failure",
|
13
|
-
ERROR
|
14
|
-
].freeze
|
15
|
-
STATUSES = [
|
16
|
-
*FAULTS,
|
17
|
-
SUCCESS = "success"
|
11
|
+
ERROR = "error"
|
18
12
|
].freeze
|
13
|
+
FAULTS = (STATUSES - [SUCCESS]).freeze
|
19
14
|
|
20
15
|
module Internals
|
21
16
|
module Callable
|
22
17
|
|
23
18
|
def self.included(base)
|
24
19
|
base.extend ClassMethods
|
25
|
-
base.class_eval do
|
26
|
-
attr_reader :faulter, :thrower, :reason
|
27
|
-
end
|
28
20
|
end
|
29
21
|
|
30
22
|
module ClassMethods
|
@@ -43,109 +35,44 @@ module Lite
|
|
43
35
|
raise NotImplementedError, "call method not defined in #{self.class}"
|
44
36
|
end
|
45
37
|
|
46
|
-
def success?
|
47
|
-
!fault?
|
48
|
-
end
|
49
|
-
|
50
|
-
def fault?(message = nil)
|
51
|
-
FAULTS.any? { |f| send(:"#{f}?", message) }
|
52
|
-
end
|
53
|
-
|
54
38
|
def status
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def faulter?
|
59
|
-
faulter == self
|
39
|
+
@status || SUCCESS
|
60
40
|
end
|
61
41
|
|
62
|
-
def
|
63
|
-
|
42
|
+
def success?
|
43
|
+
status == SUCCESS
|
64
44
|
end
|
65
45
|
|
66
|
-
def
|
67
|
-
|
46
|
+
def fault?(str = nil)
|
47
|
+
!success? && reason?(str)
|
68
48
|
end
|
69
49
|
|
70
50
|
FAULTS.each do |f|
|
71
|
-
# eg:
|
72
|
-
define_method(:"#{f}?") do |
|
73
|
-
|
74
|
-
return fault_result if message.nil?
|
75
|
-
|
76
|
-
reason == message
|
51
|
+
# eg: noop? or failure?("idk")
|
52
|
+
define_method(:"#{f}?") do |str = nil|
|
53
|
+
status == f && reason?(str)
|
77
54
|
end
|
78
55
|
end
|
79
56
|
|
80
57
|
private
|
81
58
|
|
82
|
-
def derive_faulter_from(object)
|
83
|
-
(object.faulter if object.respond_to?(:faulter)) || self
|
84
|
-
end
|
85
|
-
|
86
|
-
def derive_thrower_from(object)
|
87
|
-
if object.respond_to?(:executed?) && object.executed?
|
88
|
-
object
|
89
|
-
else
|
90
|
-
(object.thrower if object.respond_to?(:thrower)) || faulter
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def derive_reason_from(object)
|
95
|
-
if object.respond_to?(:reason)
|
96
|
-
object.reason
|
97
|
-
elsif object.respond_to?(:message)
|
98
|
-
"[#{object.class.name}] #{object.message}".chomp(".")
|
99
|
-
else
|
100
|
-
object
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def fault(object)
|
105
|
-
@faulter ||= derive_faulter_from(object)
|
106
|
-
@thrower ||= derive_thrower_from(object)
|
107
|
-
@reason ||= derive_reason_from(object)
|
108
|
-
end
|
109
|
-
|
110
|
-
# eg: Lite::Command::Noop.new(...)
|
111
|
-
def raise_fault(klass, object)
|
112
|
-
exception = klass.new(faulter, self, reason)
|
113
|
-
exception.set_backtrace(object.backtrace) if object.respond_to?(:backtrace)
|
114
|
-
raise(exception)
|
115
|
-
end
|
116
|
-
|
117
|
-
# eg: Users::ResetPassword::Noop.new(...)
|
118
|
-
def raise_dynamic_fault(exception)
|
119
|
-
fault_klass = self.class.const_get(exception.fault_klass)
|
120
|
-
raise_fault(fault_klass, exception)
|
121
|
-
end
|
122
|
-
|
123
|
-
def raise_dynamic_faults?
|
124
|
-
false
|
125
|
-
end
|
126
|
-
|
127
|
-
def throw!(command)
|
128
|
-
return if command.success?
|
129
|
-
|
130
|
-
send(:"#{command.status}!", command)
|
131
|
-
end
|
132
|
-
|
133
59
|
FAULTS.each do |f|
|
134
60
|
# eg: error(object)
|
135
61
|
define_method(:"#{f}") do |object|
|
136
|
-
|
137
|
-
|
62
|
+
derive_fault_from(object)
|
63
|
+
@status = f
|
138
64
|
end
|
139
65
|
|
140
66
|
# eg: invalid!(object)
|
141
67
|
define_method(:"#{f}!") do |object|
|
142
68
|
send(:"#{f}", object)
|
143
|
-
|
69
|
+
raise fault(f.capitalize, object)
|
144
70
|
end
|
145
71
|
|
146
72
|
# eg: on_noop(exception)
|
147
73
|
define_method(:"on_#{f}") do |_exception|
|
148
|
-
# Define in your class to run code when a
|
74
|
+
# Define in your class to run code when a
|
75
|
+
# Lite::Command::Fault or StandardError happens
|
149
76
|
end
|
150
77
|
end
|
151
78
|
|
@@ -3,14 +3,11 @@
|
|
3
3
|
module Lite
|
4
4
|
module Command
|
5
5
|
|
6
|
-
# State represents the state of the executable code. Once `execute`
|
7
|
-
# is ran, it will always complete or dnf if a fault is thrown by a
|
8
|
-
# child command.
|
9
6
|
STATES = [
|
10
|
-
PENDING
|
11
|
-
EXECUTING
|
12
|
-
COMPLETE
|
13
|
-
|
7
|
+
PENDING = "pending",
|
8
|
+
EXECUTING = "executing",
|
9
|
+
COMPLETE = "complete",
|
10
|
+
INTERRUPTED = "interrupted"
|
14
11
|
].freeze
|
15
12
|
|
16
13
|
module Internals
|
@@ -19,21 +16,18 @@ module Lite
|
|
19
16
|
def execute
|
20
17
|
around_execution { call }
|
21
18
|
rescue StandardError => e
|
22
|
-
|
19
|
+
f = e.respond_to?(:type) ? e.type : ERROR
|
23
20
|
|
24
|
-
send(:"#{
|
21
|
+
send(:"#{f}", e)
|
25
22
|
after_execution
|
26
|
-
send(:"on_#{
|
23
|
+
send(:"on_#{f}", e)
|
27
24
|
end
|
28
25
|
|
29
26
|
def execute!
|
30
27
|
around_execution { call }
|
31
28
|
rescue StandardError => e
|
32
29
|
after_execution
|
33
|
-
|
34
|
-
raise(e) unless raise_dynamic_faults? && e.is_a?(Lite::Command::Fault)
|
35
|
-
|
36
|
-
raise_dynamic_fault(e)
|
30
|
+
raise(e)
|
37
31
|
end
|
38
32
|
|
39
33
|
def state
|
@@ -41,14 +35,14 @@ module Lite
|
|
41
35
|
end
|
42
36
|
|
43
37
|
def executed?
|
44
|
-
|
38
|
+
complete? || interrupted?
|
45
39
|
end
|
46
40
|
|
47
41
|
STATES.each do |s|
|
48
|
-
# eg:
|
42
|
+
# eg: executing?
|
49
43
|
define_method(:"#{s}?") { state == s }
|
50
44
|
|
51
|
-
# eg:
|
45
|
+
# eg: interrupted!
|
52
46
|
define_method(:"#{s}!") { @state = s }
|
53
47
|
end
|
54
48
|
|
@@ -63,7 +57,7 @@ module Lite
|
|
63
57
|
end
|
64
58
|
|
65
59
|
def after_execution
|
66
|
-
fault? ?
|
60
|
+
fault? ? interrupted! : complete!
|
67
61
|
on_after_execution
|
68
62
|
stop_monotonic_time
|
69
63
|
append_execution_result
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Command
|
5
|
+
module Internals
|
6
|
+
module Faultable
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
attr_reader :caused_by, :thrown_by, :reason
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def reason?(str)
|
15
|
+
return true if str.nil?
|
16
|
+
|
17
|
+
reason == str
|
18
|
+
end
|
19
|
+
|
20
|
+
def caused_fault?
|
21
|
+
caused_by == self
|
22
|
+
end
|
23
|
+
|
24
|
+
def threw_fault?
|
25
|
+
thrown_by == self
|
26
|
+
end
|
27
|
+
|
28
|
+
def thrown?
|
29
|
+
fault? && !caused_fault?
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def throw!(command)
|
35
|
+
return if command.success?
|
36
|
+
|
37
|
+
send(:"#{command.status}!", command)
|
38
|
+
end
|
39
|
+
|
40
|
+
def derive_caused_by_from(object)
|
41
|
+
(object.caused_by if object.respond_to?(:caused_by)) || self
|
42
|
+
end
|
43
|
+
|
44
|
+
def derive_thrown_by_from(object)
|
45
|
+
if object.respond_to?(:executed?) && object.executed?
|
46
|
+
object
|
47
|
+
else
|
48
|
+
(object.thrown_by if object.respond_to?(:thrown_by)) || caused_by
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def derive_reason_from(object)
|
53
|
+
if object.respond_to?(:reason)
|
54
|
+
object.reason
|
55
|
+
elsif object.respond_to?(:message)
|
56
|
+
"[#{object.class.name}] #{object.message}".chomp(".")
|
57
|
+
else
|
58
|
+
object
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def derive_fault_from(object)
|
63
|
+
@caused_by ||= derive_caused_by_from(object)
|
64
|
+
@thrown_by ||= derive_thrown_by_from(object)
|
65
|
+
@reason ||= derive_reason_from(object)
|
66
|
+
end
|
67
|
+
|
68
|
+
def raise_dynamic_faults?
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# eg: Lite::Command::Noop.new(...) or Users::ResetPassword::Noop.new(...)
|
73
|
+
def fault(type, thrower)
|
74
|
+
klass = raise_dynamic_faults? ? self.class : Lite::Command
|
75
|
+
fault = klass.const_get(type.to_s).new(reason, caused_by, self)
|
76
|
+
fault.set_backtrace(thrower.backtrace) if thrower.respond_to?(:backtrace)
|
77
|
+
fault
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -16,7 +16,7 @@ module Lite
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def outcome
|
19
|
-
return state if pending? ||
|
19
|
+
return state if pending? || thrown?
|
20
20
|
|
21
21
|
status
|
22
22
|
end
|
@@ -34,8 +34,8 @@ module Lite
|
|
34
34
|
state:,
|
35
35
|
status:,
|
36
36
|
reason:,
|
37
|
-
|
38
|
-
|
37
|
+
caused_by: caused_by&.index,
|
38
|
+
thrown_by: thrown_by&.index,
|
39
39
|
runtime:
|
40
40
|
}.compact
|
41
41
|
end
|
data/lib/lite/command/version.rb
CHANGED
data/lib/lite/command.rb
CHANGED
@@ -5,6 +5,7 @@ require "generators/rails/command_generator" if defined?(Rails::Generators)
|
|
5
5
|
require "lite/command/version"
|
6
6
|
require "lite/command/internals/callable"
|
7
7
|
require "lite/command/internals/executable"
|
8
|
+
require "lite/command/internals/faultable"
|
8
9
|
require "lite/command/internals/resultable"
|
9
10
|
require "lite/command/fault"
|
10
11
|
require "lite/command/context"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lite-command
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juan Gomez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ostruct
|
@@ -196,6 +196,7 @@ files:
|
|
196
196
|
- lib/lite/command/fault.rb
|
197
197
|
- lib/lite/command/internals/callable.rb
|
198
198
|
- lib/lite/command/internals/executable.rb
|
199
|
+
- lib/lite/command/internals/faultable.rb
|
199
200
|
- lib/lite/command/internals/resultable.rb
|
200
201
|
- lib/lite/command/version.rb
|
201
202
|
- lite-command.gemspec
|
@@ -219,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
220
|
- !ruby/object:Gem::Version
|
220
221
|
version: '0'
|
221
222
|
requirements: []
|
222
|
-
rubygems_version: 3.5.
|
223
|
+
rubygems_version: 3.5.20
|
223
224
|
signing_key:
|
224
225
|
specification_version: 4
|
225
226
|
summary: Ruby Command based framework (aka service objects)
|