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/fault.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Exception class for task execution faults with result context.
4
+ # Base fault class for handling task execution failures and interruptions.
5
5
  #
6
- # Fault provides a specialized exception that carries task execution context
7
- # including the failed result, task instance, and execution chain. It serves
8
- # as the base class for specific fault types and provides factory methods
9
- # for creating fault instances and conditional fault matchers.
6
+ # Faults are exceptions raised when tasks encounter specific execution states
7
+ # that prevent normal completion. Unlike regular exceptions, faults carry
8
+ # rich context information including the task result, execution chain, and
9
+ # contextual data that led to the fault condition. Faults can be caught and
10
+ # handled based on specific task types or custom matching criteria.
10
11
  class Fault < Error
11
12
 
12
13
  cmdx_attr_delegator :task, :chain, :context,
@@ -15,20 +16,16 @@ module CMDx
15
16
  # @return [CMDx::Result] the result object that caused this fault
16
17
  attr_reader :result
17
18
 
18
- # Creates a new fault instance with the given result context.
19
+ # Creates a new fault instance from a task execution result.
19
20
  #
20
- # The fault message is derived from the result's metadata reason or falls
21
- # back to a default internationalized message if no reason is provided.
21
+ # @param result [CMDx::Result] the task result that caused the fault
22
22
  #
23
- # @param result [CMDx::Result] the failed task result that caused this fault
23
+ # @return [CMDx::Fault] the newly created fault instance
24
24
  #
25
- # @return [Fault] the newly created fault instance
26
- #
27
- # @example Create a fault from a failed result
28
- # result = CMDx::Result.new(task)
29
- # result.fail!(reason: "Database connection failed")
25
+ # @example Create fault from failed task result
26
+ # result = SomeTask.call(invalid_data: true)
30
27
  # fault = CMDx::Fault.new(result)
31
- # fault.message # => "Database connection failed"
28
+ # fault.task #=> SomeTask instance
32
29
  def initialize(result)
33
30
  @result = result
34
31
  super(result.metadata[:reason] || I18n.t("cmdx.faults.unspecified", default: "no reason given"))
@@ -36,23 +33,27 @@ module CMDx
36
33
 
37
34
  class << self
38
35
 
39
- # Builds a fault instance based on the result's status.
36
+ # Builds a specific fault type based on the result's status.
40
37
  #
41
- # Creates a specific fault subclass by capitalizing the result status
42
- # and looking up the corresponding fault class constant. This allows
43
- # for status-specific fault types like Failed, Skipped, etc.
38
+ # Creates an instance of the appropriate fault subclass (Skipped, Failed, etc.)
39
+ # by capitalizing the result status and looking up the corresponding fault class.
40
+ # This provides dynamic fault creation based on task execution outcomes.
44
41
  #
45
- # @param result [CMDx::Result] the failed task result
42
+ # @param result [CMDx::Result] the task result to build a fault from
46
43
  #
47
- # @return [Fault] a fault instance of the appropriate subclass
44
+ # @return [CMDx::Fault] an instance of the appropriate fault subclass
48
45
  #
49
46
  # @raise [NameError] if no fault class exists for the result status
50
47
  #
51
- # @example Build a fault for a failed result
52
- # result = CMDx::Result.new(task)
53
- # result.fail!
48
+ # @example Build fault from skipped task result
49
+ # result = SomeTask.call # result.status is :skipped
50
+ # fault = CMDx::Fault.build(result)
51
+ # fault.class #=> CMDx::Skipped
52
+ #
53
+ # @example Build fault from failed task result
54
+ # result = SomeTask.call # result.status is :failed
54
55
  # fault = CMDx::Fault.build(result)
55
- # fault.class # => CMDx::Failed
56
+ # fault.class #=> CMDx::Failed
56
57
  def build(result)
57
58
  fault = CMDx.const_get(result.status.capitalize)
58
59
  fault.new(result)
@@ -60,19 +61,28 @@ module CMDx
60
61
 
61
62
  # Creates a fault matcher that matches faults from specific task classes.
62
63
  #
