cmdx 1.0.1 → 1.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 +4 -4
- data/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +17 -2
- data/README.md +1 -1
- data/docs/basics/call.md +2 -2
- data/docs/basics/chain.md +1 -1
- data/docs/callbacks.md +3 -36
- data/docs/configuration.md +58 -12
- data/docs/interruptions/exceptions.md +1 -1
- data/docs/interruptions/faults.md +2 -2
- data/docs/logging.md +4 -4
- data/docs/middlewares.md +43 -43
- data/docs/parameters/coercions.md +49 -38
- data/docs/parameters/defaults.md +1 -1
- data/docs/parameters/validations.md +0 -39
- data/docs/testing.md +11 -12
- data/docs/workflows.md +4 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -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 +57 -171
- data/lib/cmdx/context.rb +22 -165
- 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 +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- 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 +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- 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 +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- 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 +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -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 +46 -339
- 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 +34 -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
data/lib/cmdx/coercions/time.rb
CHANGED
@@ -2,54 +2,38 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for converting values to Time objects.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
# @example Basic time coercion
|
12
|
-
# class ProcessOrderTask < CMDx::Task
|
13
|
-
# required :created_at, type: :time
|
14
|
-
# optional :scheduled_at, type: :time, format: "%Y-%m-%d %H:%M:%S"
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Coercion behavior
|
18
|
-
# Coercions::Time.call("2023-12-25 14:30:00") # => Time object
|
19
|
-
# Coercions::Time.call("25/12/2023 2:30 PM", format: "%d/%m/%Y %l:%M %p") # Custom format
|
20
|
-
# Coercions::Time.call(Date.today) # => Time (from Date)
|
21
|
-
# Coercions::Time.call("invalid") # => raises CoercionError
|
22
|
-
#
|
23
|
-
# @see ParameterValue Parameter value coercion
|
24
|
-
# @see Parameter Parameter type definitions
|
25
|
-
module Time
|
26
|
-
|
27
|
-
# Time-compatible class names that are passed through unchanged
|
28
|
-
# @return [Array<String>] class names that represent time-like objects
|
29
|
-
ANALOG_TYPES = %w[Date DateTime Time].freeze
|
7
|
+
# This coercion handles conversion of various types to Time objects, with special
|
8
|
+
# handling for analog types (DateTime, Time) and custom format parsing.
|
9
|
+
class Time < Coercion
|
30
10
|
|
31
|
-
|
11
|
+
ANALOG_TYPES = %w[DateTime Time].freeze
|
32
12
|
|
33
|
-
#
|
13
|
+
# Converts the given value to a Time object.
|
14
|
+
#
|
15
|
+
# @param value [Object] the value to convert to a Time object
|
16
|
+
# @param options [Hash] optional configuration
|
17
|
+
# @option options [String] :strptime custom format string for parsing
|
18
|
+
#
|
19
|
+
# @return [Time] the converted Time object
|
20
|
+
#
|
21
|
+
# @raise [CoercionError] if the value cannot be converted to a Time object
|
34
22
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# - String with custom format (parsed using strptime)
|
38
|
-
# - String with standard format (parsed using Time.parse)
|
23
|
+
# @example Converting with custom format
|
24
|
+
# Coercions::Time.call('2023-12-25 14:30', strptime: '%Y-%m-%d %H:%M') #=> 2023-12-25 14:30:00
|
39
25
|
#
|
40
|
-
# @
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# @return [Time] coerced time value
|
44
|
-
# @raise [CoercionError] if coercion fails
|
26
|
+
# @example Converting standard time strings
|
27
|
+
# Coercions::Time.call('2023-12-25 14:30:00') #=> 2023-12-25 14:30:00
|
28
|
+
# Coercions::Time.call('Dec 25, 2023') #=> 2023-12-25 00:00:00
|
45
29
|
#
|
46
|
-
# @example
|
47
|
-
#
|
48
|
-
# Coercions::Time.call(
|
49
|
-
# Coercions::Time.call(DateTime.now) # => Time from DateTime
|
30
|
+
# @example Analog types pass through unchanged
|
31
|
+
# time = Time.now
|
32
|
+
# Coercions::Time.call(time) #=> time (unchanged)
|
50
33
|
def call(value, options = {})
|
51
34
|
return value if ANALOG_TYPES.include?(value.class.name)
|
52
|
-
return
|
35
|
+
return value.to_time if value.respond_to?(:to_time)
|
36
|
+
return ::Time.strptime(value, options[:strptime]) if options[:strptime]
|
53
37
|
|
54
38
|
::Time.parse(value)
|
55
39
|
rescue ArgumentError, TypeError
|
@@ -2,41 +2,24 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Coercions
|
5
|
-
#
|
5
|
+
# Coercion class for virtual values that performs no conversion.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
#
|
12
|
-
# @example Virtual coercion usage
|
13
|
-
# class ProcessOrderTask < CMDx::Task
|
14
|
-
# required :order # defaults to virtual type
|
15
|
-
# optional :metadata, type: :virtual
|
16
|
-
# optional :config, type: :virtual
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# @example Coercion behavior
|
20
|
-
# Coercions::Virtual.call("string") # => "string"
|
21
|
-
# Coercions::Virtual.call(123) # => 123
|
22
|
-
# Coercions::Virtual.call([1, 2, 3]) # => [1, 2, 3]
|
23
|
-
# Coercions::Virtual.call({a: 1}) # => {a: 1}
|
24
|
-
# Coercions::Virtual.call(nil) # => nil
|
25
|
-
#
|
26
|
-
# @see ParameterValue Parameter value coercion
|
27
|
-
# @see Parameter Parameter type definitions (defaults to virtual)
|
28
|
-
module Virtual
|
7
|
+
# This coercion acts as a pass-through, returning the input value unchanged.
|
8
|
+
# It's useful when you want to maintain the original value type and format
|
9
|
+
# without any transformation.
|
10
|
+
class Virtual < Coercion
|
29
11
|
|
30
|
-
|
31
|
-
|
32
|
-
#
|
12
|
+
# Returns the given value unchanged.
|
13
|
+
#
|
14
|
+
# @param value [Object] the value to return as-is
|
15
|
+
# @param _options [Hash] optional configuration (currently unused)
|
33
16
|
#
|
34
|
-
# @
|
35
|
-
# @param _options [Hash] coercion options (unused)
|
36
|
-
# @return [Object] the original value without modification
|
17
|
+
# @return [Object] the original value without any conversion
|
37
18
|
#
|
38
|
-
# @example
|
39
|
-
# Coercions::Virtual.call(
|
19
|
+
# @example Returning values unchanged
|
20
|
+
# Coercions::Virtual.call("hello") #=> "hello"
|
21
|
+
# Coercions::Virtual.call(123) #=> 123
|
22
|
+
# Coercions::Virtual.call(nil) #=> nil
|
40
23
|
def call(value, _options = {})
|
41
24
|
value
|
42
25
|
end
|
data/lib/cmdx/configuration.rb
CHANGED
@@ -2,119 +2,65 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
|
5
|
-
|
6
|
-
#
|
7
|
-
# The configuration system allows customization of default behaviors for tasks,
|
8
|
-
# workflows, logging, and error handling across the entire application.
|
9
|
-
#
|
10
|
-
# Configuration settings are stored as instance variables with explicit accessors
|
11
|
-
# and can be modified through the configure block pattern. These settings serve
|
12
|
-
# as defaults that can be overridden at the task or workflow level when needed.
|
13
|
-
#
|
14
|
-
# ## Available Configuration Options
|
15
|
-
#
|
16
|
-
# - **logger**: Logger instance for task execution logging
|
17
|
-
# - **task_halt**: Result statuses that cause `call!` to raise faults
|
18
|
-
# - **workflow_halt**: Result statuses that halt workflow execution
|
19
|
-
# - **middlewares**: Global middleware registry applied to all tasks
|
20
|
-
# - **callbacks**: Global callback registry applied to all tasks
|
21
|
-
#
|
22
|
-
# ## Configuration Hierarchy
|
23
|
-
#
|
24
|
-
# CMDx follows a configuration hierarchy where settings can be overridden:
|
25
|
-
# 1. **Global Configuration**: Framework-wide defaults (this module)
|
26
|
-
# 2. **Task Settings**: Class-level overrides via `task_settings!`
|
27
|
-
# 3. **Runtime Parameters**: Instance-specific overrides during execution
|
28
|
-
#
|
29
|
-
# @example Basic configuration setup
|
30
|
-
# CMDx.configure do |config|
|
31
|
-
# config.logger = Logger.new($stdout)
|
32
|
-
# config.task_halt = ["failed"] # Only halt on failures
|
33
|
-
# config.middlewares.use CMDx::Middlewares::Timeout, 30
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# @example Rails initializer configuration
|
37
|
-
# # config/initializers/cmdx.rb
|
38
|
-
# CMDx.configure do |config|
|
39
|
-
# config.logger = Logger.new($stdout)
|
40
|
-
# config.task_halt = CMDx::Result::FAILED
|
41
|
-
# config.workflow_halt = [CMDx::Result::FAILED, CMDx::Result::SKIPPED]
|
42
|
-
#
|
43
|
-
# # Add global middlewares
|
44
|
-
# config.middlewares.use CMDx::Middlewares::Timeout, 30
|
45
|
-
# config.middlewares.use AuthenticationMiddleware if Rails.env.production?
|
46
|
-
#
|
47
|
-
# # Add global callbacks
|
48
|
-
# config.callbacks.register :before_execution, :log_task_start
|
49
|
-
# config.callbacks.register :on_success, NotificationCallback.new([:slack])
|
50
|
-
# config.callbacks.register :on_failure, :alert_admin, if: :production?
|
51
|
-
# end
|
52
|
-
#
|
53
|
-
# @example Custom logger configuration
|
54
|
-
# CMDx.configure do |config|
|
55
|
-
# config.logger = Logger.new(
|
56
|
-
# Rails.root.join('log', 'cmdx.log'),
|
57
|
-
# formatter: CMDx::LogFormatters::Json.new
|
58
|
-
# )
|
59
|
-
# end
|
60
|
-
#
|
61
|
-
# @example Environment-specific configuration
|
62
|
-
# CMDx.configure do |config|
|
63
|
-
# case Rails.env
|
64
|
-
# when 'development'
|
65
|
-
# config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::PrettyLine.new)
|
66
|
-
# when 'test'
|
67
|
-
# config.logger = Logger.new('/dev/null') # Silent logging
|
68
|
-
# when 'production'
|
69
|
-
# config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::Json.new)
|
70
|
-
# end
|
71
|
-
# end
|
72
|
-
#
|
73
|
-
# @see Task Task-level configuration overrides
|
74
|
-
# @see Workflow Workflow-level configuration overrides
|
75
|
-
# @see LogFormatters Available logging formatters
|
76
|
-
# @see Result Result statuses for halt configuration
|
77
|
-
# @since 1.0.0
|
78
|
-
|
79
|
-
##
|
80
|
-
# Configuration class that manages CMDx framework settings.
|
81
|
-
# Provides explicit attribute accessors for all configuration options.
|
82
|
-
#
|
83
|
-
# @since 1.0.0
|
5
|
+
# Global configuration class for CMDx framework settings.
|
6
|
+
# Manages logging, middleware, callbacks, coercions, validators, and halt conditions.
|
84
7
|
class Configuration
|
85
8
|
|
86
|
-
# Default configuration values
|
87
9
|
DEFAULT_HALT = "failed"
|
88
10
|
|
89
|
-
#
|
90
|
-
attr_accessor :logger
|
11
|
+
# @return [Logger] Logger instance for task execution logging
|
12
|
+
attr_accessor :logger
|
13
|
+
|
14
|
+
# @return [MiddlewareRegistry] Global middleware registry applied to all tasks
|
15
|
+
attr_accessor :middlewares
|
16
|
+
|
17
|
+
# @return [CallbackRegistry] Global callback registry applied to all tasks
|
18
|
+
attr_accessor :callbacks
|
19
|
+
|
20
|
+
# @return [CoercionRegistry] Global coercion registry for custom parameter types
|
21
|
+
attr_accessor :coercions
|
91
22
|
|
92
|
-
|
93
|
-
|
23
|
+
# @return [ValidatorRegistry] Global validator registry for custom parameter validation
|
24
|
+
attr_accessor :validators
|
25
|
+
|
26
|
+
# @return [String, Array<String>] Result statuses that cause `call!` to raise faults
|
27
|
+
attr_accessor :task_halt
|
28
|
+
|
29
|
+
# @return [String, Array<String>] Result statuses that halt workflow execution
|
30
|
+
attr_accessor :workflow_halt
|
31
|
+
|
32
|
+
# Initialize a new Configuration instance with default settings.
|
94
33
|
#
|
95
34
|
# @example
|
96
35
|
# config = CMDx::Configuration.new
|
36
|
+
# config.logger.level = Logger::DEBUG
|
37
|
+
#
|
38
|
+
# @return [Configuration] A new configuration instance
|
97
39
|
def initialize
|
98
|
-
@logger
|
99
|
-
@middlewares
|
100
|
-
@callbacks
|
101
|
-
@
|
40
|
+
@logger = ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
|
41
|
+
@middlewares = MiddlewareRegistry.new
|
42
|
+
@callbacks = CallbackRegistry.new
|
43
|
+
@coercions = CoercionRegistry.new
|
44
|
+
@validators = ValidatorRegistry.new
|
45
|
+
@task_halt = DEFAULT_HALT
|
102
46
|
@workflow_halt = DEFAULT_HALT
|
103
47
|
end
|
104
48
|
|
105
|
-
|
106
|
-
# Returns a hash representation of the configuration.
|
107
|
-
# Used internally by the framework for configuration merging.
|
49
|
+
# Convert the configuration to a hash representation.
|
108
50
|
#
|
109
|
-
# @return [Hash] configuration attributes as a hash
|
110
51
|
# @example
|
111
|
-
# config = CMDx.
|
112
|
-
# config.to_h
|
52
|
+
# config = CMDx::Configuration.new
|
53
|
+
# hash = config.to_h
|
54
|
+
# puts hash[:task_halt] # => "failed"
|
55
|
+
#
|
56
|
+
# @return [Hash] Hash containing all configuration values
|
113
57
|
def to_h
|
114
58
|
{
|
115
59
|
logger: @logger,
|
116
60
|
middlewares: @middlewares,
|
117
61
|
callbacks: @callbacks,
|
62
|
+
coercions: @coercions,
|
63
|
+
validators: @validators,
|
118
64
|
task_halt: @task_halt,
|
119
65
|
workflow_halt: @workflow_halt
|
120
66
|
}
|
@@ -124,72 +70,33 @@ module CMDx
|
|
124
70
|
|
125
71
|
module_function
|
126
72
|
|
127
|
-
|
128
|
-
#
|
129
|
-
# Creates a new configuration with default values if none exists.
|
73
|
+
# Get the current global configuration instance.
|
74
|
+
# Creates a new configuration if none exists.
|
130
75
|
#
|
131
|
-
#
|
132
|
-
# throughout the application lifecycle. It uses lazy initialization,
|
133
|
-
# creating the configuration only when first accessed.
|
134
|
-
#
|
135
|
-
# @return [Configuration] the current configuration object
|
136
|
-
#
|
137
|
-
# @example Accessing configuration values
|
138
|
-
# CMDx.configuration.logger #=> <Logger instance>
|
139
|
-
# CMDx.configuration.task_halt #=> "failed"
|
140
|
-
#
|
141
|
-
# @example Checking configuration state
|
76
|
+
# @example
|
142
77
|
# config = CMDx.configuration
|
143
|
-
# config.logger.
|
78
|
+
# config.logger.level = Logger::INFO
|
79
|
+
#
|
80
|
+
# @return [Configuration] The global configuration instance
|
144
81
|
def configuration
|
145
82
|
return @configuration if @configuration
|
146
83
|
|
147
84
|
@configuration ||= Configuration.new
|
148
85
|
end
|
149
86
|
|
150
|
-
|
151
|
-
# Configures CMDx settings using a block-based DSL.
|
152
|
-
# This is the preferred method for setting up CMDx configuration
|
153
|
-
# as it provides a clean, readable syntax for configuration management.
|
154
|
-
#
|
155
|
-
# The configuration block yields the current configuration object,
|
156
|
-
# allowing you to set multiple options in a single, organized block.
|
157
|
-
#
|
158
|
-
# @yieldparam config [Configuration] the configuration object to modify
|
159
|
-
# @return [Configuration] the updated configuration object
|
160
|
-
# @raise [ArgumentError] if no block is provided
|
87
|
+
# Configure the global CMDx settings using a block.
|
161
88
|
#
|
162
|
-
# @example
|
89
|
+
# @example
|
163
90
|
# CMDx.configure do |config|
|
164
|
-
# config.task_halt = ["failed", "
|
91
|
+
# config.task_halt = ["failed", "error"]
|
92
|
+
# config.logger.level = Logger::DEBUG
|
165
93
|
# end
|
166
94
|
#
|
167
|
-
# @
|
168
|
-
# CMDx.configure do |config|
|
169
|
-
# config.logger = Rails.logger if defined?(Rails)
|
170
|
-
#
|
171
|
-
# config.task_halt = if Rails.env.production?
|
172
|
-
# "failed" # Only halt on failures in production
|
173
|
-
# else
|
174
|
-
# ["failed", "skipped"] # Halt on both in development
|
175
|
-
# end
|
95
|
+
# @yield [Configuration] The configuration instance
|
176
96
|
#
|
177
|
-
|
178
|
-
# end
|
97
|
+
# @return [Configuration] The configured instance
|
179
98
|
#
|
180
|
-
# @
|
181
|
-
# CMDx.configure do |config|
|
182
|
-
# config.logger = Logger.new($stdout).tap do |logger|
|
183
|
-
# logger.formatter = case ENV['LOG_FORMAT']
|
184
|
-
# when 'json'
|
185
|
-
# CMDx::LogFormatters::Json.new
|
186
|
-
# when 'pretty'
|
187
|
-
# CMDx::LogFormatters::PrettyLine.new
|
188
|
-
# else
|
189
|
-
# CMDx::LogFormatters::Line.new
|
190
|
-
# end
|
191
|
-
# end
|
192
|
-
# end
|
99
|
+
# @raise [ArgumentError] If no block is provided
|
193
100
|
def configure
|
194
101
|
raise ArgumentError, "block required" unless block_given?
|
195
102
|
|
@@ -198,34 +105,13 @@ module CMDx
|
|
198
105
|
config
|
199
106
|
end
|
200
107
|
|
201
|
-
|
202
|
-
# Resets the configuration to default values.
|
203
|
-
# This method creates a fresh configuration object with framework defaults,
|
204
|
-
# discarding any previously set custom values.
|
108
|
+
# Reset the global configuration to default values.
|
205
109
|
#
|
206
|
-
# @
|
207
|
-
#
|
208
|
-
# @example Resetting configuration
|
209
|
-
# # After custom configuration
|
210
|
-
# CMDx.configure { |c| c.task_halt = ["failed"] }
|
211
|
-
# CMDx.configuration.task_halt #=> ["failed"]
|
212
|
-
#
|
213
|
-
# # Reset to defaults
|
110
|
+
# @example
|
214
111
|
# CMDx.reset_configuration!
|
215
|
-
# CMDx.configuration.task_halt
|
216
|
-
#
|
217
|
-
# @example Testing with clean configuration
|
218
|
-
# # In test setup
|
219
|
-
# def setup
|
220
|
-
# CMDx.reset_configuration! # Start with clean defaults
|
221
|
-
# end
|
222
|
-
#
|
223
|
-
# @example Conditional reset
|
224
|
-
# # Reset configuration in development for experimentation
|
225
|
-
# CMDx.reset_configuration! if Rails.env.development?
|
112
|
+
# CMDx.configuration.task_halt # => "failed"
|
226
113
|
#
|
227
|
-
# @
|
228
|
-
# to return to a known default state.
|
114
|
+
# @return [Configuration] A new configuration instance with defaults
|
229
115
|
def reset_configuration!
|
230
116
|
@configuration = Configuration.new
|
231
117
|
end
|
data/lib/cmdx/context.rb
CHANGED
@@ -1,181 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# Context provides a flexible parameter storage and data passing mechanism for CMDx tasks.
|
6
|
-
# It extends LazyStruct to offer dynamic attribute access with both hash-style and method-style
|
7
|
-
# syntax, serving as the primary interface for task input parameters and inter-task communication.
|
4
|
+
# Parameter and data context for task execution.
|
8
5
|
#
|
9
|
-
# Context
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# ## Usage Patterns
|
16
|
-
#
|
17
|
-
# Context is typically used in three main scenarios:
|
18
|
-
# 1. **Parameter Input**: Passing initial data to tasks
|
19
|
-
# 2. **Data Storage**: Storing intermediate results during task execution
|
20
|
-
# 3. **Task Communication**: Sharing data between multiple tasks
|
21
|
-
#
|
22
|
-
# @example Basic parameter input
|
23
|
-
# class ProcessOrderTask < CMDx::Task
|
24
|
-
# required :order_id, type: :integer
|
25
|
-
# optional :notify_customer, type: :boolean, default: true
|
26
|
-
#
|
27
|
-
# def call
|
28
|
-
# context.order = Order.find(order_id)
|
29
|
-
# context.processed_at = Time.now
|
30
|
-
#
|
31
|
-
# if notify_customer
|
32
|
-
# context.notification_sent = send_notification
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
# result = ProcessOrderTask.call(order_id: 123, notify_customer: false)
|
38
|
-
# result.context.order #=> <Order id: 123>
|
39
|
-
# result.context.processed_at #=> 2023-01-01 12:00:00 UTC
|
40
|
-
# result.context.notification_sent #=> nil
|
41
|
-
#
|
42
|
-
# @example Dynamic attribute assignment
|
43
|
-
# class DataProcessingTask < CMDx::Task
|
44
|
-
# required :input_data, type: :hash
|
45
|
-
#
|
46
|
-
# def call
|
47
|
-
# # Method-style assignment
|
48
|
-
# context.processed_data = transform(input_data)
|
49
|
-
# context.validation_errors = validate(context.processed_data)
|
50
|
-
#
|
51
|
-
# # Hash-style assignment
|
52
|
-
# context[:metadata] = { processed_at: Time.now }
|
53
|
-
# context["summary"] = generate_summary
|
54
|
-
#
|
55
|
-
# # Workflow assignment
|
56
|
-
# context.merge!(
|
57
|
-
# status: "complete",
|
58
|
-
# record_count: context.processed_data.size
|
59
|
-
# )
|
60
|
-
# end
|
61
|
-
# end
|
62
|
-
#
|
63
|
-
# @example Inter-task communication
|
64
|
-
# class OrderProcessingWorkflow < CMDx::Workflow
|
65
|
-
# def call
|
66
|
-
# # First task sets up context
|
67
|
-
# ValidateOrderTask.call(context)
|
68
|
-
#
|
69
|
-
# # Subsequent tasks use and modify context
|
70
|
-
# ProcessPaymentTask.call(context)
|
71
|
-
# UpdateInventoryTask.call(context)
|
72
|
-
# SendConfirmationTask.call(context)
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# # Initial context with order data
|
77
|
-
# result = OrderProcessingWorkflow.call(
|
78
|
-
# order_id: 123,
|
79
|
-
# payment_method: "credit_card",
|
80
|
-
# customer_email: "customer@example.com"
|
81
|
-
# )
|
82
|
-
#
|
83
|
-
# # Context accumulates data from all tasks
|
84
|
-
# result.context.order #=> <Order> (from ValidateOrderTask)
|
85
|
-
# result.context.payment_result #=> <Payment> (from ProcessPaymentTask)
|
86
|
-
# result.context.inventory_updated #=> true (from UpdateInventoryTask)
|
87
|
-
# result.context.confirmation_sent #=> true (from SendConfirmationTask)
|
88
|
-
#
|
89
|
-
# @example Context passing between tasks
|
90
|
-
# class ProcessOrderTask < CMDx::Task
|
91
|
-
# required :order_id, type: :integer
|
92
|
-
#
|
93
|
-
# def call
|
94
|
-
# context.order = Order.find(order_id)
|
95
|
-
#
|
96
|
-
# # Pass context to subtasks
|
97
|
-
# payment_result = ProcessPaymentTask.call(context)
|
98
|
-
# email_result = SendEmailTask.call(context)
|
99
|
-
#
|
100
|
-
# # Results maintain context continuity
|
101
|
-
# context.payment_processed = payment_result.success?
|
102
|
-
# context.email_sent = email_result.success?
|
103
|
-
# end
|
104
|
-
# end
|
105
|
-
#
|
106
|
-
# # After execution, context contains accumulated data
|
107
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
108
|
-
# result.context.order #=> <Order>
|
109
|
-
# result.context.payment_processed #=> true
|
110
|
-
# result.context.email_sent #=> true
|
111
|
-
#
|
112
|
-
# @example Context with nested data structures
|
113
|
-
# class AnalyticsTask < CMDx::Task
|
114
|
-
# required :user_id, type: :integer
|
115
|
-
#
|
116
|
-
# def call
|
117
|
-
# context.user = User.find(user_id)
|
118
|
-
# context.analytics = {
|
119
|
-
# page_views: calculate_page_views,
|
120
|
-
# session_duration: calculate_session_duration,
|
121
|
-
# conversion_rate: calculate_conversion_rate
|
122
|
-
# }
|
123
|
-
#
|
124
|
-
# # Access nested data
|
125
|
-
# context.dig(:analytics, :page_views) #=> 150
|
126
|
-
#
|
127
|
-
# # Add more nested data
|
128
|
-
# context.analytics[:last_login] = context.user.last_login
|
129
|
-
# end
|
130
|
-
# end
|
131
|
-
#
|
132
|
-
# @see LazyStruct Base class providing dynamic attribute functionality
|
133
|
-
# @see Task Task base class that uses Context for parameter storage
|
134
|
-
# @see Chain Chain execution context that Context belongs to
|
135
|
-
# @see Parameter Parameter definitions that populate Context
|
136
|
-
# @since 1.0.0
|
6
|
+
# Context provides flexible data storage and access patterns for task
|
7
|
+
# parameters and runtime data. Built on LazyStruct, it supports both
|
8
|
+
# hash-like and object-like access patterns with dynamic attribute
|
9
|
+
# assignment and automatic key normalization.
|
137
10
|
class Context < LazyStruct
|
138
11
|
|
139
|
-
|
140
|
-
# Builds a Context instance from the given input, with intelligent handling
|
141
|
-
# of existing Context objects to avoid unnecessary object creation.
|
12
|
+
# Creates or returns a context instance from the given input.
|
142
13
|
#
|
143
|
-
# This
|
144
|
-
#
|
145
|
-
#
|
146
|
-
# - Converting hash-like objects into new Context instances
|
14
|
+
# This method provides a safe way to build context instances, returning
|
15
|
+
# the input unchanged if it's already a Context instance and not frozen,
|
16
|
+
# otherwise creating a new Context instance with the provided data.
|
147
17
|
#
|
148
|
-
# @param context [Hash, Context,
|
149
|
-
# @return [Context] a Context instance ready for task execution
|
18
|
+
# @param context [Hash, Context, Object] input data to build context from
|
150
19
|
#
|
151
|
-
# @
|
152
|
-
# context = Context.build(name: "John", age: 30)
|
153
|
-
# context.name #=> "John"
|
154
|
-
# context.age #=> 30
|
20
|
+
# @return [Context] a Context instance containing the provided data
|
155
21
|
#
|
156
|
-
# @
|
157
|
-
# original = Context.build(data: "test")
|
158
|
-
# reused = Context.build(original)
|
159
|
-
# original.object_id == reused.object_id #=> true
|
22
|
+
# @raise [ArgumentError] if the input doesn't respond to to_h
|
160
23
|
#
|
161
|
-
# @example
|
162
|
-
#
|
163
|
-
#
|
164
|
-
# new_context = Context.build(original)
|
165
|
-
# original.object_id == new_context.object_id #=> false
|
24
|
+
# @example Build context from hash
|
25
|
+
# Context.build(name: "John", age: 30)
|
26
|
+
# # => #<CMDx::Context :name="John" :age=30>
|
166
27
|
#
|
167
|
-
# @example
|
168
|
-
#
|
169
|
-
#
|
170
|
-
# context
|
171
|
-
# context.user #=> { name: "John" }
|
28
|
+
# @example Build context from existing context
|
29
|
+
# existing = Context.build(user_id: 123)
|
30
|
+
# Context.build(existing)
|
31
|
+
# # => returns existing context unchanged
|
172
32
|
#
|
173
|
-
# @example
|
174
|
-
#
|
175
|
-
#
|
176
|
-
# # Equivalent to:
|
177
|
-
# # context = Context.build(order_id: 123, priority: "high")
|
178
|
-
# # ProcessOrderTask.new(context).call
|
33
|
+
# @example Build context from hash-like object
|
34
|
+
# Context.build(OpenStruct.new(status: "active"))
|
35
|
+
# # => #<CMDx::Context :status="active">
|
179
36
|
def self.build(context = {})
|
180
37
|
return context if context.is_a?(self) && !context.frozen?
|
181
38
|
|