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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +17 -2
  6. data/README.md +1 -1
  7. data/docs/basics/call.md +2 -2
  8. data/docs/basics/chain.md +1 -1
  9. data/docs/callbacks.md +3 -36
  10. data/docs/configuration.md +58 -12
  11. data/docs/interruptions/exceptions.md +1 -1
  12. data/docs/interruptions/faults.md +2 -2
  13. data/docs/logging.md +4 -4
  14. data/docs/middlewares.md +43 -43
  15. data/docs/parameters/coercions.md +49 -38
  16. data/docs/parameters/defaults.md +1 -1
  17. data/docs/parameters/validations.md +0 -39
  18. data/docs/testing.md +11 -12
  19. data/docs/workflows.md +4 -4
  20. data/lib/cmdx/.DS_Store +0 -0
  21. data/lib/cmdx/callback.rb +36 -56
  22. data/lib/cmdx/callback_registry.rb +82 -73
  23. data/lib/cmdx/chain.rb +65 -122
  24. data/lib/cmdx/chain_inspector.rb +22 -115
  25. data/lib/cmdx/chain_serializer.rb +17 -148
  26. data/lib/cmdx/coercion.rb +49 -0
  27. data/lib/cmdx/coercion_registry.rb +94 -0
  28. data/lib/cmdx/coercions/array.rb +18 -36
  29. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  30. data/lib/cmdx/coercions/boolean.rb +21 -40
  31. data/lib/cmdx/coercions/complex.rb +18 -31
  32. data/lib/cmdx/coercions/date.rb +20 -39
  33. data/lib/cmdx/coercions/date_time.rb +22 -39
  34. data/lib/cmdx/coercions/float.rb +19 -32
  35. data/lib/cmdx/coercions/hash.rb +22 -41
  36. data/lib/cmdx/coercions/integer.rb +20 -33
  37. data/lib/cmdx/coercions/rational.rb +20 -32
  38. data/lib/cmdx/coercions/string.rb +23 -31
  39. data/lib/cmdx/coercions/time.rb +24 -40
  40. data/lib/cmdx/coercions/virtual.rb +14 -31
  41. data/lib/cmdx/configuration.rb +57 -171
  42. data/lib/cmdx/context.rb +22 -165
  43. data/lib/cmdx/core_ext/hash.rb +42 -67
  44. data/lib/cmdx/core_ext/module.rb +35 -79
  45. data/lib/cmdx/core_ext/object.rb +63 -98
  46. data/lib/cmdx/correlator.rb +40 -156
  47. data/lib/cmdx/error.rb +37 -202
  48. data/lib/cmdx/errors.rb +165 -202
  49. data/lib/cmdx/fault.rb +55 -158
  50. data/lib/cmdx/faults.rb +26 -137
  51. data/lib/cmdx/immutator.rb +22 -109
  52. data/lib/cmdx/lazy_struct.rb +103 -187
  53. data/lib/cmdx/log_formatters/json.rb +14 -40
  54. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  55. data/lib/cmdx/log_formatters/line.rb +14 -48
  56. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  57. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  58. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  59. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  60. data/lib/cmdx/log_formatters/raw.rb +19 -49
  61. data/lib/cmdx/logger.rb +20 -82
  62. data/lib/cmdx/logger_ansi.rb +18 -75
  63. data/lib/cmdx/logger_serializer.rb +24 -114
  64. data/lib/cmdx/middleware.rb +38 -60
  65. data/lib/cmdx/middleware_registry.rb +81 -77
  66. data/lib/cmdx/middlewares/correlate.rb +41 -226
  67. data/lib/cmdx/middlewares/timeout.rb +46 -185
  68. data/lib/cmdx/parameter.rb +120 -198
  69. data/lib/cmdx/parameter_evaluator.rb +231 -0
  70. data/lib/cmdx/parameter_inspector.rb +25 -56
  71. data/lib/cmdx/parameter_registry.rb +59 -84
  72. data/lib/cmdx/parameter_serializer.rb +23 -74
  73. data/lib/cmdx/railtie.rb +24 -107
  74. data/lib/cmdx/result.rb +254 -260
  75. data/lib/cmdx/result_ansi.rb +19 -85
  76. data/lib/cmdx/result_inspector.rb +27 -68
  77. data/lib/cmdx/result_logger.rb +18 -81
  78. data/lib/cmdx/result_serializer.rb +28 -132
  79. data/lib/cmdx/rspec/matchers.rb +28 -0
  80. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  81. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  82. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  83. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  84. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  85. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  86. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  87. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  88. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  89. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  90. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  91. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  92. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  93. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  94. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  95. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  96. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  97. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  98. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  99. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  100. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  101. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  102. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  103. data/lib/cmdx/task.rb +213 -425
  104. data/lib/cmdx/task_deprecator.rb +55 -0
  105. data/lib/cmdx/task_processor.rb +245 -0
  106. data/lib/cmdx/task_serializer.rb +22 -70
  107. data/lib/cmdx/utils/ansi_color.rb +13 -89
  108. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  109. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  110. data/lib/cmdx/utils/name_affix.rb +21 -71
  111. data/lib/cmdx/validator.rb +48 -0
  112. data/lib/cmdx/validator_registry.rb +86 -0
  113. data/lib/cmdx/validators/exclusion.rb +55 -94
  114. data/lib/cmdx/validators/format.rb +31 -85
  115. data/lib/cmdx/validators/inclusion.rb +65 -110
  116. data/lib/cmdx/validators/length.rb +117 -133
  117. data/lib/cmdx/validators/numeric.rb +123 -130
  118. data/lib/cmdx/validators/presence.rb +38 -79
  119. data/lib/cmdx/version.rb +1 -7
  120. data/lib/cmdx/workflow.rb +46 -339
  121. data/lib/cmdx.rb +1 -1
  122. data/lib/generators/cmdx/install_generator.rb +14 -31
  123. data/lib/generators/cmdx/task_generator.rb +39 -55
  124. data/lib/generators/cmdx/templates/install.rb +24 -6
  125. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  126. data/lib/locales/ar.yml +0 -1
  127. data/lib/locales/cs.yml +0 -1
  128. data/lib/locales/da.yml +0 -1
  129. data/lib/locales/de.yml +0 -1
  130. data/lib/locales/el.yml +0 -1
  131. data/lib/locales/en.yml +0 -1
  132. data/lib/locales/es.yml +0 -1
  133. data/lib/locales/fi.yml +0 -1
  134. data/lib/locales/fr.yml +0 -1
  135. data/lib/locales/he.yml +0 -1
  136. data/lib/locales/hi.yml +0 -1
  137. data/lib/locales/it.yml +0 -1
  138. data/lib/locales/ja.yml +0 -1
  139. data/lib/locales/ko.yml +0 -1
  140. data/lib/locales/nl.yml +0 -1
  141. data/lib/locales/no.yml +0 -1
  142. data/lib/locales/pl.yml +0 -1
  143. data/lib/locales/pt.yml +0 -1
  144. data/lib/locales/ru.yml +0 -1
  145. data/lib/locales/sv.yml +0 -1
  146. data/lib/locales/th.yml +0 -1
  147. data/lib/locales/tr.yml +0 -1
  148. data/lib/locales/vi.yml +0 -1
  149. data/lib/locales/zh.yml +0 -1
  150. metadata +34 -8
  151. data/lib/cmdx/parameter_validator.rb +0 -81
  152. data/lib/cmdx/parameter_value.rb +0 -244
  153. data/lib/cmdx/parameters_inspector.rb +0 -72
  154. data/lib/cmdx/parameters_serializer.rb +0 -115
  155. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  156. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  157. data/lib/cmdx/validators/custom.rb +0 -102