63
- # Returns a temporary fault class that can be used in rescue clauses
64
- # to catch faults only from the specified task types. The matcher uses
65
- # the === operator to check if the fault's task is an instance of any
66
- # of the given task classes.
64
+ # Returns a dynamically created fault class that can be used in rescue blocks
65
+ # to catch faults only when they originate from specific task types. This enables
66
+ # selective fault handling based on the task that generated the fault.
67
67
  #
68
- # @param tasks [Array<Class>] task classes to match against
68
+ # @param tasks [Array<Class>] one or more task classes to match against
69
69
  #
70
- # @return [Class] a temporary fault class that matches the specified tasks
70
+ # @return [Class] a fault matcher class that responds to case equality
71
71
  #
72
- # @example Match faults from specific task classes
73
- # rescue CMDx::Fault.for?(UserCreateTask, UserUpdateTask) => fault
74
- # # Handle faults only from user-related tasks
75
- # logger.error "User operation failed: #{fault.message}"
72
+ # @example Catch faults from specific task types
73
+ # begin
74
+ # PaymentTask.call!
75
+ # rescue CMDx::Fault.for?(PaymentTask, RefundTask) => e
76
+ # puts "Payment operation failed: #{e.message}"
77
+ # end
78
+ #
79
+ # @example Match faults from multiple task types
80
+ # UserTaskFaults = CMDx::Fault.for?(CreateUserTask, UpdateUserTask, DeleteUserTask)
81
+ #
82
+ # begin
83
+ # workflow.call!
84
+ # rescue CMDx::Fault.for?(CreateUserTask, UpdateUserTask, DeleteUserTask) => e
85
+ # handle_user_operation_failure(e)
76
86
  # end
77
87
  def for?(*tasks)
78
88
  temp_fault = Class.new(self) do
@@ -84,22 +94,33 @@ module CMDx
84
94
  temp_fault.tap { |c| c.instance_variable_set(:@tasks, tasks) }
85
95
  end
86
96
 
87
- # Creates a fault matcher that matches faults based on a custom condition.
97
+ # Creates a fault matcher using a custom block for matching criteria.
88
98
  #
89
- # Returns a temporary fault class that can be used in rescue clauses
90
- # to catch faults that satisfy the given block condition. The matcher
91
- # uses the === operator to evaluate the block against the fault instance.
99
+ # Returns a dynamically created fault class that uses the provided block
100
+ # to determine if a fault should be matched. The block receives the fault
101
+ # instance and should return true if the fault matches the desired criteria.
102
+ # This enables custom fault handling logic beyond simple task type matching.
92
103
  #
93
- # @param block [Proc] the condition block to evaluate against fault instances
104
+ # @param block [Proc] a block that receives a fault and returns boolean
94
105
  #
95
- # @return [Class] a temporary fault class that matches the block condition
106
+ # @return [Class] a fault matcher class that responds to case equality
96
107
  #
97
108
  # @raise [ArgumentError] if no block is provided
98
109
  #
99
- # @example Match faults based on custom condition
100
- # rescue CMDx::Fault.matches? { |f| f.task.context.user_id == current_user.id } => fault
101
- # # Handle faults only for current user's operations
102
- # notify_user_of_failure(fault)
110
+ # @example Match faults by custom criteria
111
+ # begin
112
+ # LongRunningTask.call!
113
+ # rescue CMDx::Fault.matches? { |fault| fault.context[:timeout_exceeded] } => e
114
+ # puts "Task timed out: #{e.message}"
115
+ # end
116
+ #
117
+ # @example Match faults by metadata content
118
+ # ValidationFault = CMDx::Fault.matches? { |fault| fault.result.metadata[:type] == "validation_error" }
119
+ #
120
+ # begin
121
+ # ValidateUserTask.call!
122
+ # rescue ValidationFault => e
123
+ # display_validation_errors(e.result.errors)
103
124
  # end
104
125
  def matches?(&block)
105
126
  raise ArgumentError, "block required" unless block_given?
@@ -1,39 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Freezes task objects after execution to ensure immutability.
4
+ # Provides object immutability functionality for tasks and their associated objects.
5
5
  #
6
- # This module provides the final step in the task lifecycle by freezing
7
- # task instances and their associated objects to prevent further modification.
8
- # The freezing behavior can be controlled via environment variables and
9
- # is conditionally applied based on the task's position in the execution chain.
6
+ # This module freezes task objects and their related components after execution
7
+ # to prevent unintended modifications. It supports conditional freezing through
8
+ # environment variable configuration, allowing developers to disable immutability
9
+ # during testing scenarios where object stubbing is required.
10
10
  module Immutator
