easy_command 1.0.0.pre.rc1
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/.github/CODEOWNERS +5 -0
- data/.github/workflows/ci.yaml +52 -0
- data/.github/workflows/lint.yaml +38 -0
- data/.github/workflows/release.yml +43 -0
- data/.gitignore +14 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_maintainer_style.yml +34 -0
- data/.rubocop_style.yml +142 -0
- data/.rubocop_todo.yml +453 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +736 -0
- data/easy_command.gemspec +26 -0
- data/lib/easy_command/chainable.rb +16 -0
- data/lib/easy_command/errors.rb +85 -0
- data/lib/easy_command/result.rb +53 -0
- data/lib/easy_command/ruby-2-7-specific.rb +49 -0
- data/lib/easy_command/ruby-2-specific.rb +53 -0
- data/lib/easy_command/ruby-3-specific.rb +49 -0
- data/lib/easy_command/spec_helpers/command_matchers.rb +89 -0
- data/lib/easy_command/spec_helpers/mock_command_helper.rb +89 -0
- data/lib/easy_command/spec_helpers.rb +2 -0
- data/lib/easy_command/version.rb +3 -0
- data/lib/easy_command.rb +94 -0
- data/locales/en.yml +2 -0
- data/release-please-config.json +11 -0
- data/spec/easy_command/errors_spec.rb +121 -0
- data/spec/easy_command/result_spec.rb +176 -0
- data/spec/easy_command_spec.rb +298 -0
- data/spec/factories/addition_command.rb +12 -0
- data/spec/factories/callback_command.rb +20 -0
- data/spec/factories/failure_command.rb +12 -0
- data/spec/factories/missed_call_command.rb +7 -0
- data/spec/factories/multiplication_command.rb +12 -0
- data/spec/factories/sub_command.rb +19 -0
- data/spec/factories/subcommand_command.rb +14 -0
- data/spec/factories/success_command.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- metadata +102 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'easy_command/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.required_ruby_version = '>= 2.7'
|
7
|
+
s.name = 'easy_command'
|
8
|
+
s.version = EasyCommand::VERSION
|
9
|
+
s.authors = ['Swile']
|
10
|
+
s.email = ['ruby-maintainers@swile.co']
|
11
|
+
s.summary = 'Easy way to build and manage commands (service objects)'
|
12
|
+
s.description = 'Easy way to build and manage commands (service objects)'
|
13
|
+
s.homepage = 'http://github.com/Swile/easy-command'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.metadata['rubygems_mfa_required'] = 'true'
|
17
|
+
|
18
|
+
s.metadata["source_code_uri"] = "https://github.com/Swile/easy-command"
|
19
|
+
s.metadata["github_repo"] = "ssh://github.com/Swile/easy-command"
|
20
|
+
|
21
|
+
s.files = `git ls-files -z`.split("\x0")
|
22
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
s.require_paths = ['lib']
|
24
|
+
|
25
|
+
s.add_development_dependency 'bundler', '~> 2.0' # rubocop:disable Gemspec/DevelopmentDependencies
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module EasyCommand
|
2
|
+
module Chainable
|
3
|
+
def then(other)
|
4
|
+
if success?
|
5
|
+
other.call(result)
|
6
|
+
else
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
alias_method :|, :then
|
11
|
+
|
12
|
+
def self.included(klass)
|
13
|
+
klass.define_method(:call) { self } unless klass.instance_methods.include? :call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module EasyCommand
|
2
|
+
class NotImplementedError < ::StandardError; end
|
3
|
+
|
4
|
+
class Errors < Hash
|
5
|
+
attr_reader :source
|
6
|
+
|
7
|
+
def initialize(source: nil)
|
8
|
+
@source = source
|
9
|
+
super()
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?(attribute, code)
|
13
|
+
fetch(attribute, []).any? { |e| e[:code] == code }
|
14
|
+
end
|
15
|
+
alias_method :has_error?, :exists?
|
16
|
+
|
17
|
+
def add(attribute, code, message_or_key = nil, **options)
|
18
|
+
code = code.to_sym
|
19
|
+
message_or_key ||= code
|
20
|
+
|
21
|
+
if defined?(I18n)
|
22
|
+
# Can't use `I18n.exists?` because it doesn't accept a scope: kwarg
|
23
|
+
message =
|
24
|
+
begin
|
25
|
+
I18n.t!(message_or_key, scope: source&.i18n_scope, **options)
|
26
|
+
rescue I18n::MissingTranslationData
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
message ||= message_or_key
|
31
|
+
|
32
|
+
self[attribute] ||= []
|
33
|
+
self[attribute] << { code: code, message: message }
|
34
|
+
self[attribute].uniq!
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge_from(object)
|
38
|
+
raise ArgumentError unless object.respond_to?(:errors)
|
39
|
+
errors =
|
40
|
+
if object.errors.respond_to?(:messages)
|
41
|
+
object.errors.messages.each_with_object({}) do |(attribute, messages), object_errors|
|
42
|
+
object_errors[attribute] = messages.
|
43
|
+
zip(object.errors.details[attribute]).
|
44
|
+
map { |message, detail| [detail[:error], message] }
|
45
|
+
end
|
46
|
+
else
|
47
|
+
object.errors
|
48
|
+
end
|
49
|
+
|
50
|
+
add_multiple_errors(errors)
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_multiple_errors(errors_hash)
|
54
|
+
errors_hash.each do |key, values|
|
55
|
+
values.each do |value|
|
56
|
+
if value.is_a?(Hash)
|
57
|
+
code = value[:code]
|
58
|
+
message_or_key = value[:message]
|
59
|
+
else
|
60
|
+
code = value.first
|
61
|
+
message_or_key = value.last || value.first
|
62
|
+
end
|
63
|
+
add(key, code, message_or_key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# For SimpleCommand gem compatibility, to ease migration.
|
69
|
+
def full_messages
|
70
|
+
messages = []
|
71
|
+
each do |attribute, errors|
|
72
|
+
errors.each do |error|
|
73
|
+
messages << full_message(attribute, error[:message])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
messages
|
77
|
+
end
|
78
|
+
|
79
|
+
def full_message(attribute, message)
|
80
|
+
return message if attribute == :base
|
81
|
+
attr_name = attribute.to_s.tr('.', '_').capitalize
|
82
|
+
format("%s %s", attr_name, message)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'easy_command/chainable'
|
2
|
+
|
3
|
+
module EasyCommand
|
4
|
+
class Result
|
5
|
+
include Chainable
|
6
|
+
|
7
|
+
def initialize(content)
|
8
|
+
@content = content
|
9
|
+
end
|
10
|
+
|
11
|
+
def result
|
12
|
+
@content
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors
|
16
|
+
EasyCommand::Errors.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure?; false; end
|
20
|
+
def success?; true; end
|
21
|
+
|
22
|
+
def on_success
|
23
|
+
yield(result) if success?
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_failure
|
28
|
+
yield(errors) if failure?
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Success < Result; end
|
34
|
+
|
35
|
+
class Params < Result; end
|
36
|
+
|
37
|
+
class Failure < Result
|
38
|
+
def success?; false; end
|
39
|
+
def failure?; true; end
|
40
|
+
|
41
|
+
def with_errors(errors)
|
42
|
+
@_errors = errors
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def errors
|
47
|
+
@_errors ||=
|
48
|
+
EasyCommand::Errors.new.tap do |errors|
|
49
|
+
errors.add(:result, :failure)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# rubocop:disable Naming/FileName
|
2
|
+
# rubocop:enable Naming/FileName
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module EasyCommand
|
6
|
+
class Result
|
7
|
+
module ClassMethods
|
8
|
+
ruby2_keywords def [](*args)
|
9
|
+
new(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
ruby2_keywords def call(*args)
|
17
|
+
new(*args).call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ruby2_keywords def abort(*args)
|
22
|
+
errors.add(*args)
|
23
|
+
raise ExitError
|
24
|
+
end
|
25
|
+
|
26
|
+
module LegacyErrorHandling
|
27
|
+
# Convenience/retrocompatibility aliases
|
28
|
+
def self.errors_legacy_alias(method, errors_method)
|
29
|
+
ruby2_keywords define_method(method) { |*args|
|
30
|
+
warn "/!\\ #{method} is deprecated, please use errors.#{errors_method} instead."
|
31
|
+
errors.__send__ errors_method, *args
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ruby2_keywords def assert_subcommand(klass, *args)
|
37
|
+
command_instance = klass.new(*args).as_sub_command
|
38
|
+
(@sub_commands ||= []) << command_instance
|
39
|
+
command = command_instance.call
|
40
|
+
return command.result if command.success?
|
41
|
+
errors.merge_from(command)
|
42
|
+
raise ExitError.new(result: command.result)
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_sub(...)
|
46
|
+
warn "/!\\ 'assert_sub' is deprecated, please use 'assert_subcommand' instead."
|
47
|
+
assert_subcommand(...)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# rubocop:disable Naming/FileName
|
2
|
+
# rubocop:enable Naming/FileName
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module EasyCommand
|
6
|
+
class Result
|
7
|
+
module ClassMethods
|
8
|
+
def [](*args)
|
9
|
+
new(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def call(*args)
|
17
|
+
new(*args).call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def abort(*args)
|
22
|
+
errors.add(*args)
|
23
|
+
raise ExitError
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert(*_args)
|
27
|
+
raise ExitError if errors.any?
|
28
|
+
end
|
29
|
+
|
30
|
+
module LegacyErrorHandling
|
31
|
+
# Convenience/retrocompatibility aliases
|
32
|
+
def self.errors_legacy_alias(method, errors_method)
|
33
|
+
define_method method do |*args|
|
34
|
+
warn "/!\\ #{method} is deprecated, please use errors.#{errors_method} instead."
|
35
|
+
errors.__send__ errors_method, *args
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def assert_subcommand(klass, *args)
|
41
|
+
command_instance = klass.new(*args).as_sub_command
|
42
|
+
(@sub_commands ||= []) << command_instance
|
43
|
+
command = command_instance.call
|
44
|
+
return command.result if command.success?
|
45
|
+
errors.merge_from(command)
|
46
|
+
raise ExitError.new(result: command.result)
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_sub(klass, *args)
|
50
|
+
warn "/!\\ 'assert_sub' is deprecated, please use 'assert_subcommand' instead."
|
51
|
+
assert_subcommand(klass, *args)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# rubocop:disable Naming/FileName
|
2
|
+
# rubocop:enable Naming/FileName
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module EasyCommand
|
6
|
+
class Result
|
7
|
+
module ClassMethods
|
8
|
+
def [](*args, **kwargs)
|
9
|
+
new(*args, **kwargs)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def call(*args, **kwargs)
|
17
|
+
new(*args, **kwargs).call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def abort(*args, **kwargs)
|
22
|
+
errors.add(*args, **kwargs)
|
23
|
+
raise ExitError
|
24
|
+
end
|
25
|
+
|
26
|
+
module LegacyErrorHandling
|
27
|
+
# Convenience/retrocompatibility aliases
|
28
|
+
def self.errors_legacy_alias(method, errors_method)
|
29
|
+
define_method method do |*args, **kwargs|
|
30
|
+
warn "/!\\ #{method} is deprecated, please use errors.#{errors_method} instead."
|
31
|
+
errors.__send__ errors_method, *args, **kwargs
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_subcommand(klass, *args, **kwargs)
|
37
|
+
command_instance = klass.new(*args, **kwargs).as_sub_command
|
38
|
+
(@sub_commands ||= []) << command_instance
|
39
|
+
command = command_instance.call
|
40
|
+
return command.result if command.success?
|
41
|
+
errors.merge_from(command)
|
42
|
+
raise ExitError.new(result: command.result)
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_sub(...)
|
46
|
+
warn "/!\\ 'assert_sub' is deprecated, please use 'assert_subcommand' instead."
|
47
|
+
assert_subcommand(...)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Rewrite this as a pure module definition to remove dependency on RSpec
|
4
|
+
require 'rspec/matchers'
|
5
|
+
|
6
|
+
=begin
|
7
|
+
it { expect(Extinguishers::PayloadValidator).to have_been_called_with_acp(payload) }
|
8
|
+
it { is_expected.to be_failure }
|
9
|
+
it { is_expected.to have_failed }
|
10
|
+
it { is_expected.to have_failed.with_error(:date, :invalid) }
|
11
|
+
it { is_expected.to have_failed.with_error(:date, :invalid, "The format must be iso8601") }
|
12
|
+
it { is_expected.to have_error(:date, :invalid) }
|
13
|
+
it { is_expected.to have_error(:date, :invalid, "The format must be iso8601") }
|
14
|
+
=end
|
15
|
+
module EasyCommand
|
16
|
+
module SpecHelpers
|
17
|
+
module CommandMatchers
|
18
|
+
extend RSpec::Matchers::DSL
|
19
|
+
|
20
|
+
matcher :have_been_called_with_action_controller_parameters do
|
21
|
+
match(notify_expectation_failures: true) do |command_class|
|
22
|
+
expect(command_class).to have_received(:call).
|
23
|
+
with(an_instance_of(ActionController::Parameters)) do |params|
|
24
|
+
expect(params.to_unsafe_h).to match(payload)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_matcher :have_been_called_with_ac_parameters, :have_been_called_with_action_controller_parameters
|
29
|
+
alias_matcher :have_been_called_with_acp, :have_been_called_with_action_controller_parameters
|
30
|
+
|
31
|
+
matcher :have_failed do
|
32
|
+
match(notify_expectation_failures: true) do |result|
|
33
|
+
expect(result).to have_error(@key, @code, @message) if @key.presence
|
34
|
+
result.failure?
|
35
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
36
|
+
@matcher_error_message = e.message
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
chain :with_error do |key, code, message = nil|
|
41
|
+
@key = key
|
42
|
+
@code = code
|
43
|
+
@message = message
|
44
|
+
end
|
45
|
+
|
46
|
+
failure_message do
|
47
|
+
@matcher_error_message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
matcher :have_error do
|
52
|
+
match do |result|
|
53
|
+
if message.presence
|
54
|
+
result.errors[key]&.include?(code: code, message: message)
|
55
|
+
else
|
56
|
+
result.errors.exists?(key, code)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
failure_message do
|
61
|
+
err = "expected #{command_name} to have errors on #{to_txt key} with code #{to_txt code}"
|
62
|
+
err += " and message #{to_txt message}" if message.present?
|
63
|
+
err += "\nactual error for #{to_txt key}: #{@actual.errors[key] || 'nil'}"
|
64
|
+
err
|
65
|
+
end
|
66
|
+
|
67
|
+
def command_name
|
68
|
+
actual.class.name
|
69
|
+
end
|
70
|
+
|
71
|
+
def key
|
72
|
+
expected.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def code
|
76
|
+
expected.second
|
77
|
+
end
|
78
|
+
|
79
|
+
def message
|
80
|
+
expected.third
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_txt(value)
|
84
|
+
value.is_a?(Symbol) ? ":#{value}" : "\"#{value}\""
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCommand
|
4
|
+
module SpecHelpers
|
5
|
+
module MockCommandHelper
|
6
|
+
NO_PARAMS_PASSED = Object.new
|
7
|
+
|
8
|
+
def mock_successful_command(command, result:, params: NO_PARAMS_PASSED)
|
9
|
+
mock_command(command, success: true, result: result, params: params)
|
10
|
+
end
|
11
|
+
|
12
|
+
=begin
|
13
|
+
Example :
|
14
|
+
mock_unsuccessful_command(ExtinguishDebtAndLetterIt, errors: {
|
15
|
+
entry: { not_found: "Couldn't find Entry with 'document_identifier'='foo'" }
|
16
|
+
})
|
17
|
+
|
18
|
+
is equivalent to
|
19
|
+
mock_command(ExtinguishDebtAndLetterIt,
|
20
|
+
success: false,
|
21
|
+
result: nil,
|
22
|
+
errors: {:entry=>[code: :not_found, message: "Couldn't find Entry with 'document_identifier'='foo'"]},
|
23
|
+
)
|
24
|
+
=end
|
25
|
+
def mock_unsuccessful_command(command, errors:, params: NO_PARAMS_PASSED)
|
26
|
+
mock_command(command, success: false, errors: detailed_errors(errors), params: params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def mock_command(command, success:, result: nil, errors: {}, params: NO_PARAMS_PASSED)
|
30
|
+
if Object.const_defined?('FakeCommandErrors')
|
31
|
+
klass = Object.const_get('FakeCommandErrors')
|
32
|
+
else
|
33
|
+
klass = Object.const_set 'FakeCommandErrors', Class.new
|
34
|
+
klass.prepend Command
|
35
|
+
end
|
36
|
+
fake_command = klass.new
|
37
|
+
if errors.any?
|
38
|
+
errors.each do |attr, details|
|
39
|
+
details.each do |detail|
|
40
|
+
fake_command.errors.add(attr, detail[:code], detail[:message])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
double = instance_double(command)
|
45
|
+
allow(double).to receive(:as_sub_command).and_return(double)
|
46
|
+
allow(double).to receive(:errors).and_return(fake_command.errors)
|
47
|
+
monad =
|
48
|
+
if success
|
49
|
+
Command::Success.new(result)
|
50
|
+
else
|
51
|
+
Command::Failure.new(result).with_errors(fake_command.errors)
|
52
|
+
end
|
53
|
+
allow(double).to receive(:call).and_return(monad)
|
54
|
+
allow(double).to receive(:on_success).and_return(double)
|
55
|
+
if params == NO_PARAMS_PASSED
|
56
|
+
allow(command).to receive(:call).and_return(monad)
|
57
|
+
allow(command).to receive(:new).and_return(double)
|
58
|
+
else
|
59
|
+
mock_params, hash_params = extract_mock_params(params)
|
60
|
+
allow(command).to receive(:call).with(*mock_params, **hash_params).and_return(monad)
|
61
|
+
allow(command).to receive(:new).with(*mock_params, **hash_params).and_return(double)
|
62
|
+
end
|
63
|
+
double
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def extract_mock_params(params)
|
69
|
+
if params.is_a? Array
|
70
|
+
kw_params =
|
71
|
+
if params.last.is_a? Hash
|
72
|
+
params.pop
|
73
|
+
else
|
74
|
+
{}
|
75
|
+
end
|
76
|
+
[params, kw_params]
|
77
|
+
else
|
78
|
+
[[params], {}]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def detailed_errors(errors)
|
83
|
+
errors.transform_values do |detail|
|
84
|
+
detail.map { |code, message| { code: code, message: message } }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/easy_command.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'byebug'
|
4
|
+
if RUBY_VERSION >= "3"
|
5
|
+
require "easy_command/ruby-3-specific"
|
6
|
+
elsif RUBY_VERSION.start_with? "2.7"
|
7
|
+
require "easy_command/ruby-2-7-specific"
|
8
|
+
else
|
9
|
+
require "easy_command/ruby-2-specific"
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'easy_command/errors'
|
13
|
+
require 'easy_command/result'
|
14
|
+
require 'easy_command/version'
|
15
|
+
|
16
|
+
module EasyCommand
|
17
|
+
class CommandError < RuntimeError
|
18
|
+
attr_reader :code
|
19
|
+
|
20
|
+
def initialize(message = nil, code = nil)
|
21
|
+
@code = code
|
22
|
+
super(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ExitError < CommandError
|
27
|
+
attr_reader :result
|
28
|
+
|
29
|
+
def initialize(message = nil, code = nil, result: nil)
|
30
|
+
@result = result
|
31
|
+
super(message, code)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.prepended(base)
|
36
|
+
base.extend ClassMethods
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :result
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def self.extended(base)
|
43
|
+
base.i18n_scope = "errors.messages"
|
44
|
+
end
|
45
|
+
attr_accessor :i18n_scope
|
46
|
+
end
|
47
|
+
|
48
|
+
def call
|
49
|
+
raise NotImplementedError unless defined?(super)
|
50
|
+
|
51
|
+
result = super
|
52
|
+
if errors.none?
|
53
|
+
on_success unless @as_sub_command
|
54
|
+
Success[result]
|
55
|
+
else
|
56
|
+
Failure[result].with_errors(errors)
|
57
|
+
end
|
58
|
+
rescue ExitError => e
|
59
|
+
Failure[@result || e.result].with_errors(errors)
|
60
|
+
end
|
61
|
+
|
62
|
+
def abort(*args, result: nil, **kwargs)
|
63
|
+
errors.add(*args, **kwargs)
|
64
|
+
raise ExitError.new(result: result)
|
65
|
+
end
|
66
|
+
|
67
|
+
def assert(*_args, result: nil)
|
68
|
+
raise ExitError.new(result: result) if errors.any?
|
69
|
+
end
|
70
|
+
|
71
|
+
def errors
|
72
|
+
@_errors ||= EasyCommand::Errors.new(source: self.class)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_success
|
76
|
+
(@sub_commands ||= []).each(&:on_success)
|
77
|
+
|
78
|
+
super if defined?(super)
|
79
|
+
end
|
80
|
+
|
81
|
+
module LegacyErrorHandling
|
82
|
+
errors_legacy_alias :clear_errors, :clear
|
83
|
+
errors_legacy_alias :add_error, :add
|
84
|
+
errors_legacy_alias :merge_errors_from, :merge_from
|
85
|
+
errors_legacy_alias :has_error?, :exists?
|
86
|
+
errors_legacy_alias :full_errors, :itself
|
87
|
+
end
|
88
|
+
include LegacyErrorHandling
|
89
|
+
|
90
|
+
def as_sub_command
|
91
|
+
@as_sub_command = true
|
92
|
+
self
|
93
|
+
end
|
94
|
+
end
|
data/locales/en.yml
ADDED