cmdx 1.1.0 → 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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
data/lib/cmdx/coercion.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Base class for implementing type coercion functionality in parameter processing.
4
+ # Base class for implementing parameter coercion functionality in task processing.
5
5
  #
6
- # Coercions are used to convert parameter values from one type to another,
7
- # supporting both built-in types and custom coercion logic. All coercion
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
8
  # implementations must inherit from this class and implement the abstract call method.
9
9
  class Coercion
10
10
 
@@ -16,10 +16,13 @@ module CMDx
16
16
  # @return [Object] the coerced value
17
17
  #
18
18
  # @raise [UndefinedCallError] when the coercion subclass doesn't implement call
19
+ # @raise [CoercionError] when coercion fails in subclass implementations
19
20
  #
20
21
  # @example Execute a coercion on a value
21
- # IntegerCoercion.call("42")
22
- # # => 42
22
+ # StringCoercion.call(123) #=> "123"
23
+ #
24
+ # @example Execute with options
25
+ # CustomCoercion.call("value", strict: true) #=> processed_value
23
26
  def self.call(value, options = {})
24
27
  new.call(value, options)
25
28
  end
@@ -27,21 +30,26 @@ module CMDx
27
30
  # Abstract method that must be implemented by coercion subclasses.
28
31
  #
29
32
  # This method contains the actual coercion logic to convert the input
30
- # value to the desired type. Subclasses must override this method to
31
- # provide their specific coercion implementation.
33
+ # value to the desired type. Subclasses must override this method
34
+ # to provide their specific coercion implementation.
32
35
  #
33
- # @param _value [Object] the value to be coerced
34
- # @param _options [Hash] additional options for the coercion
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)
35
38
  #
36
39
  # @return [Object] the coerced value
37
40
  #
38
41
  # @raise [UndefinedCallError] always raised in the base class
42
+ # @raise [CoercionError] when coercion fails in subclass implementations
39
43
  #
40
44
  # @example Implement in a subclass
41
- # def call(value, options = {})
42
- # Integer(value)
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
43
51
  # end
44
- def call(_value, _options = {})
52
+ def call(value, options = {}) # rubocop:disable Lint/UnusedMethodArgument
45
53
  raise UndefinedCallError, "call method not defined in #{self.class.name}"
46
54
  end
47
55
 
@@ -1,28 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Registry for managing type coercion definitions and execution within tasks.
4
+ # Registry for managing parameter type coercion functionality.
5
5
  #
6
- # This registry handles the registration and execution of coercions that convert
7
- # parameter values from one type to another, supporting both built-in types and
8
- # custom coercion logic.
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.
9
11
  class CoercionRegistry
10
12
 
11
- # The internal hash storing coercion definitions.
12
- #
13
13
  # @return [Hash] hash containing coercion type keys and coercion class/callable values
14
14
  attr_reader :registry
15
15
 
16
- # Initializes a new coercion registry with default type coercions.
16
+ # Creates a new coercion registry with built-in coercion types.
17
17
  #
18
- # Sets up the registry with built-in coercions for standard Ruby types
19
- # including primitives, numerics, dates, and collections.
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.
20
22
  #
21
- # @return [CoercionRegistry] a new coercion registry instance
23
+ # @return [CoercionRegistry] a new registry instance with built-in coercions
22
24
  #
23
- # @example Creating a new registry
25
+ # @example Create a new coercion registry
24
26
  # registry = CoercionRegistry.new
25
- # registry.registry[:string] # => Coercions::String
27
+ # registry.registry.keys
28
+ # #=> [:array, :big_decimal, :boolean, :complex, :date, :datetime, :float, :hash, :integer, :rational, :string, :time, :virtual]
26
29
  def initialize
27
30
  @registry = {
28
31
  array: Coercions::Array,
@@ -41,44 +44,60 @@ module CMDx
41
44
  }
42
45
  end
43
46
 
44
- # Registers a custom coercion for a specific type.
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.
45
53
  #