11
11
 
12
12
  module_function
13
13
 
14
- # Freezes task objects after execution to make them immutable.
14
+ # Freezes a task and its associated objects to prevent further modification.
15
15
  #
16
- # Always freezes the task and its result. For the first task in a chain
17
- # (index 0), also freezes the context and chain, then clears the chain.
18
- # Freezing can be skipped entirely by setting the SKIP_CMDX_FREEZING
19
- # environment variable to a truthy value.
16
+ # This method makes the task, its result, and related objects immutable after
17
+ # execution. If the task result index is zero (indicating the first task in a chain),
18
+ # it also freezes the context and chain objects. The freezing behavior can be
19
+ # disabled via the SKIP_CMDX_FREEZING environment variable for testing purposes.
20
20
  #
21
- # @param task [Task] the task instance to freeze after execution
21
+ # @param task [CMDx::Task] the task instance to freeze along with its associated objects
22
22
  #
23
- # @return [nil] always returns nil
23
+ # @return [void] returns nil when freezing is skipped, otherwise no meaningful return value
24
24
  #
25
- # @raise [StandardError] if any freeze operation fails
26
- #
27
- # @example Freeze a completed task
28
- # task = MyTask.new(user_id: 123)
29
- # task.process
25
+ # @example Freeze a task after execution
26
+ # task = MyTask.call(user_id: 123)
30
27
  # CMDx::Immutator.call(task)
31
- # task.frozen? # => true
28
+ # task.frozen? #=> true
29
+ # task.result.frozen? #=> true
32
30
  #
33
- # @example Skip freezing for testing
34
- # ENV["SKIP_CMDX_FREEZING"] = "1"
31
+ # @example Skip freezing during testing
32
+ # ENV["SKIP_CMDX_FREEZING"] = "true"
33
+ # task = MyTask.call(user_id: 123)
35
34
  # CMDx::Immutator.call(task)
36
- # task.frozen? # => false
35
+ # task.frozen? #=> false
37
36
  def call(task)
38
37
  # Stubbing on frozen objects is not allowed
39
38
  skip_freezing = ENV.fetch("SKIP_CMDX_FREEZING", false)
@@ -1,29 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Hash-like data structure with dynamic attribute access and automatic key normalization.
4
+ # Flexible struct-like object with symbol-based attribute access and dynamic assignment.
5
5
  #
6
- # LazyStruct provides a flexible data container that combines hash-like access patterns
7
- # with dynamic method calls. All keys are automatically converted to symbols for
8
- # consistent access, and the structure supports both bracket notation and method-style
9
- # attribute access through method_missing.
6
+ # LazyStruct provides a hash-like object that automatically converts string keys to symbols
7
+ # and supports both hash-style and method-style attribute access. It's designed for
8
+ # storing and accessing dynamic attributes with lazy evaluation and flexible assignment patterns.
10
9
  class LazyStruct
11
10
 
12
- # Creates a new LazyStruct instance from the provided arguments.
11
+ # Creates a new LazyStruct instance with the provided attributes.
13
12
  #
14
- # @param args [Hash, #to_h] initial data for the structure, must respond to to_h
13
+ # @param args [Hash, #to_h] initial attributes for the struct
15
14
  #
16
15
  # @return [LazyStruct] a new LazyStruct instance
17
16
  #
18
17
  # @raise [ArgumentError] if args doesn't respond to to_h
19
18
  #
20
- # @example Create with hash data
19
+ # @example Create with hash attributes
21
20
  # struct = LazyStruct.new(name: "John", age: 30)
22
- # struct.name # => "John"
21
+ # struct.name #=> "John"
23
22
  #
24
23
  # @example Create with hash-like object
25
24
  # struct = LazyStruct.new(OpenStruct.new(status: "active"))
26
- # struct.status # => "active"
25
+ # struct.status #=> "active"
27
26
  def initialize(args = {})
28
27
  unless args.respond_to?(:to_h)
29
28
  raise ArgumentError,
