cmdx 1.0.1 → 1.1.1
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/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,165 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
13
|
-
# chain = result.chain
|
14
|
-
#
|
15
|
-
# ChainSerializer.call(chain)
|
16
|
-
# # => {
|
17
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
18
|
-
# # state: "complete",
|
19
|
-
# # status: "success",
|
20
|
-
# # outcome: "success",
|
21
|
-
# # runtime: 0.5,
|
22
|
-
# # results: [
|
23
|
-
# # {
|
24
|
-
# # class: "ProcessOrderTask",
|
25
|
-
# # type: "Task",
|
26
|
-
# # index: 0,
|
27
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
28
|
-
# # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
29
|
-
# # tags: [],
|
30
|
-
# # state: "complete",
|
31
|
-
# # status: "success",
|
32
|
-
# # outcome: "success",
|
33
|
-
# # metadata: {},
|
34
|
-
# # runtime: 0.5
|
35
|
-
# # }
|
36
|
-
# # ]
|
37
|
-
# # }
|
38
|
-
#
|
39
|
-
# @example Chain with multiple tasks
|
40
|
-
# class ComplexTask < CMDx::Task
|
41
|
-
# def call
|
42
|
-
# SubTask1.call(context)
|
43
|
-
# SubTask2.call(context)
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# result = ComplexTask.call
|
48
|
-
# chain = result.chain
|
49
|
-
#
|
50
|
-
# ChainSerializer.call(chain)
|
51
|
-
# # => {
|
52
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
53
|
-
# # state: "complete",
|
54
|
-
# # status: "success",
|
55
|
-
# # outcome: "success",
|
56
|
-
# # runtime: 1.2,
|
57
|
-
# # results: [
|
58
|
-
# # { class: "ComplexTask", index: 0, state: "complete", status: "success", ... },
|
59
|
-
# # { class: "SubTask1", index: 1, state: "complete", status: "success", ... },
|
60
|
-
# # { class: "SubTask2", index: 2, state: "complete", status: "success", ... }
|
61
|
-
# # ]
|
62
|
-
# # }
|
63
|
-
#
|
64
|
-
# @example Failed chain serialization
|
65
|
-
# failed_result = FailingTask.call
|
66
|
-
# failed_chain = failed_result.chain
|
67
|
-
#
|
68
|
-
# ChainSerializer.call(failed_chain)
|
69
|
-
# # => {
|
70
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
71
|
-
# # state: "interrupted",
|
72
|
-
# # status: "failed",
|
73
|
-
# # outcome: "failed",
|
74
|
-
# # runtime: 0.1,
|
75
|
-
# # results: [
|
76
|
-
# # {
|
77
|
-
# # class: "FailingTask",
|
78
|
-
# # state: "interrupted",
|
79
|
-
# # status: "failed",
|
80
|
-
# # outcome: "failed",
|
81
|
-
# # metadata: { reason: "Something went wrong" },
|
82
|
-
# # runtime: 0.1,
|
83
|
-
# # ...
|
84
|
-
# # }
|
85
|
-
# # ]
|
86
|
-
# # }
|
87
|
-
#
|
88
|
-
# @see CMDx::Chain Chain execution context and result tracking
|
89
|
-
# @see CMDx::ResultSerializer Individual result serialization
|
90
|
-
# @see CMDx::ChainInspector Human-readable chain formatting
|
4
|
+
# Serialization module for converting chain objects to hash representation.
|
5
|
+
#
|
6
|
+
# ChainSerializer provides functionality to serialize chain objects into a
|
7
|
+
# standardized hash format that includes essential metadata about the chain
|
8
|
+
# execution including unique identification, execution state, status, outcome,
|
9
|
+
# runtime, and all contained task results. The serialized format is commonly
|
10
|
+
# used for debugging, logging, introspection, and data exchange throughout
|
11
|
+
# the task execution pipeline.
|
91
12
|
module ChainSerializer
|
92
13
|
|
93
14
|
module_function
|
94
15
|
|
95
|
-
#
|
16
|
+
# Serializes a chain object into a hash representation.
|
96
17
|
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
18
|
+
# Converts a chain instance into a standardized hash format containing
|
19
|
+
# key metadata about the chain's execution context and all contained results.
|
20
|
+
# The serialization includes information delegated from the first result in
|
21
|
+
# the chain (state, status, outcome, runtime) along with the chain's unique
|
22
|
+
# identifier and complete collection of task results converted to hashes.
|
101
23
|
#
|
102
|
-
# @param chain [CMDx::Chain]
|
103
|
-
# @return [Hash] Structured hash representation of the chain and all results
|
24
|
+
# @param chain [CMDx::Chain] the chain object to serialize
|
104
25
|
#
|
105
|
-
# @
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
26
|
+
# @return [Hash] a hash containing the chain's metadata and execution information
|
27
|
+
# @option return [String] :id the unique identifier of the chain
|
28
|
+
# @option return [String] :state the execution state delegated from first result
|
29
|
+
# @option return [String] :status the execution status delegated from first result
|
30
|
+
# @option return [String] :outcome the execution outcome delegated from first result
|
31
|
+
# @option return [Float] :runtime the execution runtime in seconds delegated from first result
|
32
|
+
# @option return [Array<Hash>] :results array of serialized result hashes from all tasks in the chain
|
33
|
+
#
|
34
|
+
# @raise [NoMethodError] if the chain doesn't respond to required methods (id, state, status, outcome, runtime, results)
|
35
|
+
#
|
36
|
+
# @example Serialize a workflow chain with multiple tasks
|
37
|
+
# workflow = DataProcessingWorkflow.call(input: "data")
|
38
|
+
# ChainSerializer.call(workflow.chain)
|
39
|
+
# #=> {
|
40
|
+
# # id: "def456",
|
110
41
|
# # state: "complete",
|
111
42
|
# # status: "success",
|
112
43
|
# # outcome: "success",
|
113
|
-
# # runtime: 0.
|
44
|
+
# # runtime: 0.123,
|
114
45
|
# # results: [
|
115
|
-
# # {
|
116
|
-
# #
|
117
|
-
# #
|
118
|
-
# # index: 0,
|
119
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
120
|
-
# # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
121
|
-
# # tags: [],
|
122
|
-
# # state: "complete",
|
123
|
-
# # status: "success",
|
124
|
-
# # outcome: "success",
|
125
|
-
# # metadata: {},
|
126
|
-
# # runtime: 0.1
|
127
|
-
# # }
|
46
|
+
# # { index: 0, class: "ValidateDataTask", status: "success", ... },
|
47
|
+
# # { index: 1, class: "ProcessDataTask", status: "success", ... },
|
48
|
+
# # { index: 2, class: "SaveDataTask", status: "success", ... }
|
128
49
|
# # ]
|
129
50
|
# # }
|
130
|
-
#
|
131
|
-
# @example Multi-task chain serialization
|
132
|
-
# class ParentTask < CMDx::Task
|
133
|
-
# def call
|
134
|
-
# ChildTask.call(context)
|
135
|
-
# end
|
136
|
-
# end
|
137
|
-
#
|
138
|
-
# chain = ParentTask.call.chain
|
139
|
-
# ChainSerializer.call(chain)
|
140
|
-
# # => {
|
141
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
142
|
-
# # state: "complete", # From first result (ParentTask)
|
143
|
-
# # status: "success", # From first result (ParentTask)
|
144
|
-
# # outcome: "success", # From first result (ParentTask)
|
145
|
-
# # runtime: 0.5, # From first result (ParentTask)
|
146
|
-
# # results: [
|
147
|
-
# # { class: "ParentTask", index: 0, ... },
|
148
|
-
# # { class: "ChildTask", index: 1, ... }
|
149
|
-
# # ]
|
150
|
-
# # }
|
151
|
-
#
|
152
|
-
# @example Empty chain serialization
|
153
|
-
# empty_chain = Chain.new
|
154
|
-
# ChainSerializer.call(empty_chain)
|
155
|
-
# # => {
|
156
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
157
|
-
# # state: nil,
|
158
|
-
# # status: nil,
|
159
|
-
# # outcome: nil,
|
160
|
-
# # runtime: nil,
|
161
|
-
# # results: []
|
162
|
-
# # }
|
163
51
|
def call(chain)
|
164
52
|
{
|
165
53
|
id: chain.id,
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Base class for implementing parameter coercion functionality in task processing.
|
5
|
+
#
|
6
|
+
# Coercions are used to convert parameter values from one type to another during
|
7
|
+
# task execution, enabling automatic type conversion and normalization. All coercion
|
8
|
+
# implementations must inherit from this class and implement the abstract call method.
|
9
|
+
class Coercion
|
10
|
+
|
11
|
+
# Executes a coercion by creating a new instance and calling it.
|
12
|
+
#
|
13
|
+
# @param value [Object] the value to be coerced
|
14
|
+
# @param options [Hash] additional options for the coercion
|
15
|
+
#
|
16
|
+
# @return [Object] the coerced value
|
17
|
+
#
|
18
|
+
# @raise [UndefinedCallError] when the coercion subclass doesn't implement call
|
19
|
+
# @raise [CoercionError] when coercion fails in subclass implementations
|
20
|
+
#
|
21
|
+
# @example Execute a coercion on a value
|
22
|
+
# StringCoercion.call(123) #=> "123"
|
23
|
+
#
|
24
|
+
# @example Execute with options
|
25
|
+
# CustomCoercion.call("value", strict: true) #=> processed_value
|
26
|
+
def self.call(value, options = {})
|
27
|
+
new.call(value, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Abstract method that must be implemented by coercion subclasses.
|
31
|
+
#
|
32
|
+
# This method contains the actual coercion logic to convert the input
|
33
|
+
# value to the desired type. Subclasses must override this method
|
34
|
+
# to provide their specific coercion implementation.
|
35
|
+
#
|
36
|
+
# @param value [Object] the value to be coerced (unused in base class)
|
37
|
+
# @param options [Hash] additional options for the coercion (unused in base class)
|
38
|
+
#
|
39
|
+
# @return [Object] the coerced value
|
40
|
+
#
|
41
|
+
# @raise [UndefinedCallError] always raised in the base class
|
42
|
+
# @raise [CoercionError] when coercion fails in subclass implementations
|
43
|
+
#
|
44
|
+
# @example Implement in a subclass
|
45
|
+
# class StringCoercion < CMDx::Coercion
|
46
|
+
# def call(value, _options = {})
|
47
|
+
# String(value)
|
48
|
+
# rescue ArgumentError, TypeError
|
49
|
+
# raise CoercionError, "could not coerce into a string"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
def call(value, options = {}) # rubocop:disable Lint/UnusedMethodArgument
|
53
|
+
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Registry for managing parameter type coercion functionality.
|
5
|
+
#
|
6
|
+
# CoercionRegistry provides a centralized system for storing, accessing, and
|
7
|
+
# executing type coercions during task parameter processing. It maintains an
|
8
|
+
# internal registry of coercion type keys mapped to their corresponding coercion
|
9
|
+
# classes or callables, supporting both built-in framework coercions and custom
|
10
|
+
# user-defined coercions for flexible type conversion during task execution.
|
11
|
+
class CoercionRegistry
|
12
|
+
|
13
|
+
# @return [Hash] hash containing coercion type keys and coercion class/callable values
|
14
|
+
attr_reader :registry
|
15
|
+
|
16
|
+
# Creates a new coercion registry with built-in coercion types.
|
17
|
+
#
|
18
|
+
# Initializes the registry with all standard framework coercions including
|
19
|
+
# primitive types (string, integer, float, boolean), date/time types,
|
20
|
+
# collection types (array, hash), numeric types (big_decimal, rational, complex),
|
21
|
+
# and the virtual coercion type for parameter definitions without type conversion.
|
22
|
+
#
|
23
|
+
# @return [CoercionRegistry] a new registry instance with built-in coercions
|
24
|
+
#
|
25
|
+
# @example Create a new coercion registry
|
26
|
+
# registry = CoercionRegistry.new
|
27
|
+
# registry.registry.keys
|
28
|
+
# #=> [:array, :big_decimal, :boolean, :complex, :date, :datetime, :float, :hash, :integer, :rational, :string, :time, :virtual]
|
29
|
+
def initialize
|
30
|
+
@registry = {
|
31
|
+
array: Coercions::Array,
|
32
|
+
big_decimal: Coercions::BigDecimal,
|
33
|
+
boolean: Coercions::Boolean,
|
34
|
+
complex: Coercions::Complex,
|
35
|
+
date: Coercions::Date,
|
36
|
+
datetime: Coercions::DateTime,
|
37
|
+
float: Coercions::Float,
|
38
|
+
hash: Coercions::Hash,
|
39
|
+
integer: Coercions::Integer,
|
40
|
+
rational: Coercions::Rational,
|
41
|
+
string: Coercions::String,
|
42
|
+
time: Coercions::Time,
|
43
|
+
virtual: Coercions::Virtual
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Registers a new coercion type in the registry.
|
48
|
+
#
|
49
|
+
# Adds or overwrites a coercion type mapping in the registry, allowing custom
|
50
|
+
# coercions to be used during task parameter processing. The coercion can be
|
51
|
+
# a class that responds to `call`, a callable object, or a symbol/string
|
52
|
+
# representing a method to invoke on the task instance.
|
53
|
+
#
|
54
|
+
# @param type [Symbol] the coercion type identifier to register
|
55
|
+
# @param coercion [Class, Proc, Symbol, String] the coercion implementation
|
56
|
+
#
|
57
|
+
# @return [CoercionRegistry] self for method chaining
|
58
|
+
#
|
59
|
+
# @example Register a custom coercion class
|
60
|
+
# registry.register(:temperature, TemperatureCoercion)
|
61
|
+
#
|
62
|
+
# @example Register a coercion proc
|
63
|
+
# registry.register(:upcase, proc { |value, options| value.to_s.upcase })
|
64
|
+
#
|
65
|
+
# @example Register a method symbol
|
66
|
+
# registry.register(:custom_parse, :parse_custom_format)
|
67
|
+
def register(type, coercion)
|
68
|
+
registry[type] = coercion
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes a coercion by type on the provided value.
|
73
|
+
#
|
74
|
+
# Looks up and executes the coercion implementation for the specified type,
|
75
|
+
# applying it to the provided value with optional configuration. Handles
|
76
|
+
# different coercion implementation types including callable objects,
|
77
|
+
# method symbols/strings, and coercion classes.
|
78
|
+
#
|
79
|
+
# @param task [CMDx::Task] the task instance for context when calling methods
|
80
|
+
# @param type [Symbol] the coercion type to execute
|
81
|
+
# @param value [Object] the value to be coerced
|
82
|
+
# @param options [Hash] additional options passed to the coercion
|
83
|
+
# @option options [Object] any any additional configuration for the coercion
|
84
|
+
#
|
85
|
+
# @return [Object] the coerced value
|
86
|
+
#
|
87
|
+
# @raise [UnknownCoercionError] when the specified coercion type is not registered
|
88
|
+
# @raise [CoercionError] when the coercion fails to convert the value
|
89
|
+
#
|
90
|
+
# @example Execute a built-in coercion
|
91
|
+
# registry.call(task, :integer, "123")
|
92
|
+
# #=> 123
|
93
|
+
#
|
94
|
+
# @example Execute with options
|
95
|
+
# registry.call(task, :date, "2024-01-15", format: "%Y-%m-%d")
|
96
|
+
# #=> #<Date: 2024-01-15>
|
97
|
+
#
|
98
|
+
# @example Handle unknown coercion type
|
99
|
+
# registry.call(task, :unknown_type, "value")
|
100
|
+
# #=> raises UnknownCoercionError
|
101
|
+
def call(task, type, value, options = {})
|
102
|
+
raise UnknownCoercionError, "unknown coercion #{type}" unless registry.key?(type)
|
103
|
+
|
104
|
+
case coercion = registry[type]
|
105
|
+
when Symbol, String, Proc
|
106
|
+
task.cmdx_try(coercion, value, options)
|
107
|
+
else
|
108
|
+
coercion.call(value, options)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/cmdx/coercions/array.rb
CHANGED
@@ -2,47 +2,29 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for converting values to arrays.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
# @example Basic array coercion
|
12
|
-
# class ProcessOrderTask < CMDx::Task
|
13
|
-
# optional :tags, type: :array, default: []
|
14
|
-
# optional :item_ids, type: :array
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Coercion behavior
|
18
|
-
# Coercions::Array.call([1, 2, 3]) # => [1, 2, 3]
|
19
|
-
# Coercions::Array.call("hello") # => ["hello"]
|
20
|
-
# Coercions::Array.call('["a","b"]') # => ["a", "b"] (JSON)
|
21
|
-
# Coercions::Array.call('[1,2,3]') # => [1, 2, 3] (JSON)
|
22
|
-
# Coercions::Array.call(nil) # => []
|
23
|
-
# Coercions::Array.call(42) # => [42]
|
24
|
-
#
|
25
|
-
# @see ParameterValue Parameter value coercion
|
26
|
-
# @see Parameter Parameter type definitions
|
27
|
-
module Array
|
7
|
+
# This coercion handles conversion of various types to arrays, with special
|
8
|
+
# handling for JSON-formatted strings that start with "[".
|
9
|
+
class Array < Coercion
|
28
10
|
|
29
|
-
|
30
|
-
|
31
|
-
#
|
11
|
+
# Converts the given value to an array.
|
12
|
+
#
|
13
|
+
# @param value [Object] the value to convert to an array
|
14
|
+
# @param _options [Hash] optional configuration (currently unused)
|
15
|
+
#
|
16
|
+
# @return [Array] the converted array value
|
32
17
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# general array conversion.
|
18
|
+
# @raise [JSON::ParserError] if value is a JSON string that cannot be parsed
|
19
|
+
# @raise [TypeError] if the value cannot be converted to an array
|
36
20
|
#
|
37
|
-
# @
|
38
|
-
#
|
39
|
-
# @return [Array] coerced array value
|
40
|
-
# @raise [JSON::ParserError] if JSON parsing fails
|
21
|
+
# @example Converting a JSON string
|
22
|
+
# Coercions::Array.call('["a", "b", "c"]') #=> ["a", "b", "c"]
|
41
23
|
#
|
42
|
-
# @example
|
43
|
-
# Coercions::Array.call("
|
44
|
-
# Coercions::Array.call(
|
45
|
-
# Coercions::Array.call(
|
24
|
+
# @example Converting other values
|
25
|
+
# Coercions::Array.call("hello") #=> ["hello"]
|
26
|
+
# Coercions::Array.call(123) #=> [123]
|
27
|
+
# Coercions::Array.call(nil) #=> []
|
46
28
|
def call(value, _options = {})
|
47
29
|
if value.is_a?(::String) && value.start_with?("[")
|
48
30
|
JSON.parse(value)
|
@@ -2,45 +2,33 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for converting values to BigDecimal.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# and
|
10
|
-
|
11
|
-
# @example Basic BigDecimal coercion
|
12
|
-
# class ProcessOrderTask < CMDx::Task
|
13
|
-
# required :total_amount, type: :big_decimal
|
14
|
-
# optional :tax_rate, type: :big_decimal, precision: 4
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Coercion behavior
|
18
|
-
# Coercions::BigDecimal.call("123.45") # => #<BigDecimal:...,'0.12345E3',18(27)>
|
19
|
-
# Coercions::BigDecimal.call(42) # => #<BigDecimal:...,'0.42E2',9(18)>
|
20
|
-
# Coercions::BigDecimal.call("0.333333", precision: 6) # Custom precision
|
21
|
-
#
|
22
|
-
# @see ParameterValue Parameter value coercion
|
23
|
-
# @see Parameter Parameter type definitions
|
24
|
-
module BigDecimal
|
7
|
+
# This coercion handles conversion of various types to BigDecimal with
|
8
|
+
# configurable precision. It provides precise decimal arithmetic capabilities
|
9
|
+
# for financial calculations and other use cases requiring exact decimal representation.
|
10
|
+
class BigDecimal < Coercion
|
25
11
|
|
26
|
-
# Default precision for BigDecimal calculations
|
27
|
-
# @return [Integer] default precision value
|
28
12
|
DEFAULT_PRECISION = 14
|
29
13
|
|
30
|
-
|
31
|
-
|
32
|
-
#
|
14
|
+
# Converts the given value to a BigDecimal.
|
15
|
+
#
|
16
|
+
# @param value [Object] the value to convert to a BigDecimal
|
17
|
+
# @param options [Hash] optional configuration
|
18
|
+
# @option options [Integer] :precision the precision for the BigDecimal (defaults to 14)
|
19
|
+
#
|
20
|
+
# @return [BigDecimal] the converted BigDecimal value
|
21
|
+
#
|
22
|
+
# @raise [CoercionError] if the value cannot be converted to a BigDecimal
|
23
|
+
#
|
24
|
+
# @example Converting a string
|
25
|
+
# Coercions::BigDecimal.call('123.45') #=> #<BigDecimal:...,'0.12345E3',18(27)>
|
33
26
|
#
|
34
|
-
# @
|
35
|
-
#
|
36
|
-
# @option options [Integer] :precision decimal precision (default: 14)
|
37
|
-
# @return [BigDecimal] coerced BigDecimal value
|
38
|
-
# @raise [CoercionError] if coercion fails
|
27
|
+
# @example Converting with custom precision
|
28
|
+
# Coercions::BigDecimal.call('123.456789', precision: 10) #=> #<BigDecimal:...,'0.123456789E3',18(27)>
|
39
29
|
#
|
40
|
-
# @example
|
41
|
-
# Coercions::BigDecimal.call(
|
42
|
-
# Coercions::BigDecimal.call("0.333", precision: 10) # => BigDecimal with custom precision
|
43
|
-
# Coercions::BigDecimal.call(42.5) # => BigDecimal from float
|
30
|
+
# @example Converting an integer
|
31
|
+
# Coercions::BigDecimal.call(100) #=> #<BigDecimal:...,'0.1E3',9(18)>
|
44
32
|
def call(value, options = {})
|
45
33
|
BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
|
46
34
|
rescue ArgumentError, TypeError
|
@@ -2,53 +2,34 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for converting values to booleans.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
# @example Basic boolean coercion
|
12
|
-
# class ProcessOrderTask < CMDx::Task
|
13
|
-
# optional :send_email, type: :boolean, default: true
|
14
|
-
# optional :is_urgent, type: :boolean, default: false
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Coercion behavior
|
18
|
-
# Coercions::Boolean.call("true") # => true
|
19
|
-
# Coercions::Boolean.call("yes") # => true
|
20
|
-
# Coercions::Boolean.call("1") # => true
|
21
|
-
# Coercions::Boolean.call("false") # => false
|
22
|
-
# Coercions::Boolean.call("no") # => false
|
23
|
-
# Coercions::Boolean.call("0") # => false
|
24
|
-
# Coercions::Boolean.call("invalid") # => raises CoercionError
|
25
|
-
#
|
26
|
-
# @see ParameterValue Parameter value coercion
|
27
|
-
# @see Parameter Parameter type definitions
|
28
|
-
module Boolean
|
7
|
+
# This coercion handles conversion of various string representations to
|
8
|
+
# boolean values, supporting common true/false variations like "yes/no",
|
9
|
+
# "1/0", and "t/f".
|
10
|
+
class Boolean < Coercion
|
29
11
|
|
30
|
-
# Pattern matching false-like values (case insensitive)
|
31
|
-
# @return [Regexp] regex for falsey string values
|
32
12
|
FALSEY = /^(false|f|no|n|0)$/i
|
33
|
-
|
34
|
-
# Pattern matching true-like values (case insensitive)
|
35
|
-
# @return [Regexp] regex for truthy string values
|
36
13
|
TRUTHY = /^(true|t|yes|y|1)$/i
|
37
14
|
|
38
|
-
|
39
|
-
|
40
|
-
#
|
15
|
+
# Converts the given value to a boolean.
|
16
|
+
#
|
17
|
+
# @param value [Object] the value to convert to a boolean
|
18
|
+
# @param _options [Hash] optional configuration (currently unused)
|
19
|
+
#
|
20
|
+
# @return [Boolean] the converted boolean value
|
21
|
+
#
|
22
|
+
# @raise [CoercionError] if the value cannot be converted to a boolean
|
41
23
|
#
|
42
|
-
# @
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
24
|
+
# @example Converting truthy values
|
25
|
+
# Coercions::Boolean.call('true') #=> true
|
26
|
+
# Coercions::Boolean.call('yes') #=> true
|
27
|
+
# Coercions::Boolean.call('1') #=> true
|
46
28
|
#
|
47
|
-
# @example
|
48
|
-
# Coercions::Boolean.call(
|
49
|
-
# Coercions::Boolean.call(
|
50
|
-
# Coercions::Boolean.call(
|
51
|
-
# Coercions::Boolean.call("0") # => false
|
29
|
+
# @example Converting falsey values
|
30
|
+
# Coercions::Boolean.call('false') #=> false
|
31
|
+
# Coercions::Boolean.call('no') #=> false
|
32
|
+
# Coercions::Boolean.call('0') #=> false
|
52
33
|
def call(value, _options = {})
|
53
34
|
case value.to_s.downcase
|
54
35
|
when FALSEY then false
|
@@ -2,41 +2,28 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for converting values to complex numbers.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
# @example Basic complex coercion
|
12
|
-
# class MathTask < CMDx::Task
|
13
|
-
# required :complex_number, type: :complex
|
14
|
-
# optional :coefficient, type: :complex, default: Complex(1, 0)
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Coercion behavior
|
18
|
-
# Coercions::Complex.call("1+2i") # => (1+2i)
|
19
|
-
# Coercions::Complex.call("3-4i") # => (3-4i)
|
20
|
-
# Coercions::Complex.call(5) # => (5+0i)
|
21
|
-
# Coercions::Complex.call("invalid") # => raises CoercionError
|
22
|
-
#
|
23
|
-
# @see ParameterValue Parameter value coercion
|
24
|
-
# @see Parameter Parameter type definitions
|
25
|
-
module Complex
|
26
|
-
|
27
|
-
module_function
|
7
|
+
# This coercion handles conversion of various types to complex numbers,
|
8
|
+
# including strings, integers, floats, and other numeric types.
|
9
|
+
class Complex < Coercion
|
28
10
|
|
29
|
-
#
|
11
|
+
# Converts the given value to a complex number.
|
12
|
+
#
|
13
|
+
# @param value [Object] the value to convert to a complex number
|
14
|
+
# @param _options [Hash] optional configuration (currently unused)
|
15
|
+
#
|
16
|
+
# @return [Complex] the converted complex number value
|
17
|
+
#
|
18
|
+
# @raise [CoercionError] if the value cannot be converted to a complex number
|
30
19
|
#
|
31
|
-
# @
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# @raise [CoercionError] if coercion fails
|
20
|
+
# @example Converting numeric values
|
21
|
+
# Coercions::Complex.call(5) #=> (5+0i)
|
22
|
+
# Coercions::Complex.call(3.14) #=> (3.14+0i)
|
35
23
|
#
|
36
|
-
# @example
|
37
|
-
# Coercions::Complex.call("
|
38
|
-
# Coercions::Complex.call(
|
39
|
-
# Coercions::Complex.call("3.5-2i") # => (3.5-2i)
|
24
|
+
# @example Converting string representations
|
25
|
+
# Coercions::Complex.call("2+3i") #=> (2+3i)
|
26
|
+
# Coercions::Complex.call("1-2i") #=> (1-2i)
|
40
27
|
def call(value, _options = {})
|
41
28
|
Complex(value)
|
42
29
|
rescue ArgumentError, TypeError
|