46
- # @param type [Symbol] the type identifier for the coercion
47
- # @param coercion [Object] the coercion callable (class, proc, symbol, or string)
54
+ # @param type [Symbol] the coercion type identifier to register
55
+ # @param coercion [Class, Proc, Symbol, String] the coercion implementation
48
56
  #
49
- # @return [CoercionRegistry] returns self for method chaining
57
+ # @return [CoercionRegistry] self for method chaining
50
58
  #
51
- # @example Registering a custom coercion class
52
- # registry.register(:uuid, UUIDCoercion)
59
+ # @example Register a custom coercion class
60
+ # registry.register(:temperature, TemperatureCoercion)
53
61
  #
54
- # @example Registering a proc coercion
55
- # registry.register(:upcase, ->(value, options) { value.to_s.upcase })
62
+ # @example Register a coercion proc
63
+ # registry.register(:upcase, proc { |value, options| value.to_s.upcase })
56
64
  #
57
- # @example Chaining registrations
58
- # registry.register(:custom1, MyCoercion).register(:custom2, AnotherCoercion)
65
+ # @example Register a method symbol
66
+ # registry.register(:custom_parse, :parse_custom_format)
59
67
  def register(type, coercion)
60
68
  registry[type] = coercion
61
69
  self
62
70
  end
63
71
 
64
- # Executes a coercion for the specified type and value.
72
+ # Executes a coercion by type on the provided value.
65
73
  #
66
- # @param task [Task] the task instance executing the coercion
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
67
80
  # @param type [Symbol] the coercion type to execute
68
81
  # @param value [Object] the value to be coerced
69
- # @param options [Hash] additional options for the coercion
82
+ # @param options [Hash] additional options passed to the coercion
83
+ # @option options [Object] any any additional configuration for the coercion
70
84
  #
71
85
  # @return [Object] the coerced value
72
86
  #
73
- # @raise [UnknownCoercionError] when the coercion type is not registered
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
74
93
  #
75
- # @example Coercing a string to integer
76
- # registry.call(task, :integer, "42")
77
- # # => 42
94
+ # @example Execute with options
95
+ # registry.call(task, :date, "2024-01-15", format: "%Y-%m-%d")
96
+ # #=> #<Date: 2024-01-15>
78
97
  #
79
- # @example Coercing with options
80
- # registry.call(task, :array, "a,b,c", delimiter: ",")
81
- # # => ["a", "b", "c"]
98
+ # @example Handle unknown coercion type
99
+ # registry.call(task, :unknown_type, "value")
100
+ # #=> raises UnknownCoercionError
82
101
  def call(task, type, value, options = {})
83
102
  raise UnknownCoercionError, "unknown coercion #{type}" unless registry.key?(type)
84
103
 
@@ -3,7 +3,15 @@
3
3
  module CMDx
4
4
 
5
5
  # Global configuration class for CMDx framework settings.
6
- # Manages logging, middleware, callbacks, coercions, validators, and halt conditions.
6
+ #
7
+ # Manages logging, middleware, callbacks, coercions, validators, and halt conditions
8
+ # for the entire CMDx framework. The Configuration class provides centralized control
9
+ # over framework behavior including task execution flow, error handling, and component
10
+ # registration. All settings configured here become defaults for tasks and workflows
11
+ # unless explicitly overridden at the task or workflow level.
12
+ #
13
+ # The configuration system supports both global and per-task customization, allowing
14
+ # fine-grained control over framework behavior while maintaining sensible defaults.
7
15
  class Configuration
8
16
 
9
17
  DEFAULT_HALT = "failed"
@@ -29,13 +37,18 @@ module CMDx
29
37
  # @return [String, Array<String>] Result statuses that halt workflow execution
30
38
  attr_accessor :workflow_halt
31
39
 
32
- # Initialize a new Configuration instance with default settings.
40
+ # Creates a new configuration instance with default settings.
41
+ #
42
+ # Initializes all configuration attributes with sensible defaults including
43
+ # a stdout logger with line formatting, empty registries for extensibility
44
+ # components, and default halt conditions for both tasks and workflows.
33
45
  #