@@ -35,34 +34,36 @@ module CMDx
35
34
 
36
35
  # Retrieves the value for the specified key.
37
36
  #
38
- # @param key [Symbol, String] the key to retrieve
37
+ # @param key [Symbol, String] the key to look up
39
38
  #
40
39
  # @return [Object, nil] the value associated with the key, or nil if not found
41
40
  #
42
- # @example Access existing key
41
+ # @example Access attribute by symbol
43
42
  # struct = LazyStruct.new(name: "John")
44
- # struct[:name] # => "John"
45
- # struct["name"] # => "John"
43
+ # struct[:name] #=> "John"
44
+ #
45
+ # @example Access attribute by string
46
+ # struct[:name] #=> "John"
47
+ # struct["name"] #=> "John"
46
48
  def [](key)
47
49
  table[symbolized_key(key)]
48
50
  end
49
51
 
50
- # Fetches the value for the specified key with optional default handling.
52
+ # Retrieves the value for the specified key or returns/yields a default.
51
53
  #
52
- # @param key [Symbol, String] the key to fetch
53
- # @param args [Array] optional default value or block arguments
54
+ # @param key [Symbol, String] the key to look up
55
+ # @param args [Array] additional arguments passed to Hash#fetch
54
56
  #
55
- # @return [Object] the value associated with the key, or default if not found
57
+ # @return [Object] the value associated with the key, or default value
56
58
  #
57
59
  # @raise [KeyError] if key is not found and no default is provided
58
60
  #
59
61
  # @example Fetch with default value
60
62
  # struct = LazyStruct.new(name: "John")
61
- # struct.fetch!(:name) # => "John"
62
- # struct.fetch!(:missing, "default") # => "default"
63
+ # struct.fetch!(:age, 25) #=> 25
63
64
  #
64
- # @example Fetch with block
65
- # struct.fetch!(:missing) { "computed default" } # => "computed default"
65
+ # @example Fetch with block default
66
+ # struct.fetch!(:missing) { "default" } #=> "default"
66
67
  def fetch!(key, ...)
67
68
  table.fetch(symbolized_key(key), ...)
68
69
  end
@@ -76,107 +77,115 @@ module CMDx
76
77
  #
77
78
  # @example Store a value
78
79
  # struct = LazyStruct.new
79
- # struct.store!(:name, "John") # => "John"
80
- # struct[:name] # => "John"
80
+ # struct.store!(:name, "John") #=> "John"
81
+ # struct.name #=> "John"
81
82
  def store!(key, value)
82
83
  table[symbolized_key(key)] = value
83
84
  end
84
85
  alias []= store!
85
86
 
86
- # Merges the provided arguments into the current structure.
87
+ # Merges the provided arguments into the struct's attributes.
87
88
  #
88
- # @param args [Hash, #to_h] the data to merge, must respond to to_h
89
+ # @param args [Hash, #to_h] attributes to merge into the struct
89
90
  #
90
- # @return [LazyStruct] returns self for method chaining
91
+ # @return [LazyStruct] self for method chaining
91
92
  #
92
- # @example Merge additional data
93
+ # @example Merge attributes
93
94
  # struct = LazyStruct.new(name: "John")
94
95
  # struct.merge!(age: 30, city: "NYC")
95
- # struct.age # => 30
96
+ # struct.age #=> 30
96
97
  def merge!(args = {})
97
98
  args.to_h.each { |key, value| store!(symbolized_key(key), value) }
98
99
  self
99
100
  end
100
101
 
101
- # Deletes the specified key from the structure.
102
+ # Deletes the specified key from the struct.
102
103
  #
103
104
  # @param key [Symbol, String] the key to delete
104
- # @param block [Proc] optional block to execute if key is not found
105
+ # @param block [Proc] optional block to yield if key is not found
105
106
  #
106
107
  # @return [Object, nil] the deleted value, or result of block if key not found
107
108
  #
108
- # @example Delete a key
109
+ # @example Delete an attribute
109
110
  # struct = LazyStruct.new(name: "John", age: 30)
110
- # struct.delete!(:age) # => 30
111
- # struct.age # => nil
111
+ # struct.delete!(:age) #=> 30
112
+ # struct.age #=> nil
113
+ #
114
+ # @example Delete with default block
115
+ # struct.delete!(:missing) { "not found" } #=> "not found"
112
116
  def delete!(key, &)