@@ -2,54 +2,38 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
- # Coerces values to Time type.
5
+ # Coercion class for converting values to Time objects.
6
6
  #
7
- # The Time coercion converts parameter values to Time objects
8
- # with support for time parsing, custom format strings, and
9
- # automatic handling of time-like objects.
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
- module_function
11
+ ANALOG_TYPES = %w[DateTime Time].freeze
32
12
 
33
- # Coerce a value to Time.
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
- # Handles multiple input formats:
36
- # - Date, DateTime, Time objects (returned as-is)
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
- # @param value [Object] value to coerce to time
41
- # @param options [Hash] coercion options
42
- # @option options [String] :format custom time format string
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
- # Coercions::Time.call("2023-12-25 14:30:00") # => Time
48
- # Coercions::Time.call("25/12/2023 14:30", format: "%d/%m/%Y %H:%M") # => Time with custom format
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 ::Time.strptime(value, options[:format]) if options[:format]
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
- # Virtual coercion that returns values unchanged.
5
+ # Coercion class for virtual values that performs no conversion.
6
6
  #
7
- # The Virtual coercion is a pass-through that returns the input value
8
- # without any transformation. This is useful for parameters that should
9
- # not undergo type coercion or when you want to preserve the original
10
- # value type and structure.
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
- module_function
31
-
32
- # Return the value unchanged (no coercion).
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
- # @param value [Object] value to pass through unchanged
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(anything) # => anything
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
@@ -2,119 +2,65 @@
2
2
 
3
3
  module CMDx
4
4
 
5
- ##
6
- # Provides global configuration management for CMDx framework settings.
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
- # Configuration attributes
90
- attr_accessor :logger, :middlewares, :callbacks, :task_halt, :workflow_halt
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
- # Initializes a new configuration with default values.
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 = ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
99
- @middlewares = MiddlewareRegistry.new
100
- @callbacks = CallbackRegistry.new
101
- @task_halt = DEFAULT_HALT
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.configuration
112
- # config.to_h #=> { logger: ..., task_halt: "failed", ... }
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
- # Returns the current global configuration instance.
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
- # The configuration is stored as a module-level variable and persists
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.class #=> 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 Basic configuration
89
+ # @example
163
90
  # CMDx.configure do |config|
164
- # config.task_halt = ["failed", "skipped"]
91
+ # config.task_halt = ["failed", "error"]
92
+ # config.logger.level = Logger::DEBUG
165
93
  # end
166
94
  #
167
- # @example Complex configuration with conditionals
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
- # @example Formatter configuration
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
- # @return [Configuration] the newly created configuration with default values
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 #=> "failed"
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
- # @note This method is primarily useful for testing or when you need
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 objects act as the data container for task execution, holding input parameters,
10
- # intermediate results, and any data that needs to be shared between tasks. They support
11
- # dynamic attribute assignment and provide a convenient API for data manipulation throughout
12
- # the task execution lifecycle.
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 factory method provides optimized Context creation by:
144
- # - Returning existing Context objects if they're unfrozen (reusable)
145
- # - Creating new Context objects for frozen contexts (immutable)
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, #to_h] input data for context creation
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
- # @example Creating context from hash
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
- # @example Reusing unfrozen context
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 Creating new context from frozen context
162
- # original = Context.build(data: "test")
163
- # original.freeze
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 Converting ActionController::Parameters
168
- # # In Rails controllers
169
- # params = ActionController::Parameters.new(user: { name: "John" })
170
- # context = Context.build(params.permit(:user))
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 Task execution with built context
174
- # # CMDx automatically uses Context.build for task parameters
175
- # result = ProcessOrderTask.call(order_id: 123, priority: "high")
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