34
- # @example
35
- # config = CMDx::Configuration.new
36
- # config.logger.level = Logger::DEBUG
46
+ # @return [Configuration] a new configuration instance with default settings
37
47
  #
38
- # @return [Configuration] A new configuration instance
48
+ # @example Create a new configuration
49
+ # config = Configuration.new
50
+ # config.logger.class #=> Logger
51
+ # config.task_halt #=> "failed"
39
52
  def initialize
40
53
  @logger = ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
41
54
  @middlewares = MiddlewareRegistry.new
@@ -46,14 +59,26 @@ module CMDx
46
59
  @workflow_halt = DEFAULT_HALT
47
60
  end
48
61
 
49
- # Convert the configuration to a hash representation.
62
+ # Converts the configuration to a hash representation.
50
63
  #
51
- # @example
52
- # config = CMDx::Configuration.new
53
- # hash = config.to_h
54
- # puts hash[:task_halt] # => "failed"
64
+ # Creates a hash containing all configuration attributes for serialization,
65
+ # inspection, or transfer between processes. The hash includes all registries
66
+ # and settings in their current state.
67
+ #
68
+ # @return [Hash] hash representation of the configuration
69
+ # @option return [Logger] :logger the configured logger instance
70
+ # @option return [MiddlewareRegistry] :middlewares the middleware registry
71
+ # @option return [CallbackRegistry] :callbacks the callback registry
72
+ # @option return [CoercionRegistry] :coercions the coercion registry
73
+ # @option return [ValidatorRegistry] :validators the validator registry
74
+ # @option return [String, Array<String>] :task_halt the task halt configuration
75
+ # @option return [String, Array<String>] :workflow_halt the workflow halt configuration
55
76
  #
56
- # @return [Hash] Hash containing all configuration values
77
+ # @example Convert configuration to hash
78
+ # config = Configuration.new
79
+ # hash = config.to_h
80
+ # hash[:logger].class #=> Logger
81
+ # hash[:task_halt] #=> "failed"
57
82
  def to_h