113
117
  table.delete(symbolized_key(key), &)
114
118
  end
115
119
  alias delete_field! delete!
116
120
 
117
- # Checks equality with another LazyStruct instance.
121
+ # Checks equality with another object.
118
122
  #
119
- # @param other [Object] the object to compare with
123
+ # @param other [Object] the object to compare against
120
124
  #
121
- # @return [Boolean] true if both objects are LazyStruct instances with the same data
125
+ # @return [Boolean] true if other is a LazyStruct with identical attributes
122
126
  #
123
- # @example Compare structures
127
+ # @example Compare structs
124
128
  # struct1 = LazyStruct.new(name: "John")
125
129
  # struct2 = LazyStruct.new(name: "John")
126
- # struct1.eql?(struct2) # => true
130
+ # struct1.eql?(struct2) #=> true
127
131
  def eql?(other)
128
132
  other.is_a?(self.class) && (to_h == other.to_h)
129
133
  end
130
134
  alias == eql?
131
135
 
132
- # Extracts nested values using the specified key path.
136
+ # Extracts nested values using key path traversal.
133
137
  #
134
- # @param key [Symbol, String] the first key in the path
135
- # @param keys [Array] additional keys for nested access
138
+ # @param key [Symbol, String] the initial key to look up
139
+ # @param keys [Array<Symbol, String>] additional keys for nested traversal
136
140
  #
137
- # @return [Object, nil] the value at the specified path, or nil if not found
141
+ # @return [Object, nil] the nested value, or nil if any key in the path is missing
138
142
  #
139
143
  # @example Dig into nested structure
140
144
  # struct = LazyStruct.new(user: { profile: { name: "John" } })
141
- # struct.dig(:user, :profile, :name) # => "John"
145
+ # struct.dig(:user, :profile, :name) #=> "John"
146
+ # struct.dig(:user, :missing, :name) #=> nil
142
147
  def dig(key, *keys)
143
148
  table.dig(symbolized_key(key), *keys)
144
149
  end
145
150
 
146
- # Iterates over each key-value pair in the structure.
151
+ # Iterates over each key-value pair in the struct.
147
152
  #
148
- # @param block [Proc] the block to execute for each pair
153
+ # @param block [Proc] the block to execute for each key-value pair
149
154
  #
150
- # @return [LazyStruct] returns self if block given, otherwise returns enumerator
155
+ # @return [Enumerator, LazyStruct] an enumerator if no block given, self otherwise
151
156
  #
152
157
  # @example Iterate over pairs
153
158
  # struct = LazyStruct.new(name: "John", age: 30)
154
159
  # struct.each_pair { |key, value| puts "#{key}: #{value}" }
155
- # # Output: name: John, age: 30
160
+ # # Output: name: John
161
+ # # age: 30
156
162
  def each_pair(&)
157
163
  table.each_pair(&)
158
164
  end
159
165
 
160
- # Converts the structure to a hash representation.
166
+ # Converts the struct to a hash representation.
161
167
  #
162
- # @param block [Proc] optional block for hash transformation
168
+ # @param block [Proc] optional block for transforming key-value pairs
163
169
  #
164
- # @return [Hash] hash representation of the structure
170
+ # @return [Hash] a hash containing all the struct's attributes
165
171
  #
166
172
  # @example Convert to hash
167
173
  # struct = LazyStruct.new(name: "John", age: 30)
168
- # struct.to_h # => {:name=>"John", :age=>30}
174
+ # struct.to_h #=> { name: "John", age: 30 }
175
+ #
176
+ # @example Convert with transformation
177
+ # struct.to_h { |k, v| [k.to_s, v.to_s] } #=> { "name" => "John", "age" => "30" }
169
178
  def to_h(&)
170
179
  table.to_h(&)
171
180
  end
172
181
 
173
- # Returns a string representation of the structure.
182
+ # Returns a string representation of the struct for debugging.
174
183
  #
175
- # @return [String] formatted string showing class name and key-value pairs
184
+ # @return [String] a formatted string showing the class name and attributes
176
185
  #
