cmdx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.rspec +4 -0
- data/.rubocop.yml +64 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/Rakefile +12 -0
- data/docs/basics/call.md +31 -0
- data/docs/basics/context.md +67 -0
- data/docs/basics/run.md +34 -0
- data/docs/basics/setup.md +32 -0
- data/docs/batch.md +53 -0
- data/docs/configuration.md +57 -0
- data/docs/example.md +82 -0
- data/docs/getting_started.md +47 -0
- data/docs/hooks.md +59 -0
- data/docs/interruptions/exceptions.md +29 -0
- data/docs/interruptions/faults.md +89 -0
- data/docs/interruptions/halt.md +80 -0
- data/docs/logging.md +102 -0
- data/docs/outcomes/result.md +17 -0
- data/docs/outcomes/states.md +31 -0
- data/docs/outcomes/statuses.md +33 -0
- data/docs/parameters/coercions.md +52 -0
- data/docs/parameters/defaults.md +47 -0
- data/docs/parameters/definitions.md +123 -0
- data/docs/parameters/namespacing.md +57 -0
- data/docs/parameters/validations.md +312 -0
- data/docs/tips_and_tricks.md +79 -0
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/batch.rb +43 -0
- data/lib/cmdx/coercions/array.rb +15 -0
- data/lib/cmdx/coercions/big_decimal.rb +23 -0
- data/lib/cmdx/coercions/boolean.rb +27 -0
- data/lib/cmdx/coercions/complex.rb +21 -0
- data/lib/cmdx/coercions/date.rb +26 -0
- data/lib/cmdx/coercions/date_time.rb +26 -0
- data/lib/cmdx/coercions/float.rb +21 -0
- data/lib/cmdx/coercions/hash.rb +31 -0
- data/lib/cmdx/coercions/integer.rb +21 -0
- data/lib/cmdx/coercions/rational.rb +21 -0
- data/lib/cmdx/coercions/string.rb +15 -0
- data/lib/cmdx/coercions/time.rb +26 -0
- data/lib/cmdx/coercions/virtual.rb +15 -0
- data/lib/cmdx/configuration.rb +25 -0
- data/lib/cmdx/context.rb +15 -0
- data/lib/cmdx/core_ext/hash.rb +36 -0
- data/lib/cmdx/core_ext/module.rb +48 -0
- data/lib/cmdx/core_ext/object.rb +55 -0
- data/lib/cmdx/error.rb +23 -0
- data/lib/cmdx/errors.rb +92 -0
- data/lib/cmdx/fault.rb +47 -0
- data/lib/cmdx/faults.rb +11 -0
- data/lib/cmdx/immutator.rb +21 -0
- data/lib/cmdx/lazy_struct.rb +79 -0
- data/lib/cmdx/log_formatters/json.rb +13 -0
- data/lib/cmdx/log_formatters/key_value.rb +13 -0
- data/lib/cmdx/log_formatters/line.rb +14 -0
- data/lib/cmdx/log_formatters/logstash.rb +18 -0
- data/lib/cmdx/log_formatters/raw.rb +13 -0
- data/lib/cmdx/logger.rb +16 -0
- data/lib/cmdx/parameter.rb +101 -0
- data/lib/cmdx/parameter_inspector.rb +23 -0
- data/lib/cmdx/parameter_serializer.rb +20 -0
- data/lib/cmdx/parameter_validator.rb +19 -0
- data/lib/cmdx/parameter_value.rb +136 -0
- data/lib/cmdx/parameters.rb +34 -0
- data/lib/cmdx/parameters_inspector.rb +13 -0
- data/lib/cmdx/parameters_serializer.rb +13 -0
- data/lib/cmdx/railtie.rb +32 -0
- data/lib/cmdx/result.rb +170 -0
- data/lib/cmdx/result_inspector.rb +31 -0
- data/lib/cmdx/result_logger.rb +22 -0
- data/lib/cmdx/result_serializer.rb +38 -0
- data/lib/cmdx/run.rb +33 -0
- data/lib/cmdx/run_inspector.rb +21 -0
- data/lib/cmdx/run_serializer.rb +16 -0
- data/lib/cmdx/task.rb +151 -0
- data/lib/cmdx/task_hook.rb +18 -0
- data/lib/cmdx/utils/datetime_formatter.rb +17 -0
- data/lib/cmdx/utils/method_name.rb +24 -0
- data/lib/cmdx/utils/runtime.rb +19 -0
- data/lib/cmdx/validators/custom.rb +20 -0
- data/lib/cmdx/validators/exclusion.rb +51 -0
- data/lib/cmdx/validators/format.rb +27 -0
- data/lib/cmdx/validators/inclusion.rb +51 -0
- data/lib/cmdx/validators/length.rb +114 -0
- data/lib/cmdx/validators/numeric.rb +114 -0
- data/lib/cmdx/validators/presence.rb +27 -0
- data/lib/cmdx/version.rb +7 -0
- data/lib/cmdx.rb +80 -0
- data/lib/generators/cmdx/batch_generator.rb +30 -0
- data/lib/generators/cmdx/install_generator.rb +15 -0
- data/lib/generators/cmdx/task_generator.rb +30 -0
- data/lib/generators/cmdx/templates/batch.rb.tt +7 -0
- data/lib/generators/cmdx/templates/install.rb +23 -0
- data/lib/generators/cmdx/templates/task.rb.tt +9 -0
- data/lib/locales/en.yml +36 -0
- data/lib/locales/es.yml +36 -0
- metadata +288 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Date
|
6
|
+
|
7
|
+
ANALOG_TYPES = %w[Date DateTime Time].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(value, options = {})
|
12
|
+
return value if ANALOG_TYPES.include?(value.class.name)
|
13
|
+
return ::Date.strptime(value, options[:format]) if options[:format]
|
14
|
+
|
15
|
+
::Date.parse(value)
|
16
|
+
rescue TypeError, ::Date::Error
|
17
|
+
raise CoercionError, I18n.t(
|
18
|
+
"cmdx.coercions.into_a",
|
19
|
+
type: "date",
|
20
|
+
default: "could not coerce into a date"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module DateTime
|
6
|
+
|
7
|
+
ANALOG_TYPES = %w[Date DateTime Time].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(value, options = {})
|
12
|
+
return value if ANALOG_TYPES.include?(value.class.name)
|
13
|
+
return ::DateTime.strptime(value, options[:format]) if options[:format]
|
14
|
+
|
15
|
+
::DateTime.parse(value)
|
16
|
+
rescue TypeError, ::Date::Error
|
17
|
+
raise CoercionError, I18n.t(
|
18
|
+
"cmdx.coercions.into_a",
|
19
|
+
type: "datetime",
|
20
|
+
default: "could not coerce into a datetime"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Float
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def call(value, _options = {})
|
10
|
+
Float(value)
|
11
|
+
rescue ArgumentError, TypeError
|
12
|
+
raise CoercionError, I18n.t(
|
13
|
+
"cmdx.coercions.into_a",
|
14
|
+
type: "float",
|
15
|
+
default: "could not coerce into a float"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Hash
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def call(value, _options = {})
|
10
|
+
case value.class.name
|
11
|
+
when "Hash", "ActionController::Parameters" then value
|
12
|
+
when "Array" then ::Hash[*value]
|
13
|
+
else raise_coercion_error!
|
14
|
+
end
|
15
|
+
rescue ArgumentError, TypeError
|
16
|
+
raise_coercion_error!
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def raise_coercion_error!
|
22
|
+
raise CoercionError, I18n.t(
|
23
|
+
"cmdx.coercions.into_a",
|
24
|
+
type: "hash",
|
25
|
+
default: "could not coerce into a hash"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Integer
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def call(value, _options = {})
|
10
|
+
Integer(value)
|
11
|
+
rescue ArgumentError, TypeError
|
12
|
+
raise CoercionError, I18n.t(
|
13
|
+
"cmdx.coercions.into_an",
|
14
|
+
type: "integer",
|
15
|
+
default: "could not coerce into an integer"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Rational
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def call(value, _options = {})
|
10
|
+
Rational(value)
|
11
|
+
rescue ArgumentError, TypeError
|
12
|
+
raise CoercionError, I18n.t(
|
13
|
+
"cmdx.coercions.into_a",
|
14
|
+
type: "rational",
|
15
|
+
default: "could not coerce into a rational"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Coercions
|
5
|
+
module Time
|
6
|
+
|
7
|
+
ANALOG_TYPES = %w[Date DateTime Time].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(value, options = {})
|
12
|
+
return value if ANALOG_TYPES.include?(value.class.name)
|
13
|
+
return ::Time.strptime(value, options[:format]) if options[:format]
|
14
|
+
|
15
|
+
::Time.parse(value)
|
16
|
+
rescue ArgumentError, TypeError
|
17
|
+
raise CoercionError, I18n.t(
|
18
|
+
"cmdx.coercions.into_a",
|
19
|
+
type: "time",
|
20
|
+
default: "could not coerce into a time"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def configuration
|
8
|
+
@configuration || reset_configuration!
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure
|
12
|
+
yield(configuration)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset_configuration!
|
16
|
+
@configuration = LazyStruct.new(
|
17
|
+
logger: ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new),
|
18
|
+
task_halt: CMDx::Result::FAILED,
|
19
|
+
task_timeout: nil,
|
20
|
+
batch_halt: CMDx::Result::FAILED,
|
21
|
+
batch_timeout: nil
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/cmdx/context.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module CoreExt
|
5
|
+
module HashExtensions
|
6
|
+
|
7
|
+
def __cmdx_fetch(key)
|
8
|
+
case key
|
9
|
+
when Symbol then fetch(key) { self[key.to_s] }
|
10
|
+
when String then fetch(key) { self[key.to_sym] }
|
11
|
+
else self[key]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def __cmdx_key?(key)
|
16
|
+
key?(key) || key?(
|
17
|
+
case key
|
18
|
+
when Symbol then key.to_s
|
19
|
+
when String then key.to_sym
|
20
|
+
end
|
21
|
+
)
|
22
|
+
rescue NoMethodError
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def __cmdx_respond_to?(key, include_private = false)
|
27
|
+
respond_to?(key.to_sym, include_private) || __cmdx_key?(key)
|
28
|
+
rescue NoMethodError
|
29
|
+
__cmdx_key?(key)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Hash.include(CMDx::CoreExt::HashExtensions)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module CoreExt
|
5
|
+
module ModuleExtensions
|
6
|
+
|
7
|
+
def __cmdx_attr_delegator(*methods, **options)
|
8
|
+
methods.each do |method|
|
9
|
+
method_name = Utils::MethodName.call(method, options.fetch(:to), options)
|
10
|
+
|
11
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
12
|
+
object = (options[:to] == :class ? self.class : send(options[:to]))
|
13
|
+
|
14
|
+
unless options[:allow_missing] || object.respond_to?(method, true)
|
15
|
+
raise NoMethodError,
|
16
|
+
"undefined method `#{method}' for #{options[:to]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
object.send(method, *args, **kwargs, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
case options
|
23
|
+
in { protected: true } then send(:protected, method_name)
|
24
|
+
in { private: true } then send(:private, method_name)
|
25
|
+
else # Leave public
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def __cmdx_attr_setting(method, **options)
|
31
|
+
define_singleton_method(method) do
|
32
|
+
@cmd_facets ||= {}
|
33
|
+
return @cmd_facets[method] if @cmd_facets.key?(method)
|
34
|
+
|
35
|
+
value = superclass.__cmdx_try(method)
|
36
|
+
return @cmd_facets[method] = value.dup unless value.nil?
|
37
|
+
|
38
|
+
default = options[:default]
|
39
|
+
value = default.__cmdx_call
|
40
|
+
@cmd_facets[method] = default.is_a?(Proc) ? value : value.dup
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Module.include(CMDx::CoreExt::ModuleExtensions)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module CoreExt
|
5
|
+
module ObjectExtensions
|
6
|
+
|
7
|
+
alias __cmdx_respond_to? respond_to?
|
8
|
+
|
9
|
+
def __cmdx_try(key, ...)
|
10
|
+
if key.is_a?(Proc)
|
11
|
+
return instance_eval(&key) unless is_a?(Module) || key.inspect.include?("(lambda)")
|
12
|
+
|
13
|
+
key.call(...)
|
14
|
+
elsif respond_to?(key, true)
|
15
|
+
send(key, ...)
|
16
|
+
elsif is_a?(Hash)
|
17
|
+
__cmdx_fetch(key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def __cmdx_eval(options = {})
|
22
|
+
if options[:if] && options[:unless]
|
23
|
+
__cmdx_try(options[:if]) && !__cmdx_try(options[:unless])
|
24
|
+
elsif options[:if]
|
25
|
+
__cmdx_try(options[:if])
|
26
|
+
elsif options[:unless]
|
27
|
+
!__cmdx_try(options[:unless])
|
28
|
+
else
|
29
|
+
options.fetch(:default, true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def __cmdx_yield(key, ...)
|
34
|
+
if key.is_a?(Symbol) || key.is_a?(String)
|
35
|
+
return key unless respond_to?(key, true)
|
36
|
+
|
37
|
+
send(key, ...)
|
38
|
+
elsif is_a?(Hash) || key.is_a?(Proc)
|
39
|
+
__cmdx_try(key, ...)
|
40
|
+
else
|
41
|
+
key
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def __cmdx_call(...)
|
46
|
+
return self unless respond_to?(:call)
|
47
|
+
|
48
|
+
call(...)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Object.include(CMDx::CoreExt::ObjectExtensions)
|
data/lib/cmdx/error.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
|
5
|
+
# Base of all CMDx errors
|
6
|
+
Error = Class.new(StandardError)
|
7
|
+
|
8
|
+
# Raised when value could not be coerced to defined type
|
9
|
+
CoercionError = Class.new(Error)
|
10
|
+
|
11
|
+
# Raised when call execution time exceeds max allowed
|
12
|
+
TimeoutError = Class.new(Interrupt)
|
13
|
+
|
14
|
+
# Raised when call method not defined in implementing class
|
15
|
+
UndefinedCallError = Class.new(Error)
|
16
|
+
|
17
|
+
# Raised when unknown coercion type
|
18
|
+
UnknownCoercionError = Class.new(Error)
|
19
|
+
|
20
|
+
# Raised when value failed a validation
|
21
|
+
ValidationError = Class.new(Error)
|
22
|
+
|
23
|
+
end
|
data/lib/cmdx/errors.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
class Errors
|
5
|
+
|
6
|
+
__cmdx_attr_delegator :clear, :delete, :each, :empty?, :key?, :keys, :size, :values, to: :errors
|
7
|
+
|
8
|
+
attr_reader :errors
|
9
|
+
|
10
|
+
alias attribute_names keys
|
11
|
+
alias blank? empty?
|
12
|
+
alias valid? empty?
|
13
|
+
alias has_key? key?
|
14
|
+
alias include? key?
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@errors = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(key, value)
|
21
|
+
errors[key] ||= []
|
22
|
+
errors[key] << value
|
23
|
+
errors[key].uniq!
|
24
|
+
end
|
25
|
+
alias []= add
|
26
|
+
|
27
|
+
def added?(key, val)
|
28
|
+
return false unless key?(key)
|
29
|
+
|
30
|
+
errors[key].include?(val)
|
31
|
+
end
|
32
|
+
alias of_kind? added?
|
33
|
+
|
34
|
+
def each
|
35
|
+
errors.each_key do |key|
|
36
|
+
errors[key].each { |val| yield(key, val) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def full_message(key, value)
|
41
|
+
"#{key} #{value}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def full_messages
|
45
|
+
errors.each_with_object([]) do |(key, arr), memo|
|
46
|
+
arr.each { |val| memo << full_message(key, val) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias to_a full_messages
|
50
|
+
|
51
|
+
def full_messages_for(key)
|
52
|
+
return [] unless key?(key)
|
53
|
+
|
54
|
+
errors[key].map { |val| full_message(key, val) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def invalid?
|
58
|
+
!valid?
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge!(hash)
|
62
|
+
errors.merge!(hash) do |_, arr1, arr2|
|
63
|
+
arr3 = arr1 + arr2
|
64
|
+
arr3.uniq!
|
65
|
+
arr3
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def messages_for(key)
|
70
|
+
return [] unless key?(key)
|
71
|
+
|
72
|
+
errors[key]
|
73
|
+
end
|
74
|
+
alias [] messages_for
|
75
|
+
|
76
|
+
def present?
|
77
|
+
!blank?
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_hash(full_messages = false)
|
81
|
+
return errors unless full_messages
|
82
|
+
|
83
|
+
errors.each_with_object({}) do |(key, arr), memo|
|
84
|
+
memo[key] = arr.map { |val| full_message(key, val) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
alias messages to_hash
|
88
|
+
alias group_by_attribute to_hash
|
89
|
+
alias as_json to_hash
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
data/lib/cmdx/fault.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
class Fault < Error
|
5
|
+
|
6
|
+
__cmdx_attr_delegator :task, :run, :context, to: :result
|
7
|
+
|
8
|
+
attr_reader :result
|
9
|
+
|
10
|
+
def initialize(result)
|
11
|
+
@result = result
|
12
|
+
super(result.metadata[:reason] || I18n.t("cmdx.faults.unspecified", default: "no reason given"))
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def build(result)
|
18
|
+
fault = CMDx.const_get(result.status.capitalize)
|
19
|
+
fault.new(result)
|
20
|
+
end
|
21
|
+
|
22
|
+
def for?(*tasks)
|
23
|
+
temp_fault = Class.new(self) do
|
24
|
+
def self.===(other)
|
25
|
+
other.is_a?(superclass) && @tasks.any? { |task| other.task.is_a?(task) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
temp_fault.tap { |c| c.instance_variable_set(:@tasks, tasks) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches?(&block)
|
33
|
+
raise ArgumentError, "a block is required" unless block_given?
|
34
|
+
|
35
|
+
temp_fault = Class.new(self) do
|
36
|
+
def self.===(other)
|
37
|
+
other.is_a?(superclass) && @block.call(other)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
temp_fault.tap { |c| c.instance_variable_set(:@block, block) }
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/cmdx/faults.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
module Immutator
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def call(task)
|
9
|
+
# Stubbing on frozen objects is not allowed
|
10
|
+
return if (ENV.fetch("RAILS_ENV", nil) || ENV.fetch("RACK_ENV", nil)) == "test"
|
11
|
+
|
12
|
+
task.freeze
|
13
|
+
task.result.freeze
|
14
|
+
return unless task.result.index.zero?
|
15
|
+
|
16
|
+
task.context.freeze
|
17
|
+
task.run.freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
class LazyStruct
|
5
|
+
|
6
|
+
def initialize(args = {})
|
7
|
+
unless args.respond_to?(:to_h)
|
8
|
+
raise ArgumentError,
|
9
|
+
"must be respond to `to_h`"
|
10
|
+
end
|
11
|
+
|
12
|
+
@table = args.transform_keys(&:to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@table[key.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch!(key, ...)
|
20
|
+
@table.fetch(key.to_sym, ...)
|
21
|
+
end
|
22
|
+
|
23
|
+
def store!(key, value)
|
24
|
+
@table[key.to_sym] = value
|
25
|
+
end
|
26
|
+
alias []= store!
|
27
|
+
|
28
|
+
def merge!(args = {})
|
29
|
+
args.to_h.each { |key, value| store!(key, value) }
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete!(key, &)
|
34
|
+
@table.delete(key.to_sym, &)
|
35
|
+
end
|
36
|
+
alias delete_field! delete!
|
37
|
+
|
38
|
+
def eql?(other)
|
39
|
+
other.is_a?(self.class) && (to_h == other.to_h)
|
40
|
+
end
|
41
|
+
alias == eql?
|
42
|
+
|
43
|
+
def dig(key, *keys)
|
44
|
+
begin
|
45
|
+
key = key.to_sym
|
46
|
+
rescue NoMethodError
|
47
|
+
raise TypeError, "#{key} is not a symbol nor a string"
|
48
|
+
end
|
49
|
+
|
50
|
+
@table.dig(key, *keys)
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_pair(&)
|
54
|
+
@table.each_pair(&)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_h(&)
|
58
|
+
@table.to_h(&)
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"#<#{self.class}#{@table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
|
63
|
+
end
|
64
|
+
alias to_s inspect
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def method_missing(method_name, *args, **_kwargs, &)
|
69
|
+
@table.fetch(method_name.to_sym) do
|
70
|
+
store!(method_name[0..-2], args.first) if method_name.end_with?("=")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond_to_missing?(method_name, include_private = false)
|
75
|
+
@table.key?(method_name.to_sym) || super
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|