58
83
  {
59
84
  logger: @logger,
@@ -70,33 +95,49 @@ module CMDx
70
95
 
71
96
  module_function
72
97
 
73
- # Get the current global configuration instance.
74
- # Creates a new configuration if none exists.
98
+ # Returns the current global configuration instance.
75
99
  #
76
- # @example
77
- # config = CMDx.configuration
78
- # config.logger.level = Logger::INFO
100
+ # Provides access to the singleton configuration instance used by the entire
101
+ # CMDx framework. Creates a new configuration with default settings if none
102
+ # exists. This method is thread-safe and ensures only one configuration
103
+ # instance exists per process.
104
+ #
105
+ # @return [Configuration] the current global configuration instance
79
106
  #
80
- # @return [Configuration] The global configuration instance
107
+ # @example Access global configuration
108
+ # config = CMDx.configuration
109
+ # config.logger.level = Logger::DEBUG
110
+ # config.task_halt = ["failed", "skipped"]
81
111
  def configuration
82
112
  return @configuration if @configuration
83
113
 
84
114
  @configuration ||= Configuration.new
85
115
  end
86
116
 
87
- # Configure the global CMDx settings using a block.
117
+ # Configures the global CMDx settings using a block.
88
118
  #
89
- # @example
90
- # CMDx.configure do |config|
91
- # config.task_halt = ["failed", "error"]
92
- # config.logger.level = Logger::DEBUG
93
- # end
119
+ # Yields the current configuration instance to the provided block for
120
+ # modification. This is the recommended way to configure CMDx as it
121
+ # provides a clean DSL-like interface for setting up the framework.
94
122
  #
95
- # @yield [Configuration] The configuration instance
123
+ # @param block [Proc] configuration block that receives the configuration instance
96
124
  #
97
- # @return [Configuration] The configured instance
125
+ # @return [Configuration] the configured configuration instance
98
126
  #
99
- # @raise [ArgumentError] If no block is provided
127
+ # @raise [ArgumentError] if no block is provided
128
+ #
129
+ # @example Configure CMDx settings
130
+ # CMDx.configure do |config|
131
+ # config.logger.level = Logger::INFO
132
+ # config.task_halt = ["failed", "skipped"]
133
+ # config.middlewares.register(CMDx::Middlewares::Timeout.new(seconds: 30))
134
+ # end
135
+ #
136
+ # @example Configure with custom logger
137
+ # CMDx.configure do |config|
138
+ # config.logger = Rails.logger
139
+ # config.logger.formatter = CMDx::LogFormatters::JSON.new
140
+ # end
100
141
  def configure
101
142
  raise ArgumentError, "block required" unless block_given?
102
143
 
@@ -105,13 +146,25 @@ module CMDx
105
146
  config
106
147
  end
107
148
 
108
- # Reset the global configuration to default values.
149
+ # Resets the global configuration to default settings.
150
+ #
151
+ # Creates a new configuration instance with default settings, discarding
152
+ # any existing configuration. This is useful for testing scenarios or
153
+ # when you need to start with a clean configuration state.
154
+ #
155
+ # @return [Configuration] a new configuration instance with default settings
156
+ #
157
+ # @example Reset to defaults
158
+ # CMDx.configure { |c| c.task_halt = ["failed", "skipped"] }
159
+ # CMDx.configuration.task_halt #=> ["failed", "skipped"]
109
160
  #
110
- # @example
111
161
  # CMDx.reset_configuration!
112
- # CMDx.configuration.task_halt # => "failed"
162
+ # CMDx.configuration.task_halt #=> "failed"
113
163
  #
114
- # @return [Configuration] A new configuration instance with defaults
164
+ # @example Use in test setup
165
+ # RSpec.configure do |config|
166
+ # config.before(:each) { CMDx.reset_configuration! }
167
+ # end
115
168
  def reset_configuration!
116
169
  @configuration = Configuration.new
117
170
  end
data/lib/cmdx/context.rb CHANGED
@@ -1,38 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Parameter and data context for task execution.
4
+ # Execution context container for task parameter storage and access.
5
5
  #
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.
6
+ # Context provides normalized parameter storage for task execution, inheriting
7
+ # from LazyStruct to provide flexible attribute access patterns. It serves as
8
+ # the primary interface for storing and retrieving execution parameters, allowing
9
+ # both hash-style and method-style attribute access with automatic key normalization.
10
+ # Context instances are used throughout task execution to maintain parameter state
11
+ # and provide consistent data access across the task lifecycle.
10
12
  class Context < LazyStruct
11
13
 
12
- # Creates or returns a context instance from the given input.
14
+ # Creates or returns a Context instance from the provided input.
13
15
  #
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.
16
+ # This factory method normalizes various input types into a proper Context instance,
17
+ # ensuring consistent context handling throughout the framework. If the input is
18
+ # already a Context instance and not frozen, it returns the input unchanged to
19
+ # avoid unnecessary object creation. Otherwise, it creates a new Context instance
20
+ # with the provided data.
17
21
  #
18
- # @param context [Hash, Context, Object] input data to build context from
22
+ # @param context [Hash, Context, Object] input data to convert to Context
23
+ # @option context [Object] any any attribute keys and values for context initialization
19
24
  #
20
25
  # @return [Context] a Context instance containing the provided data
21
26
  #
22
- # @raise [ArgumentError] if the input doesn't respond to to_h
27
+ # @example Create context from hash
28
+ # context = Context.build(user_id: 123, action: "process")
29
+ # context.user_id #=> 123
30
+ # context.action #=> "process"
23
31
  #
24
- # @example Build context from hash
25
- # Context.build(name: "John", age: 30)
26
- # # => #<CMDx::Context :name="John" :age=30>
32
+ # @example Return existing unfrozen context
33
+ # existing = Context.new(status: "active")
34
+ # result = Context.build(existing)
35
+ # result.equal?(existing) #=> true
27
36
  #
28
- # @example Build context from existing context
29
- # existing = Context.build(user_id: 123)
30
- # Context.build(existing)
31
- # # => returns existing context unchanged
37
+ # @example Create new context from frozen context
38
+ # frozen_context = Context.new(data: "test").freeze
39
+ # new_context = Context.build(frozen_context)
40
+ # new_context.equal?(frozen_context) #=> false
41
+ # new_context.data #=> "test"
32
42
  #
33
- # @example Build context from hash-like object
34
- # Context.build(OpenStruct.new(status: "active"))
35
- # # => #<CMDx::Context :status="active">
43
+ # @example Create context from empty input
44
+ # context = Context.build
45
+ # context.class #=> CMDx::Context
46
+ # context.to_h #=> {}
36
47
  def self.build(context = {})
37
48
  return context if context.is_a?(self) && !context.frozen?
38
49
 
@@ -16,13 +16,13 @@ module CMDx
16
16
  #
17
17
  # @example Fetch with symbol key
18
18
  # hash = { name: "John", "age" => 30 }
19
- # hash.cmdx_fetch(:name) # => "John"
20
- # hash.cmdx_fetch(:age) # => 30
19
+ # hash.cmdx_fetch(:name) #=> "John"
20
+ # hash.cmdx_fetch(:age) #=> 30
21
21
  #
22
22
  # @example Fetch with string key
23
23
  # hash = { name: "John", "age" => 30 }
24
- # hash.cmdx_fetch("name") # => "John"
25
- # hash.cmdx_fetch("age") # => 30
24
+ # hash.cmdx_fetch("name") #=> "John"
25
+ # hash.cmdx_fetch("age") #=> 30
26
26
  def cmdx_fetch(key)
27
27
  case key
28
28
  when Symbol then fetch(key) { self[key.to_s] }
@@ -40,11 +40,11 @@ module CMDx
40
40
  #
41
41
  # @example Check key existence
42
42
  # hash = { name: "John", "age" => 30 }
43
- # hash.cmdx_key?(:name) # => true
44
- # hash.cmdx_key?("name") # => true
45
- # hash.cmdx_key?(:age) # => true
46
- # hash.cmdx_key?("age") # => true
47
- # hash.cmdx_key?(:missing) # => false
43
+ # hash.cmdx_key?(:name) #=> true
44
+ # hash.cmdx_key?("name") #=> true
45
+ # hash.cmdx_key?(:age) #=> true
46
+ # hash.cmdx_key?("age") #=> true
47
+ # hash.cmdx_key?(:missing) #=> false
48
48
  def cmdx_key?(key)
49
49
  key?(key) || key?(
50
50
  case key
@@ -66,10 +66,10 @@ module CMDx
66
66
  #
67
67
  # @example Check method or key response
68
68
  # hash = { name: "John", "age" => 30 }
69
- # hash.cmdx_respond_to?(:keys) # => true (method exists)
70
- # hash.cmdx_respond_to?(:name) # => true (key exists)
71
- # hash.cmdx_respond_to?("age") # => true (key exists)
72
- # hash.cmdx_respond_to?(:missing) # => false
69
+ # hash.cmdx_respond_to?(:keys) #=> true (method exists)
70
+ # hash.cmdx_respond_to?(:name) #=> true (key exists)
71
+ # hash.cmdx_respond_to?("age") #=> true (key exists)
72
+ # hash.cmdx_respond_to?(:missing) #=> false
73
73
  def cmdx_respond_to?(key, include_private = false)
74
74
  respond_to?(key.to_sym, include_private) || cmdx_key?(key)
75
75
  rescue NoMethodError
@@ -71,7 +71,7 @@ module CMDx
71
71
  # cmdx_attr_setting :timeout, default: 30
72
72
  # end
73
73
  #
74
- # BaseTask.timeout # => 30
74
+ # BaseTask.timeout #=> 30
75
75
  #
76
76
  # @example Define a setting with a dynamic default
77
77
  # class Task
@@ -18,14 +18,14 @@ module CMDx
18
18
  # @return [Object, nil] the result of the method call, proc evaluation, or hash access; nil if not found
19
19
  #
20
20
  # @example Try calling a method
21
- # "hello".cmdx_try(:upcase) # => "HELLO"
22
- # "hello".cmdx_try(:missing) # => nil
21
+ # "hello".cmdx_try(:upcase) #=> "HELLO"
22
+ # "hello".cmdx_try(:missing) #=> nil
23
23
  #
24
24
  # @example Try evaluating a proc
25
- # obj.cmdx_try(-> { self.class.name }) # => "String"
25
+ # obj.cmdx_try(-> { self.class.name }) #=> "String"
26
26
  #
27
27
  # @example Try accessing a hash key
28
- # {name: "John"}.cmdx_try(:name) # => "John"
28
+ # {name: "John"}.cmdx_try(:name) #=> "John"
29
29
  def cmdx_try(key, *args, **kwargs, &)
30
30
  if key.is_a?(Proc)
31
31
  return instance_eval(&key) unless is_a?(Module) || key.inspect.include?("(lambda)")
@@ -53,13 +53,13 @@ module CMDx
53
53
  # @return [Boolean] true if conditions are met, false otherwise
54
54
  #
55
55
  # @example Evaluate with if condition
56
- # user.cmdx_eval(if: :active?) # => true if user.active? is truthy
56
+ # user.cmdx_eval(if: :active?) #=> true if user.active? is truthy
57
57
  #
58
58
  # @example Evaluate with unless condition
59
- # user.cmdx_eval(unless: :banned?) # => true if user.banned? is falsy
59
+ # user.cmdx_eval(unless: :banned?) #=> true if user.banned? is falsy
60
60
  #
61
61
  # @example Evaluate with both conditions
62
- # user.cmdx_eval(if: :active?, unless: :banned?) # => true if active and not banned
62
+ # user.cmdx_eval(if: :active?, unless: :banned?) #=> true if active and not banned
63
63
  def cmdx_eval(options = {})
64
64
  if options[:if] && options[:unless]
65
65
  cmdx_try(options[:if]) && !cmdx_try(options[:unless])
@@ -81,13 +81,13 @@ module CMDx
81
81
  # @return [Object] the result of method call, proc evaluation, or the value itself
82
82
  #
83
83
  # @example Yield a method call
84
- # "hello".cmdx_yield(:upcase) # => "HELLO"
84
+ # "hello".cmdx_yield(:upcase) #=> "HELLO"
85
85
  #
86
86
  # @example Yield a static value
87
- # obj.cmdx_yield("static") # => "static"
87
+ # obj.cmdx_yield("static") #=> "static"
88
88
  #
89
89
  # @example Yield a proc
90
- # obj.cmdx_yield(-> { Time.now }) # => 2023-01-01 12:00:00 UTC
90
+ # obj.cmdx_yield(-> { Time.now }) #=> 2023-01-01 12:00:00 UTC
91
91
  def cmdx_yield(key, ...)
92
92
  if key.is_a?(Symbol) || key.is_a?(String)
93
93
  return key unless respond_to?(key, true)
@@ -108,10 +108,10 @@ module CMDx
108
108
  # @return [Object] the result of calling the object, or the object itself if not callable
109
109
  #
110
110
  # @example Invoke a proc
111
- # proc { "hello" }.cmdx_call # => "hello"
111
+ # proc { "hello" }.cmdx_call #=> "hello"
112
112
  #
113
113
  # @example Invoke a non-callable object
114
- # "hello".cmdx_call # => "hello"
114
+ # "hello".cmdx_call #=> "hello"
115
115
  def cmdx_call(...)
116
116
  return self unless respond_to?(:call)
117
117