177
- # @example Inspect structure
186
+ # @example Inspect struct
178
187
  # struct = LazyStruct.new(name: "John", age: 30)
179
- # struct.inspect # => "#<CMDx::LazyStruct :name=\"John\" :age=30>"
188
+ # struct.inspect #=> "#<CMDx::LazyStruct :name=\"John\" :age=30>"
180
189
  def inspect
181
190
  "#<#{self.class.name}#{table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
182
191
  end
@@ -184,50 +193,49 @@ module CMDx
184
193
 
185
194
  private
186
195
 
187
- # Returns the internal hash table, initializing it if needed.
196
+ # Returns the internal hash table storing the struct's attributes.
188
197
  #
189
- # @return [Hash] the internal hash storage
198
+ # @return [Hash] the internal attribute storage
190
199
  def table
191
200
  @table ||= {}
192
201
  end
193
202
 
194
- # Provides dynamic method access to stored values and assignment.
203
+ # Handles dynamic method calls for attribute access and assignment.
195
204
  #
196
205
  # @param method_name [Symbol] the method name being called
197
- # @param args [Array] method arguments
206
+ # @param args [Array] arguments passed to the method
198
207
  # @param _kwargs [Hash] keyword arguments (unused)
199
- # @param block [Proc] optional block (unused)
208
+ # @param block [Proc] block passed to the method (unused)
200
209
  #
201
- # @return [Object] the value for the method name, or result of assignment
210
+ # @return [Object, nil] the attribute value for getters, or the assigned value for setters
202
211
  #
203
- # @example Dynamic method access
212
+ # @example Dynamic attribute access
204
213
  # struct = LazyStruct.new(name: "John")
205
- # struct.name # => "John"
206
- # struct.age = 30
207
- # struct.age # => 30
214
+ # struct.name #=> "John"
215
+ # struct.age = 30 #=> 30
208
216
  def method_missing(method_name, *args, **_kwargs, &)
209
217
  table.fetch(symbolized_key(method_name)) do
210
218
  store!(method_name[0..-2], args.first) if method_name.end_with?("=")
211
219
  end
212
220
  end
213
221
 
214
- # Checks if the structure responds to the specified method name.
222
+ # Checks if the struct responds to a method name.
215
223
  #
216
224
  # @param method_name [Symbol] the method name to check
217
225
  # @param include_private [Boolean] whether to include private methods
218
226
  #
219
- # @return [Boolean] true if method is available or key exists in structure
227
+ # @return [Boolean] true if the struct has the attribute or responds to the method
220
228
  def respond_to_missing?(method_name, include_private = false)
221
229
  table.key?(symbolized_key(method_name)) || super
222
230
  end
223
231
 
224
232
  # Converts a key to a symbol for consistent internal storage.
225
233
  #
226
- # @param key [Object] the key to convert to symbol
234
+ # @param key [Symbol, String, Object] the key to convert
227
235
  #
228
236
  # @return [Symbol] the symbolized key
229
237
  #
230
- # @raise [TypeError] if key cannot be converted to symbol
238
+ # @raise [TypeError] if the key cannot be converted to a symbol
231
239
  def symbolized_key(key)
232
240
  key.to_sym
233
241
  rescue NoMethodError
@@ -23,7 +23,7 @@ module CMDx
23
23
  # @example Formatting a log entry
24
24
  # formatter = CMDx::LogFormatters::Json.new
25
25
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # # => "{\"severity\":\"INFO\",\"pid\":12345,\"timestamp\":\"2024-01-01T12:00:00Z\",\"message\":\"Task completed\"}\n"
26
+ # #=> "{\"severity\":\"INFO\",\"pid\":12345,\"timestamp\":\"2024-01-01T12:00:00Z\",\"message\":\"Task completed\"}\n"
27
27
  def call(severity, time, task, message)
