cmdx 1.19.0 → 1.21.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 +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +82 -16
- data/README.md +1 -1
- data/lib/cmdx/attribute.rb +82 -19
- data/lib/cmdx/attribute_registry.rb +79 -8
- data/lib/cmdx/attribute_value.rb +2 -2
- data/lib/cmdx/callback_registry.rb +60 -26
- data/lib/cmdx/chain.rb +34 -5
- data/lib/cmdx/coercion_registry.rb +42 -20
- data/lib/cmdx/coercions/array.rb +2 -2
- data/lib/cmdx/coercions/big_decimal.rb +1 -1
- data/lib/cmdx/coercions/boolean.rb +2 -2
- data/lib/cmdx/coercions/complex.rb +1 -1
- data/lib/cmdx/coercions/date.rb +1 -1
- data/lib/cmdx/coercions/date_time.rb +1 -1
- data/lib/cmdx/coercions/float.rb +1 -1
- data/lib/cmdx/coercions/hash.rb +1 -1
- data/lib/cmdx/coercions/integer.rb +1 -1
- data/lib/cmdx/coercions/rational.rb +1 -1
- data/lib/cmdx/coercions/string.rb +1 -1
- data/lib/cmdx/coercions/symbol.rb +1 -1
- data/lib/cmdx/coercions/time.rb +1 -1
- data/lib/cmdx/configuration.rb +38 -0
- data/lib/cmdx/context.rb +11 -8
- data/lib/cmdx/deprecator.rb +27 -14
- data/lib/cmdx/errors.rb +3 -4
- data/lib/cmdx/exception.rb +7 -0
- data/lib/cmdx/executor.rb +80 -53
- data/lib/cmdx/identifier.rb +4 -6
- data/lib/cmdx/locale.rb +32 -9
- data/lib/cmdx/middleware_registry.rb +43 -23
- data/lib/cmdx/middlewares/correlate.rb +4 -2
- data/lib/cmdx/middlewares/runtime.rb +18 -3
- data/lib/cmdx/middlewares/timeout.rb +11 -10
- data/lib/cmdx/parallelizer.rb +100 -0
- data/lib/cmdx/pipeline.rb +42 -23
- data/lib/cmdx/railtie.rb +1 -1
- data/lib/cmdx/result.rb +91 -19
- data/lib/cmdx/retry.rb +166 -0
- data/lib/cmdx/settings.rb +226 -0
- data/lib/cmdx/task.rb +62 -65
- data/lib/cmdx/utils/format.rb +17 -1
- data/lib/cmdx/utils/normalize.rb +52 -0
- data/lib/cmdx/utils/wrap.rb +38 -0
- data/lib/cmdx/validator_registry.rb +44 -19
- data/lib/cmdx/validators/absence.rb +1 -1
- data/lib/cmdx/validators/exclusion.rb +2 -2
- data/lib/cmdx/validators/format.rb +1 -1
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +1 -1
- data/lib/cmdx/validators/numeric.rb +1 -1
- data/lib/cmdx/validators/presence.rb +1 -1
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +17 -0
- data/lib/cmdx.rb +12 -0
- data/lib/generators/cmdx/templates/install.rb +20 -5
- data/lib/locales/af.yml +2 -0
- data/lib/locales/ar.yml +2 -0
- data/lib/locales/az.yml +2 -0
- data/lib/locales/be.yml +2 -0
- data/lib/locales/bg.yml +2 -0
- data/lib/locales/bn.yml +2 -0
- data/lib/locales/bs.yml +2 -0
- data/lib/locales/ca.yml +2 -0
- data/lib/locales/cnr.yml +2 -0
- data/lib/locales/cs.yml +2 -0
- data/lib/locales/cy.yml +2 -0
- data/lib/locales/da.yml +2 -0
- data/lib/locales/de.yml +2 -0
- data/lib/locales/dz.yml +2 -0
- data/lib/locales/el.yml +2 -0
- data/lib/locales/en.yml +2 -0
- data/lib/locales/eo.yml +2 -0
- data/lib/locales/es.yml +2 -0
- data/lib/locales/et.yml +2 -0
- data/lib/locales/eu.yml +2 -0
- data/lib/locales/fa.yml +2 -0
- data/lib/locales/fi.yml +2 -0
- data/lib/locales/fr.yml +2 -0
- data/lib/locales/fy.yml +2 -0
- data/lib/locales/gd.yml +2 -0
- data/lib/locales/gl.yml +2 -0
- data/lib/locales/he.yml +2 -0
- data/lib/locales/hi.yml +2 -0
- data/lib/locales/hr.yml +2 -0
- data/lib/locales/hu.yml +2 -0
- data/lib/locales/hy.yml +2 -0
- data/lib/locales/id.yml +2 -0
- data/lib/locales/is.yml +2 -0
- data/lib/locales/it.yml +2 -0
- data/lib/locales/ja.yml +2 -0
- data/lib/locales/ka.yml +2 -0
- data/lib/locales/kk.yml +2 -0
- data/lib/locales/km.yml +2 -0
- data/lib/locales/kn.yml +2 -0
- data/lib/locales/ko.yml +2 -0
- data/lib/locales/lb.yml +2 -0
- data/lib/locales/lo.yml +2 -0
- data/lib/locales/lt.yml +2 -0
- data/lib/locales/lv.yml +2 -0
- data/lib/locales/mg.yml +2 -0
- data/lib/locales/mk.yml +2 -0
- data/lib/locales/ml.yml +2 -0
- data/lib/locales/mn.yml +2 -0
- data/lib/locales/mr-IN.yml +2 -0
- data/lib/locales/ms.yml +2 -0
- data/lib/locales/nb.yml +2 -0
- data/lib/locales/ne.yml +2 -0
- data/lib/locales/nl.yml +2 -0
- data/lib/locales/nn.yml +2 -0
- data/lib/locales/oc.yml +2 -0
- data/lib/locales/or.yml +2 -0
- data/lib/locales/pa.yml +2 -0
- data/lib/locales/pl.yml +2 -0
- data/lib/locales/pt.yml +2 -0
- data/lib/locales/rm.yml +2 -0
- data/lib/locales/ro.yml +2 -0
- data/lib/locales/ru.yml +2 -0
- data/lib/locales/sc.yml +2 -0
- data/lib/locales/sk.yml +2 -0
- data/lib/locales/sl.yml +2 -0
- data/lib/locales/sq.yml +2 -0
- data/lib/locales/sr.yml +2 -0
- data/lib/locales/st.yml +2 -0
- data/lib/locales/sv.yml +2 -0
- data/lib/locales/sw.yml +2 -0
- data/lib/locales/ta.yml +2 -0
- data/lib/locales/te.yml +2 -0
- data/lib/locales/th.yml +2 -0
- data/lib/locales/tl.yml +2 -0
- data/lib/locales/tr.yml +2 -0
- data/lib/locales/tt.yml +2 -0
- data/lib/locales/ug.yml +2 -0
- data/lib/locales/uk.yml +2 -0
- data/lib/locales/ur.yml +2 -0
- data/lib/locales/uz.yml +2 -0
- data/lib/locales/vi.yml +2 -0
- data/lib/locales/wo.yml +2 -0
- data/lib/locales/zh-CN.yml +2 -0
- data/lib/locales/zh-HK.yml +2 -0
- data/lib/locales/zh-TW.yml +2 -0
- data/lib/locales/zh-YUE.yml +2 -0
- data/mkdocs.yml +5 -1
- metadata +6 -15
|
@@ -5,19 +5,11 @@ module CMDx
|
|
|
5
5
|
#
|
|
6
6
|
# Provides a centralized way to register, deregister, and execute type coercions
|
|
7
7
|
# for various data types including arrays, numbers, dates, and other primitives.
|
|
8
|
+
#
|
|
9
|
+
# Supports copy-on-write semantics: a duped registry shares the parent's
|
|
10
|
+
# data until a write operation triggers materialization.
|
|
8
11
|
class CoercionRegistry
|
|
9
12
|
|
|
10
|
-
# Returns the internal registry mapping coercion types to handler classes.
|
|
11
|
-
#
|
|
12
|
-
# @return [Hash{Symbol => Class}] Hash of coercion type names to coercion classes
|
|
13
|
-
#
|
|
14
|
-
# @example
|
|
15
|
-
# registry.registry # => { integer: Coercions::Integer, boolean: Coercions::Boolean }
|
|
16
|
-
#
|
|
17
|
-
# @rbs @registry: Hash[Symbol, Class]
|
|
18
|
-
attr_reader :registry
|
|
19
|
-
alias to_h registry
|
|
20
|
-
|
|
21
13
|
# Initialize a new coercion registry.
|
|
22
14
|
#
|
|
23
15
|
# @param registry [Hash{Symbol => Class}, nil] optional initial registry hash
|
|
@@ -44,17 +36,30 @@ module CMDx
|
|
|
44
36
|
}
|
|
45
37
|
end
|
|
46
38
|
|
|
47
|
-
#
|
|
39
|
+
# Sets up copy-on-write state when duplicated via dup.
|
|
48
40
|
#
|
|
49
|
-
# @
|
|
41
|
+
# @param source [CoercionRegistry] The registry being duplicated
|
|
42
|
+
#
|
|
43
|
+
# @rbs (CoercionRegistry source) -> void
|
|
44
|
+
def initialize_dup(source)
|
|
45
|
+
@parent = source
|
|
46
|
+
@registry = nil
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the internal registry mapping coercion types to handler classes.
|
|
51
|
+
# Delegates to the parent registry when not yet materialized.
|
|
52
|
+
#
|
|
53
|
+
# @return [Hash{Symbol => Class}] Hash of coercion type names to coercion classes
|
|
50
54
|
#
|
|
51
55
|
# @example
|
|
52
|
-
#
|
|
56
|
+
# registry.registry # => { integer: Coercions::Integer, boolean: Coercions::Boolean }
|
|
53
57
|
#
|
|
54
|
-
# @rbs () ->
|
|
55
|
-
def
|
|
56
|
-
|
|
58
|
+
# @rbs () -> Hash[Symbol, Class]
|
|
59
|
+
def registry
|
|
60
|
+
@registry || @parent.registry
|
|
57
61
|
end
|
|
62
|
+
alias to_h registry
|
|
58
63
|
|
|
59
64
|
# Register a new coercion handler for a type.
|
|
60
65
|
#
|
|
@@ -69,7 +74,9 @@ module CMDx
|
|
|
69
74
|
#
|
|
70
75
|
# @rbs ((Symbol | String) name, Class coercion) -> self
|
|
71
76
|
def register(name, coercion)
|
|
72
|
-
|
|
77
|
+
materialize!
|
|
78
|
+
|
|
79
|
+
@registry[name.to_sym] = coercion
|
|
73
80
|
self
|
|
74
81
|
end
|
|
75
82
|
|
|
@@ -85,7 +92,9 @@ module CMDx
|
|
|
85
92
|
#
|
|
86
93
|
# @rbs ((Symbol | String) name) -> self
|
|
87
94
|
def deregister(name)
|
|
88
|
-
|
|
95
|
+
materialize!
|
|
96
|
+
|
|
97
|
+
@registry.delete(name.to_sym)
|
|
89
98
|
self
|
|
90
99
|
end
|
|
91
100
|
|
|
@@ -106,11 +115,24 @@ module CMDx
|
|
|
106
115
|
# result = registry.coerce(:boolean, task, "true", strict: true)
|
|
107
116
|
#
|
|
108
117
|
# @rbs (Symbol type, untyped task, untyped value, ?Hash[Symbol, untyped] options) -> untyped
|
|
109
|
-
def coerce(type, task, value, options =
|
|
118
|
+
def coerce(type, task, value, options = EMPTY_HASH)
|
|
110
119
|
raise TypeError, "unknown coercion type #{type.inspect}" unless registry.key?(type)
|
|
111
120
|
|
|
112
121
|
Utils::Call.invoke(task, registry[type], value, options)
|
|
113
122
|
end
|
|
114
123
|
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
# Copies the parent's registry data into this instance,
|
|
127
|
+
# severing the copy-on-write link.
|
|
128
|
+
#
|
|
129
|
+
# @rbs () -> void
|
|
130
|
+
def materialize!
|
|
131
|
+
return if @registry
|
|
132
|
+
|
|
133
|
+
@registry = @parent.registry.dup
|
|
134
|
+
@parent = nil
|
|
135
|
+
end
|
|
136
|
+
|
|
115
137
|
end
|
|
116
138
|
end
|
data/lib/cmdx/coercions/array.rb
CHANGED
|
@@ -30,14 +30,14 @@ module CMDx
|
|
|
30
30
|
# Array.call("[not json") # => raises CoercionError
|
|
31
31
|
#
|
|
32
32
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Array[untyped]
|
|
33
|
-
def call(value, options =
|
|
33
|
+
def call(value, options = EMPTY_HASH)
|
|
34
34
|
if value.is_a?(::String) && (
|
|
35
35
|
value.start_with?("[") ||
|
|
36
36
|
value.strip == "null"
|
|
37
37
|
)
|
|
38
38
|
JSON.parse(value) || []
|
|
39
39
|
else
|
|
40
|
-
|
|
40
|
+
Utils::Wrap.array(value)
|
|
41
41
|
end
|
|
42
42
|
rescue JSON::ParserError
|
|
43
43
|
type = Locale.t("cmdx.types.array")
|
|
@@ -31,7 +31,7 @@ module CMDx
|
|
|
31
31
|
# BigDecimal.call(3.14159) # => #<BigDecimal:7f8b8c0d8e0f '0.314159E1',9(18)>
|
|
32
32
|
#
|
|
33
33
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> BigDecimal
|
|
34
|
-
def call(value, options =
|
|
34
|
+
def call(value, options = EMPTY_HASH)
|
|
35
35
|
BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
|
|
36
36
|
rescue ArgumentError, TypeError
|
|
37
37
|
type = Locale.t("cmdx.types.big_decimal")
|
|
@@ -43,9 +43,9 @@ module CMDx
|
|
|
43
43
|
# Boolean.call("abc") # => raises CoercionError
|
|
44
44
|
#
|
|
45
45
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> bool
|
|
46
|
-
def call(value, options =
|
|
46
|
+
def call(value, options = EMPTY_HASH)
|
|
47
47
|
case value.to_s
|
|
48
|
-
when FALSEY,
|
|
48
|
+
when FALSEY, EMPTY_STRING then false
|
|
49
49
|
when TRUTHY then true
|
|
50
50
|
else
|
|
51
51
|
type = Locale.t("cmdx.types.boolean")
|
|
@@ -29,7 +29,7 @@ module CMDx
|
|
|
29
29
|
# Complex.call(Complex(1, 2)) # => (1+2i)
|
|
30
30
|
#
|
|
31
31
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Complex
|
|
32
|
-
def call(value, options =
|
|
32
|
+
def call(value, options = EMPTY_HASH)
|
|
33
33
|
Complex(value)
|
|
34
34
|
rescue ArgumentError, TypeError
|
|
35
35
|
type = Locale.t("cmdx.types.complex")
|
data/lib/cmdx/coercions/date.rb
CHANGED
|
@@ -32,7 +32,7 @@ module CMDx
|
|
|
32
32
|
# Date.call(DateTime.new(2023, 12, 25)) # => #<Date: 2023-12-25>
|
|
33
33
|
#
|
|
34
34
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Date
|
|
35
|
-
def call(value, options =
|
|
35
|
+
def call(value, options = EMPTY_HASH)
|
|
36
36
|
return value.to_date if value.respond_to?(:to_date)
|
|
37
37
|
return ::Date.strptime(value, options[:strptime]) if options[:strptime]
|
|
38
38
|
|
|
@@ -32,7 +32,7 @@ module CMDx
|
|
|
32
32
|
# DateTime.call(Time.new(2023, 12, 25)) # => #<DateTime: 2023-12-25T00:00:00+00:00>
|
|
33
33
|
#
|
|
34
34
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> DateTime
|
|
35
|
-
def call(value, options =
|
|
35
|
+
def call(value, options = EMPTY_HASH)
|
|
36
36
|
return value.to_datetime if value.respond_to?(:to_datetime)
|
|
37
37
|
return ::DateTime.strptime(value, options[:strptime]) if options[:strptime]
|
|
38
38
|
|
data/lib/cmdx/coercions/float.rb
CHANGED
|
@@ -32,7 +32,7 @@ module CMDx
|
|
|
32
32
|
# Float.call(Complex(5.0, 0)) # => 5.0
|
|
33
33
|
#
|
|
34
34
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Float
|
|
35
|
-
def call(value, options =
|
|
35
|
+
def call(value, options = EMPTY_HASH)
|
|
36
36
|
Float(value)
|
|
37
37
|
rescue ArgumentError, RangeError, TypeError
|
|
38
38
|
type = Locale.t("cmdx.types.float")
|
data/lib/cmdx/coercions/hash.rb
CHANGED
|
@@ -33,7 +33,7 @@ module CMDx
|
|
|
33
33
|
# Hash.call('{"key": "value"}') # => {"key" => "value"}
|
|
34
34
|
#
|
|
35
35
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Hash[untyped, untyped]
|
|
36
|
-
def call(value, options =
|
|
36
|
+
def call(value, options = EMPTY_HASH)
|
|
37
37
|
if value.nil?
|
|
38
38
|
{}
|
|
39
39
|
elsif value.is_a?(::Hash)
|
|
@@ -35,7 +35,7 @@ module CMDx
|
|
|
35
35
|
# Integer.call("abc") # => raises CoercionError
|
|
36
36
|
#
|
|
37
37
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Integer
|
|
38
|
-
def call(value, options =
|
|
38
|
+
def call(value, options = EMPTY_HASH)
|
|
39
39
|
Integer(value)
|
|
40
40
|
rescue ArgumentError, FloatDomainError, RangeError, TypeError
|
|
41
41
|
type = Locale.t("cmdx.types.integer")
|
|
@@ -35,7 +35,7 @@ module CMDx
|
|
|
35
35
|
# Rational.call(0) # => (0/1)
|
|
36
36
|
#
|
|
37
37
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Rational
|
|
38
|
-
def call(value, options =
|
|
38
|
+
def call(value, options = EMPTY_HASH)
|
|
39
39
|
Rational(value)
|
|
40
40
|
rescue ArgumentError, FloatDomainError, RangeError, TypeError, ZeroDivisionError
|
|
41
41
|
type = Locale.t("cmdx.types.rational")
|
|
@@ -28,7 +28,7 @@ module CMDx
|
|
|
28
28
|
# Symbol.call(:existing) # => :existing
|
|
29
29
|
#
|
|
30
30
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Symbol
|
|
31
|
-
def call(value, options =
|
|
31
|
+
def call(value, options = EMPTY_HASH)
|
|
32
32
|
value.to_sym
|
|
33
33
|
rescue NoMethodError
|
|
34
34
|
type = Locale.t("cmdx.types.symbol")
|
data/lib/cmdx/coercions/time.rb
CHANGED
|
@@ -34,7 +34,7 @@ module CMDx
|
|
|
34
34
|
# Time.call("12-25-2023", strptime: "%m-%d-%Y") # => Time object
|
|
35
35
|
#
|
|
36
36
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Time
|
|
37
|
-
def call(value, options =
|
|
37
|
+
def call(value, options = EMPTY_HASH)
|
|
38
38
|
return value.to_time if value.respond_to?(:to_time)
|
|
39
39
|
return ::Time.strptime(value, options[:strptime]) if options[:strptime]
|
|
40
40
|
|
data/lib/cmdx/configuration.rb
CHANGED
|
@@ -123,6 +123,38 @@ module CMDx
|
|
|
123
123
|
# @rbs @rollback_on: Array[String]
|
|
124
124
|
attr_accessor :rollback_on
|
|
125
125
|
|
|
126
|
+
# Returns whether to include context data in hash representation output.
|
|
127
|
+
#
|
|
128
|
+
# @return [Boolean] true if context should be included (default: false)
|
|
129
|
+
#
|
|
130
|
+
# @example
|
|
131
|
+
# config.dump_context = true
|
|
132
|
+
#
|
|
133
|
+
# @rbs @dump_context: bool
|
|
134
|
+
attr_accessor :dump_context
|
|
135
|
+
|
|
136
|
+
# Returns whether to freeze task results after execution.
|
|
137
|
+
# Set to false in test environments to allow stubbing on result objects.
|
|
138
|
+
#
|
|
139
|
+
# @return [Boolean] true if results should be frozen (default: true)
|
|
140
|
+
#
|
|
141
|
+
# @example
|
|
142
|
+
# config.freeze_results = false
|
|
143
|
+
#
|
|
144
|
+
# @rbs @freeze_results: bool
|
|
145
|
+
attr_accessor :freeze_results
|
|
146
|
+
|
|
147
|
+
# Returns the default locale used for built-in translation lookups.
|
|
148
|
+
# Must match the basename of a YAML file in lib/locales/ (e.g. "en", "es", "ja").
|
|
149
|
+
#
|
|
150
|
+
# @return [String] The locale identifier (default: "en")
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# config.default_locale = "es"
|
|
154
|
+
#
|
|
155
|
+
# @rbs @locale: String
|
|
156
|
+
attr_accessor :default_locale
|
|
157
|
+
|
|
126
158
|
# Initializes a new Configuration instance with default values.
|
|
127
159
|
#
|
|
128
160
|
# Creates new registry instances for middlewares, callbacks, coercions, and
|
|
@@ -145,6 +177,9 @@ module CMDx
|
|
|
145
177
|
@task_breakpoints = DEFAULT_BREAKPOINTS
|
|
146
178
|
@workflow_breakpoints = DEFAULT_BREAKPOINTS
|
|
147
179
|
@rollback_on = DEFAULT_ROLLPOINTS
|
|
180
|
+
@dump_context = false
|
|
181
|
+
@freeze_results = true
|
|
182
|
+
@default_locale = "en"
|
|
148
183
|
|
|
149
184
|
@backtrace = false
|
|
150
185
|
@backtrace_cleaner = nil
|
|
@@ -177,9 +212,12 @@ module CMDx
|
|
|
177
212
|
task_breakpoints: @task_breakpoints,
|
|
178
213
|
workflow_breakpoints: @workflow_breakpoints,
|
|
179
214
|
rollback_on: @rollback_on,
|
|
215
|
+
freeze_results: @freeze_results,
|
|
216
|
+
default_locale: @default_locale,
|
|
180
217
|
backtrace: @backtrace,
|
|
181
218
|
backtrace_cleaner: @backtrace_cleaner,
|
|
182
219
|
exception_handler: @exception_handler,
|
|
220
|
+
dump_context: @dump_context,
|
|
183
221
|
logger: @logger
|
|
184
222
|
}
|
|
185
223
|
end
|
data/lib/cmdx/context.rb
CHANGED
|
@@ -22,7 +22,7 @@ module CMDx
|
|
|
22
22
|
attr_reader :table
|
|
23
23
|
alias to_h table
|
|
24
24
|
|
|
25
|
-
def_delegators :table, :each, :map
|
|
25
|
+
def_delegators :table, :keys, :values, :each, :each_key, :each_value, :map
|
|
26
26
|
|
|
27
27
|
# Creates a new Context instance from the given arguments.
|
|
28
28
|
#
|
|
@@ -160,8 +160,8 @@ module CMDx
|
|
|
160
160
|
# context.to_h # => {name: "John", age: 30, city: "NYC"}
|
|
161
161
|
#
|
|
162
162
|
# @rbs (?untyped args) -> self
|
|
163
|
-
def merge!(args =
|
|
164
|
-
args.to_h.
|
|
163
|
+
def merge!(args = EMPTY_HASH)
|
|
164
|
+
table.merge!(args.to_h.transform_keys(&:to_sym))
|
|
165
165
|
self
|
|
166
166
|
end
|
|
167
167
|
alias merge merge!
|
|
@@ -214,7 +214,7 @@ module CMDx
|
|
|
214
214
|
#
|
|
215
215
|
# @rbs (untyped other) -> bool
|
|
216
216
|
def eql?(other)
|
|
217
|
-
other.is_a?(self.class) && (
|
|
217
|
+
other.is_a?(self.class) && (table == other.to_h)
|
|
218
218
|
end
|
|
219
219
|
alias == eql?
|
|
220
220
|
|
|
@@ -280,13 +280,15 @@ module CMDx
|
|
|
280
280
|
#
|
|
281
281
|
# @rbs (Symbol method_name, *untyped args, **untyped _kwargs) ?{ () -> untyped } -> untyped
|
|
282
282
|
def method_missing(method_name, *args, **_kwargs, &)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
if method_name.end_with?("=")
|
|
284
|
+
store(method_name.name.chop, args.first)
|
|
285
|
+
else
|
|
286
|
+
table[method_name]
|
|
286
287
|
end
|
|
287
288
|
end
|
|
288
289
|
|
|
289
290
|
# Checks if the object responds to a given method.
|
|
291
|
+
# Supports both getter access for existing keys and setter methods.
|
|
290
292
|
#
|
|
291
293
|
# @param method_name [Symbol] the method name to check
|
|
292
294
|
# @param include_private [Boolean] whether to include private methods
|
|
@@ -296,11 +298,12 @@ module CMDx
|
|
|
296
298
|
# @example
|
|
297
299
|
# context = Context.new(name: "John")
|
|
298
300
|
# context.respond_to?(:name) # => true
|
|
301
|
+
# context.respond_to?(:name=) # => true
|
|
299
302
|
# context.respond_to?(:age) # => false
|
|
300
303
|
#
|
|
301
304
|
# @rbs (Symbol method_name, ?bool include_private) -> bool
|
|
302
305
|
def respond_to_missing?(method_name, include_private = false)
|
|
303
|
-
key?(method_name) || super
|
|
306
|
+
key?(method_name) || method_name.end_with?("=") || super
|
|
304
307
|
end
|
|
305
308
|
|
|
306
309
|
end
|
data/lib/cmdx/deprecator.rb
CHANGED
|
@@ -10,11 +10,23 @@ module CMDx
|
|
|
10
10
|
|
|
11
11
|
extend self
|
|
12
12
|
|
|
13
|
+
# @rbs RAISE_REGEXP: Regexp
|
|
14
|
+
RAISE_REGEXP = /\Araise\z/
|
|
15
|
+
private_constant :RAISE_REGEXP
|
|
16
|
+
|
|
17
|
+
# @rbs LOG_REGEXP: Regexp
|
|
18
|
+
LOG_REGEXP = /\Alog\z/
|
|
19
|
+
private_constant :LOG_REGEXP
|
|
20
|
+
|
|
21
|
+
# @rbs WARN_REGEXP: Regexp
|
|
22
|
+
WARN_REGEXP = /\Awarn\z/
|
|
23
|
+
private_constant :WARN_REGEXP
|
|
24
|
+
|
|
13
25
|
# @rbs EVAL: Proc
|
|
14
26
|
EVAL = proc do |target, callable|
|
|
15
27
|
case callable
|
|
16
|
-
when /raise|log|warn/ then callable
|
|
17
28
|
when NilClass, FalseClass, TrueClass then !!callable
|
|
29
|
+
when RAISE_REGEXP, LOG_REGEXP, WARN_REGEXP then callable
|
|
18
30
|
when Symbol then target.send(callable)
|
|
19
31
|
when Proc then target.instance_eval(&callable)
|
|
20
32
|
else
|
|
@@ -28,14 +40,14 @@ module CMDx
|
|
|
28
40
|
# Restricts task usage based on deprecation settings.
|
|
29
41
|
#
|
|
30
42
|
# @param task [Object] The task object to check for deprecation
|
|
31
|
-
# @option task.class.settings
|
|
43
|
+
# @option task.class.settings.deprecate [Symbol, Proc, String, Boolean]
|
|
32
44
|
# The deprecation configuration for the task
|
|
33
|
-
# @option task.class.settings
|
|
34
|
-
# @option task.class.settings
|
|
35
|
-
# @option task.class.settings
|
|
36
|
-
# @option task.class.settings
|
|
37
|
-
# @option task.class.settings
|
|
38
|
-
# @option task.class.settings
|
|
45
|
+
# @option task.class.settings.deprecate :raise Raises DeprecationError
|
|
46
|
+
# @option task.class.settings.deprecate :log Logs deprecation warning
|
|
47
|
+
# @option task.class.settings.deprecate :warn Outputs warning to stderr
|
|
48
|
+
# @option task.class.settings.deprecate true Raises DeprecationError
|
|
49
|
+
# @option task.class.settings.deprecate false No action taken
|
|
50
|
+
# @option task.class.settings.deprecate nil No action taken
|
|
39
51
|
#
|
|
40
52
|
# @raise [DeprecationError] When deprecation type is :raise or true
|
|
41
53
|
# @raise [RuntimeError] When deprecation type is unknown
|
|
@@ -49,13 +61,14 @@ module CMDx
|
|
|
49
61
|
#
|
|
50
62
|
# @rbs (Task task) -> void
|
|
51
63
|
def restrict(task)
|
|
52
|
-
|
|
64
|
+
setting = task.class.settings.deprecate
|
|
65
|
+
return unless setting
|
|
53
66
|
|
|
54
|
-
case type
|
|
55
|
-
when NilClass, FalseClass # Do nothing
|
|
56
|
-
when TrueClass,
|
|
57
|
-
when
|
|
58
|
-
when
|
|
67
|
+
case type = EVAL.call(task, setting)
|
|
68
|
+
when NilClass, FalseClass then nil # Do nothing
|
|
69
|
+
when TrueClass, RAISE_REGEXP then raise DeprecationError, "#{task.class.name} usage prohibited"
|
|
70
|
+
when LOG_REGEXP then task.logger.warn { "DEPRECATED: migrate to a replacement or discontinue use" }
|
|
71
|
+
when WARN_REGEXP then warn("[#{task.class.name}] DEPRECATED: migrate to a replacement or discontinue use", category: :deprecated)
|
|
59
72
|
else raise "unknown deprecation type #{type.inspect}"
|
|
60
73
|
end
|
|
61
74
|
end
|
data/lib/cmdx/errors.rb
CHANGED
|
@@ -18,7 +18,7 @@ module CMDx
|
|
|
18
18
|
# @rbs @messages: Hash[Symbol, Set[String]]
|
|
19
19
|
attr_reader :messages
|
|
20
20
|
|
|
21
|
-
def_delegators :messages, :empty
|
|
21
|
+
def_delegators :messages, :any?, :clear, :empty?, :size
|
|
22
22
|
|
|
23
23
|
# Initialize a new error collection.
|
|
24
24
|
#
|
|
@@ -57,9 +57,8 @@ module CMDx
|
|
|
57
57
|
#
|
|
58
58
|
# @rbs (Symbol attribute) -> bool
|
|
59
59
|
def for?(attribute)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
!messages[attribute].empty?
|
|
60
|
+
set = messages[attribute]
|
|
61
|
+
!set.nil? && !set.empty?
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
# Convert errors to a hash format with arrays of full messages.
|
data/lib/cmdx/exception.rb
CHANGED
|
@@ -29,6 +29,13 @@ module CMDx
|
|
|
29
29
|
# of required functionality.
|
|
30
30
|
UndefinedMethodError = Class.new(Error)
|
|
31
31
|
|
|
32
|
+
# Error raised when task execution exceeds the configured timeout limit.
|
|
33
|
+
#
|
|
34
|
+
# This error occurs when a task takes longer to execute than the specified
|
|
35
|
+
# time limit. Timeout errors are raised by Ruby's Timeout module and are
|
|
36
|
+
# caught by the middleware to properly fail the task with timeout information.
|
|
37
|
+
TimeoutError = Class.new(Interrupt)
|
|
38
|
+
|
|
32
39
|
# Raised when attribute validation fails during task execution.
|
|
33
40
|
#
|
|
34
41
|
# This error occurs when a attribute value doesn't meet the validation criteria
|