28
28
  m = LoggerSerializer.call(severity, time, task, message).merge!(
29
29
  severity:,
@@ -23,7 +23,7 @@ module CMDx
23
23
  # @example Formatting a log entry
24
24
  # formatter = CMDx::LogFormatters::KeyValue.new
25
25
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # # => "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
26
+ # #=> "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
27
27
  def call(severity, time, task, message)
28
28
  m = LoggerSerializer.call(severity, time, task, message).merge!(
29
29
  severity:,
@@ -23,7 +23,7 @@ module CMDx
23
23
  # @example Formatting a log entry
24
24
  # formatter = CMDx::LogFormatters::Line.new
25
25
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # # => "I, [2024-01-01T12:00:00.000Z #12345] INFO -- TaskClass: Task completed\n"
26
+ # #=> "I, [2024-01-01T12:00:00.000Z #12345] INFO -- TaskClass: Task completed\n"
27
27
  def call(severity, time, task, message)
28
28
  t = Utils::LogTimestamp.call(time.utc)
29
29
  m = LoggerSerializer.call(severity, time, task, message)
@@ -24,7 +24,7 @@ module CMDx
24
24
  # @example Formatting a log entry
25
25
  # formatter = CMDx::LogFormatters::Logstash.new
26
26
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
27
- # # => "{\"severity\":\"INFO\",\"pid\":12345,\"@version\":\"1\",\"@timestamp\":\"2024-01-01T12:00:00.000Z\",\"message\":\"Task completed\"}\n"
27
+ # #=> "{\"severity\":\"INFO\",\"pid\":12345,\"@version\":\"1\",\"@timestamp\":\"2024-01-01T12:00:00.000Z\",\"message\":\"Task completed\"}\n"
28
28
  def call(severity, time, task, message)
29
29
  m = LoggerSerializer.call(severity, time, task, message).merge!(
30
30
  severity:,
@@ -24,7 +24,7 @@ module CMDx
24
24
  # @example Formatting a log entry
25
25
  # formatter = CMDx::LogFormatters::PrettyJson.new
26
26
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
27
- # # => "{\n \"severity\": \"INFO\",\n \"pid\": 12345,\n \"timestamp\": \"2024-01-01T12:00:00Z\",\n \"message\": \"Task completed\"\n}\n"
27
+ # #=> "{\n \"severity\": \"INFO\",\n \"pid\": 12345,\n \"timestamp\": \"2024-01-01T12:00:00Z\",\n \"message\": \"Task completed\"\n}\n"
28
28
  def call(severity, time, task, message)
29
29
  m = LoggerSerializer.call(severity, time, task, message).merge!(
30
30
  severity:,
@@ -21,7 +21,7 @@ module CMDx
21
21
  # @example Formatting a log entry
22
22
  # formatter = CMDx::LogFormatters::PrettyKeyValue.new
23
23
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
24
- # # => "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
24
+ # #=> "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
25
25
  def call(severity, time, task, message)
26
26
  m = LoggerSerializer.call(severity, time, task, message, ansi_colorize: true).merge!(
27
27
  severity:,
@@ -25,7 +25,7 @@ module CMDx
25
25
  # @example Formatting a log entry
26
26
  # formatter = CMDx::LogFormatters::PrettyLine.new
27
27
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
28
- # # => "\e[32mI\e[0m, [2024-01-01T12:00:00.000Z #12345] \e[32mINFO\e[0m -- MyTask: Task completed\n"
28
+ # #=> "\e[32mI\e[0m, [2024-01-01T12:00:00.000Z #12345] \e[32mINFO\e[0m -- MyTask: Task completed\n"
29
29
  def call(severity, time, task, message)
30
30
  i = LoggerAnsi.call(severity[0])
31
31
  s = LoggerAnsi.call(severity)
@@ -22,12 +22,12 @@ module CMDx
22
22
  # @example Formatting a log entry
23
23
  # formatter = CMDx::LogFormatters::Raw.new
24
24
  # result = formatter.call("INFO", Time.now, task_object, "Task completed")
25
- # # => "\"Task completed\"\n"
25
+ # #=> "\"Task completed\"\n"
26
26
  #
27
27
  # @example Formatting a complex object
28
28
  # formatter = CMDx::LogFormatters::Raw.new
29
29
  # result = formatter.call("DEBUG", Time.now, task_object, { status: :success, count: 42 })
30
- # # => "{:status=>:success, :count=>42}\n"
30
+ # #=> "{:status=>:success, :count=>42}\n"
31
31
  def call(_severity, _time, _task, message)
32
32
  message.inspect + "\n" # rubocop:disable Style/StringConcatenation
33
33
  end