cmdx 1.0.0 → 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 (169) 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 +5 -0
  5. data/CHANGELOG.md +101 -49
  6. data/README.md +2 -1
  7. data/docs/ai_prompts.md +10 -0
  8. data/docs/basics/call.md +11 -2
  9. data/docs/basics/chain.md +10 -1
  10. data/docs/basics/context.md +9 -0
  11. data/docs/basics/setup.md +9 -0
  12. data/docs/callbacks.md +14 -37
  13. data/docs/configuration.md +68 -27
  14. data/docs/getting_started.md +11 -0
  15. data/docs/internationalization.md +148 -0
  16. data/docs/interruptions/exceptions.md +10 -1
  17. data/docs/interruptions/faults.md +11 -2
  18. data/docs/interruptions/halt.md +9 -0
  19. data/docs/logging.md +14 -4
  20. data/docs/middlewares.md +53 -43
  21. data/docs/outcomes/result.md +9 -0
  22. data/docs/outcomes/states.md +9 -0
  23. data/docs/outcomes/statuses.md +9 -0
  24. data/docs/parameters/coercions.md +58 -38
  25. data/docs/parameters/defaults.md +10 -1
  26. data/docs/parameters/definitions.md +9 -0
  27. data/docs/parameters/namespacing.md +9 -0
  28. data/docs/parameters/validations.md +8 -67
  29. data/docs/testing.md +22 -13
  30. data/docs/tips_and_tricks.md +9 -0
  31. data/docs/workflows.md +14 -4
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +36 -56
  34. data/lib/cmdx/callback_registry.rb +82 -73
  35. data/lib/cmdx/chain.rb +65 -122
  36. data/lib/cmdx/chain_inspector.rb +22 -115
  37. data/lib/cmdx/chain_serializer.rb +17 -148
  38. data/lib/cmdx/coercion.rb +49 -0
  39. data/lib/cmdx/coercion_registry.rb +94 -0
  40. data/lib/cmdx/coercions/array.rb +18 -36
  41. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  42. data/lib/cmdx/coercions/boolean.rb +21 -40
  43. data/lib/cmdx/coercions/complex.rb +18 -31
  44. data/lib/cmdx/coercions/date.rb +20 -39
  45. data/lib/cmdx/coercions/date_time.rb +22 -39
  46. data/lib/cmdx/coercions/float.rb +19 -32
  47. data/lib/cmdx/coercions/hash.rb +22 -41
  48. data/lib/cmdx/coercions/integer.rb +20 -33
  49. data/lib/cmdx/coercions/rational.rb +20 -32
  50. data/lib/cmdx/coercions/string.rb +23 -31
  51. data/lib/cmdx/coercions/time.rb +24 -40
  52. data/lib/cmdx/coercions/virtual.rb +14 -31
  53. data/lib/cmdx/configuration.rb +57 -171
  54. data/lib/cmdx/context.rb +22 -165
  55. data/lib/cmdx/core_ext/hash.rb +42 -67
  56. data/lib/cmdx/core_ext/module.rb +35 -79
  57. data/lib/cmdx/core_ext/object.rb +63 -98
  58. data/lib/cmdx/correlator.rb +40 -156
  59. data/lib/cmdx/error.rb +37 -202
  60. data/lib/cmdx/errors.rb +165 -202
  61. data/lib/cmdx/fault.rb +55 -158
  62. data/lib/cmdx/faults.rb +26 -137
  63. data/lib/cmdx/immutator.rb +22 -109
  64. data/lib/cmdx/lazy_struct.rb +103 -187
  65. data/lib/cmdx/log_formatters/json.rb +14 -40
  66. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  67. data/lib/cmdx/log_formatters/line.rb +14 -48
  68. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  69. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  70. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  71. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  72. data/lib/cmdx/log_formatters/raw.rb +19 -49
  73. data/lib/cmdx/logger.rb +20 -82
  74. data/lib/cmdx/logger_ansi.rb +18 -75
  75. data/lib/cmdx/logger_serializer.rb +24 -114
  76. data/lib/cmdx/middleware.rb +38 -60
  77. data/lib/cmdx/middleware_registry.rb +81 -77
  78. data/lib/cmdx/middlewares/correlate.rb +41 -226
  79. data/lib/cmdx/middlewares/timeout.rb +46 -185
  80. data/lib/cmdx/parameter.rb +120 -198
  81. data/lib/cmdx/parameter_evaluator.rb +231 -0
  82. data/lib/cmdx/parameter_inspector.rb +25 -56
  83. data/lib/cmdx/parameter_registry.rb +59 -84
  84. data/lib/cmdx/parameter_serializer.rb +23 -74
  85. data/lib/cmdx/railtie.rb +24 -107
  86. data/lib/cmdx/result.rb +254 -260
  87. data/lib/cmdx/result_ansi.rb +19 -85
  88. data/lib/cmdx/result_inspector.rb +27 -68
  89. data/lib/cmdx/result_logger.rb +18 -81
  90. data/lib/cmdx/result_serializer.rb +28 -132
  91. data/lib/cmdx/rspec/matchers.rb +28 -0
  92. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  93. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  94. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  96. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  97. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  98. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  99. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  100. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  101. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  102. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  103. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  104. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  105. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  106. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  107. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  108. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  109. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  110. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  111. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  112. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  113. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  114. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  115. data/lib/cmdx/task.rb +213 -425
  116. data/lib/cmdx/task_deprecator.rb +55 -0
  117. data/lib/cmdx/task_processor.rb +245 -0
  118. data/lib/cmdx/task_serializer.rb +22 -70
  119. data/lib/cmdx/utils/ansi_color.rb +13 -89
  120. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  121. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  122. data/lib/cmdx/utils/name_affix.rb +21 -71
  123. data/lib/cmdx/validator.rb +48 -0
  124. data/lib/cmdx/validator_registry.rb +86 -0
  125. data/lib/cmdx/validators/exclusion.rb +55 -94
  126. data/lib/cmdx/validators/format.rb +31 -85
  127. data/lib/cmdx/validators/inclusion.rb +65 -110
  128. data/lib/cmdx/validators/length.rb +117 -133
  129. data/lib/cmdx/validators/numeric.rb +123 -130
  130. data/lib/cmdx/validators/presence.rb +38 -79
  131. data/lib/cmdx/version.rb +1 -7
  132. data/lib/cmdx/workflow.rb +46 -339
  133. data/lib/cmdx.rb +1 -1
  134. data/lib/generators/cmdx/install_generator.rb +14 -31
  135. data/lib/generators/cmdx/task_generator.rb +39 -55
  136. data/lib/generators/cmdx/templates/install.rb +61 -11
  137. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  138. data/lib/locales/ar.yml +35 -0
  139. data/lib/locales/cs.yml +35 -0
  140. data/lib/locales/da.yml +35 -0
  141. data/lib/locales/de.yml +35 -0
  142. data/lib/locales/el.yml +35 -0
  143. data/lib/locales/en.yml +19 -20
  144. data/lib/locales/es.yml +19 -20
  145. data/lib/locales/fi.yml +35 -0
  146. data/lib/locales/fr.yml +35 -0
  147. data/lib/locales/he.yml +35 -0
  148. data/lib/locales/hi.yml +35 -0
  149. data/lib/locales/it.yml +35 -0
  150. data/lib/locales/ja.yml +35 -0
  151. data/lib/locales/ko.yml +35 -0
  152. data/lib/locales/nl.yml +35 -0
  153. data/lib/locales/no.yml +35 -0
  154. data/lib/locales/pl.yml +35 -0
  155. data/lib/locales/pt.yml +35 -0
  156. data/lib/locales/ru.yml +35 -0
  157. data/lib/locales/sv.yml +35 -0
  158. data/lib/locales/th.yml +35 -0
  159. data/lib/locales/tr.yml +35 -0
  160. data/lib/locales/vi.yml +35 -0
  161. data/lib/locales/zh.yml +35 -0
  162. metadata +57 -8
  163. data/lib/cmdx/parameter_validator.rb +0 -81
  164. data/lib/cmdx/parameter_value.rb +0 -244
  165. data/lib/cmdx/parameters_inspector.rb +0 -72
  166. data/lib/cmdx/parameters_serializer.rb +0 -115
  167. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  168. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  169. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,81 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # LazyStruct provides a flexible, hash-like data structure with dynamic method access
6
- # and lazy attribute definition. It serves as the foundation for CMDx's Context system,
7
- # allowing for dynamic parameter access and manipulation with both hash-style and
8
- # method-style syntax.
4
+ # Hash-like data structure with dynamic attribute access and automatic key normalization.
9
5
  #
10
- # LazyStruct combines the flexibility of a Hash with the convenience of method access,
11
- # automatically creating getter and setter methods for any key-value pairs stored within it.
12
- # All keys are normalized to symbols for consistent access patterns.
13
- #
14
- #
15
- # @example Basic usage
16
- # struct = LazyStruct.new(name: "John", age: 30)
17
- # struct.name #=> "John"
18
- # struct.age #=> 30
19
- # struct[:name] #=> "John"
20
- # struct["age"] #=> 30
21
- #
22
- # @example Dynamic attribute assignment
23
- # struct = LazyStruct.new
24
- # struct.email = "john@example.com"
25
- # struct[:phone] = "555-1234"
26
- # struct["address"] = "123 Main St"
27
- #
28
- # struct.email #=> "john@example.com"
29
- # struct.phone #=> "555-1234"
30
- # struct.address #=> "123 Main St"
31
- #
32
- # @example Hash-like operations
33
- # struct = LazyStruct.new(name: "John")
34
- # struct.merge!(age: 30, city: "NYC")
35
- # struct.delete!(:city)
36
- # struct.to_h #=> {:name => "John", :age => 30}
37
- #
38
- # @example Nested data access
39
- # struct = LazyStruct.new(user: {profile: {name: "John"}})
40
- # struct.dig(:user, :profile, :name) #=> "John"
41
- #
42
- # @example Usage in CMDx Context
43
- # class ProcessUserTask < CMDx::Task
44
- # required :user_id, type: :integer
45
- #
46
- # def call
47
- # context.user = User.find(user_id)
48
- # context.processed_at = Time.now
49
- # context.result_data = {status: "complete"}
50
- # end
51
- # end
52
- #
53
- # result = ProcessUserTask.call(user_id: 123)
54
- # result.context.user #=> <User id: 123>
55
- # result.context.processed_at #=> 2023-01-01 12:00:00 UTC
56
- #
57
- # @see Context Context class that inherits from LazyStruct
58
- # @see Configuration Configuration class that uses LazyStruct
59
- # @since 1.0.0
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.
60
10
  class LazyStruct
61
11
 
62
- ##
63
- # Initializes a new LazyStruct with the given data.
64
- # The input must respond to `to_h` for hash conversion.
12
+ # Creates a new LazyStruct instance from the provided arguments.
65
13
  #
66
- # @param args [Hash, #to_h] initial data for the struct
67
- # @raise [ArgumentError] if args doesn't respond to `to_h`
14
+ # @param args [Hash, #to_h] initial data for the structure, must respond to to_h
68
15
  #
69
- # @example With hash
70
- # struct = LazyStruct.new(name: "John", age: 30)
16
+ # @return [LazyStruct] a new LazyStruct instance
71
17
  #
72
- # @example With hash-like object
73
- # params = ActionController::Parameters.new(name: "John")
74
- # struct = LazyStruct.new(params)
18
+ # @raise [ArgumentError] if args doesn't respond to to_h
75
19
  #
76
- # @example Empty initialization
77
- # struct = LazyStruct.new
78
- # struct.name = "John" # Dynamic assignment
20
+ # @example Create with hash data
21
+ # struct = LazyStruct.new(name: "John", age: 30)
22
+ # struct.name # => "John"
23
+ #
24
+ # @example Create with hash-like object
25
+ # struct = LazyStruct.new(OpenStruct.new(status: "active"))
26
+ # struct.status # => "active"
79
27
  def initialize(args = {})
80
28
  unless args.respond_to?(:to_h)
81
29
  raise ArgumentError,
@@ -85,162 +33,150 @@ module CMDx
85
33
  @table = args.to_h.transform_keys { |k| symbolized_key(k) }
86
34
  end
87
35
 
88
- ##
89
- # Retrieves a value by key using hash-style access.
90
- # Keys are automatically converted to symbols.
36
+ # Retrieves the value for the specified key.
91
37
  #
92
38
  # @param key [Symbol, String] the key to retrieve
93
- # @return [Object, nil] the stored value or nil if not found
94
39
  #
95
- # @example
96
- # struct[:name] #=> "John"
97
- # struct["name"] #=> "John"
98
- # struct[:missing] #=> nil
40
+ # @return [Object, nil] the value associated with the key, or nil if not found
41
+ #
42
+ # @example Access existing key
43
+ # struct = LazyStruct.new(name: "John")
44
+ # struct[:name] # => "John"
45
+ # struct["name"] # => "John"
99
46
  def [](key)
100
47
  table[symbolized_key(key)]
101
48
  end
102
49
 
103
- ##
104
- # Retrieves a value by key with error handling and default support.
105
- # Similar to Hash#fetch, raises KeyError if key not found and no default given.
50
+ # Fetches the value for the specified key with optional default handling.
106
51
  #
107
- # @param key [Symbol, String] the key to retrieve
108
- # @param args [Array] default value if key not found
109
- # @return [Object] the stored value or default
110
- # @raise [KeyError] if key not found and no default provided
52
+ # @param key [Symbol, String] the key to fetch
53
+ # @param args [Array] optional default value or block arguments
111
54
  #
112
- # @example With existing key
113
- # struct.fetch!(:name) #=> "John"
55
+ # @return [Object] the value associated with the key, or default if not found
114
56
  #
115
- # @example With default value
116
- # struct.fetch!(:missing, "default") #=> "default"
57
+ # @raise [KeyError] if key is not found and no default is provided
117
58
  #
118
- # @example With block default
119
- # struct.fetch!(:missing) { "computed default" } #=> "computed default"
59
+ # @example Fetch with default value
60
+ # struct = LazyStruct.new(name: "John")
61
+ # struct.fetch!(:name) # => "John"
62
+ # struct.fetch!(:missing, "default") # => "default"
120
63
  #
121
- # @example Key not found
122
- # struct.fetch!(:missing) #=> raises KeyError
64
+ # @example Fetch with block
65
+ # struct.fetch!(:missing) { "computed default" } # => "computed default"
123
66
  def fetch!(key, ...)
124
67
  table.fetch(symbolized_key(key), ...)
125
68
  end
126
69
 
127
- ##
128
- # Stores a value by key, converting the key to a symbol.
70
+ # Stores a value for the specified key.
129
71
  #
130
- # @param key [Symbol, String] the key to store under
72
+ # @param key [Symbol, String] the key to store the value under
131
73
  # @param value [Object] the value to store
74
+ #
132
75
  # @return [Object] the stored value
133
76
  #
134
- # @example
135
- # struct.store!(:name, "John")
136
- # struct.store!("age", 30)
137
- # struct.name #=> "John"
138
- # struct.age #=> 30
77
+ # @example Store a value
78
+ # struct = LazyStruct.new
79
+ # struct.store!(:name, "John") # => "John"
80
+ # struct[:name] # => "John"
139
81
  def store!(key, value)
140
82
  table[symbolized_key(key)] = value
141
83
  end
142
84
  alias []= store!
143
85
 
144
- ##
145
- # Merges another hash-like object into this struct.
146
- # All keys from the source are converted to symbols.
86
+ # Merges the provided arguments into the current structure.
87
+ #
88
+ # @param args [Hash, #to_h] the data to merge, must respond to to_h
147
89
  #
148
- # @param args [Hash, #to_h] data to merge into this struct
149
- # @return [LazyStruct] self for method chaining
90
+ # @return [LazyStruct] returns self for method chaining
150
91
  #
151
- # @example
92
+ # @example Merge additional data
152
93
  # struct = LazyStruct.new(name: "John")
153
94
  # struct.merge!(age: 30, city: "NYC")
154
- # struct.to_h #=> {:name => "John", :age => 30, :city => "NYC"}
95
+ # struct.age # => 30
155
96
  def merge!(args = {})
156
97
  args.to_h.each { |key, value| store!(symbolized_key(key), value) }
157
98
  self
158
99
  end
159
100
 
160
- ##
161
- # Deletes a key-value pair from the struct.
101
+ # Deletes the specified key from the structure.
162
102
  #
163
103
  # @param key [Symbol, String] the key to delete
164
- # @param block [Proc] optional block to execute if key not found
165
- # @return [Object, nil] the deleted value or result of block
104
+ # @param block [Proc] optional block to execute if key is not found
166
105
  #
167
- # @example
168
- # struct.delete!(:name) #=> "John"
169
- # struct.delete!(:missing) #=> nil
170
- # struct.delete!(:missing) { "not found" } #=> "not found"
106
+ # @return [Object, nil] the deleted value, or result of block if key not found
107
+ #
108
+ # @example Delete a key
109
+ # struct = LazyStruct.new(name: "John", age: 30)
110
+ # struct.delete!(:age) # => 30
111
+ # struct.age # => nil
171
112
  def delete!(key, &)
172
113
  table.delete(symbolized_key(key), &)
173
114
  end
174
115
  alias delete_field! delete!
175
116
 
176
- ##
177
- # Compares this struct with another for equality.
178
- # Two LazyStructs are equal if they have the same class and hash representation.
117
+ # Checks equality with another LazyStruct instance.
179
118
  #
180
- # @param other [Object] object to compare with
181
- # @return [Boolean] true if structs are equal
119
+ # @param other [Object] the object to compare with
182
120
  #
183
- # @example
121
+ # @return [Boolean] true if both objects are LazyStruct instances with the same data
122
+ #
123
+ # @example Compare structures
184
124
  # struct1 = LazyStruct.new(name: "John")
185
125
  # struct2 = LazyStruct.new(name: "John")
186
- # struct1 == struct2 #=> true
187
- # struct1.eql?(struct2) #=> true
126
+ # struct1.eql?(struct2) # => true
188
127
  def eql?(other)
189
128
  other.is_a?(self.class) && (to_h == other.to_h)
190
129
  end
191
130
  alias == eql?
192
131
 
193
- ##
194
- # Retrieves nested values using a sequence of keys.
195
- # Similar to Hash#dig, safely navigates nested structures.
132
+ # Extracts nested values using the specified key path.
196
133
  #
197
- # @param key [Symbol, String] the first key to access
198
- # @param keys [Array<Symbol, String>] additional keys for nested access
199
- # @return [Object, nil] the nested value or nil if path doesn't exist
200
- # @raise [TypeError] if key cannot be converted to symbol
134
+ # @param key [Symbol, String] the first key in the path
135
+ # @param keys [Array] additional keys for nested access
201
136
  #
202
- # @example
203
- # struct = LazyStruct.new(user: {profile: {name: "John"}})
204
- # struct.dig(:user, :profile, :name) #=> "John"
205
- # struct.dig(:user, :missing, :name) #=> nil
137
+ # @return [Object, nil] the value at the specified path, or nil if not found
138
+ #
139
+ # @example Dig into nested structure
140
+ # struct = LazyStruct.new(user: { profile: { name: "John" } })
141
+ # struct.dig(:user, :profile, :name) # => "John"
206
142
  def dig(key, *keys)
207
143
  table.dig(symbolized_key(key), *keys)
208
144
  end
209
145
 
210
- ##
211
- # Iterates over each key-value pair in the struct.
146
+ # Iterates over each key-value pair in the structure.
147
+ #
148
+ # @param block [Proc] the block to execute for each pair
212
149
  #
213
- # @yieldparam key [Symbol] the key
214
- # @yieldparam value [Object] the value
215
- # @return [LazyStruct] self if block given, Enumerator otherwise
150
+ # @return [LazyStruct] returns self if block given, otherwise returns enumerator
216
151
  #
217
- # @example
152
+ # @example Iterate over pairs
153
+ # struct = LazyStruct.new(name: "John", age: 30)
218
154
  # struct.each_pair { |key, value| puts "#{key}: #{value}" }
155
+ # # Output: name: John, age: 30
219
156
  def each_pair(&)
220
157
  table.each_pair(&)
221
158
  end
222
159
 
223
- ##
224
- # Converts the struct to a hash representation.
160
+ # Converts the structure to a hash representation.
225
161
  #
226
162
  # @param block [Proc] optional block for hash transformation
227
- # @return [Hash] hash representation with symbol keys
228
163
  #
229
- # @example
164
+ # @return [Hash] hash representation of the structure
165
+ #
166
+ # @example Convert to hash
230
167
  # struct = LazyStruct.new(name: "John", age: 30)
231
- # struct.to_h #=> {:name => "John", :age => 30}
168
+ # struct.to_h # => {:name=>"John", :age=>30}
232
169
  def to_h(&)
233
170
  table.to_h(&)
234
171
  end
235
172
 
236
- ##
237
- # Returns a string representation of the struct showing all key-value pairs.
173
+ # Returns a string representation of the structure.
238
174
  #
239
- # @return [String] formatted string representation
175
+ # @return [String] formatted string showing class name and key-value pairs
240
176
  #
241
- # @example
177
+ # @example Inspect structure
242
178
  # struct = LazyStruct.new(name: "John", age: 30)
243
- # struct.inspect #=> '#<CMDx::LazyStruct:name="John" :age=30>'
179
+ # struct.inspect # => "#<CMDx::LazyStruct :name=\"John\" :age=30>"
244
180
  def inspect
245
181
  "#<#{self.class.name}#{table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
246
182
  end
@@ -248,70 +184,50 @@ module CMDx
248
184
 
249
185
  private
250
186
 
187
+ # Returns the internal hash table, initializing it if needed.
188
+ #
189
+ # @return [Hash] the internal hash storage
251
190
  def table
252
191
  @table ||= {}
253
192
  end
254
193
 
255
- ##
256
- # Handles dynamic method calls for attribute access and assignment.
257
- # Getter methods return the stored value, setter methods (ending with =) store values.
194
+ # Provides dynamic method access to stored values and assignment.
258
195
  #
259
196
  # @param method_name [Symbol] the method name being called
260
- # @param args [Array] arguments passed to the method
261
- # @return [Object] the stored value for getters, the assigned value for setters
197
+ # @param args [Array] method arguments
198
+ # @param _kwargs [Hash] keyword arguments (unused)
199
+ # @param block [Proc] optional block (unused)
262
200
  #
263
- # @example Getter methods
264
- # struct.name # Calls method_missing(:name)
265
- # struct.undefined # Calls method_missing(:undefined) => nil
201
+ # @return [Object] the value for the method name, or result of assignment
266
202
  #
267
- # @example Setter methods
268
- # struct.name = "John" # Calls method_missing(:name=, "John")
269
- #
270
- # @api private
203
+ # @example Dynamic method access
204
+ # struct = LazyStruct.new(name: "John")
205
+ # struct.name # => "John"
206
+ # struct.age = 30
207
+ # struct.age # => 30
271
208
  def method_missing(method_name, *args, **_kwargs, &)
272
209
  table.fetch(symbolized_key(method_name)) do
273
210
  store!(method_name[0..-2], args.first) if method_name.end_with?("=")
274
211
  end
275
212
  end
276
213
 
277
- ##
278
- # Determines if the struct responds to a given method name.
279
- # Returns true for any key in the internal table or standard methods.
214
+ # Checks if the structure responds to the specified method name.
280
215
  #
281
216
  # @param method_name [Symbol] the method name to check
282
217
  # @param include_private [Boolean] whether to include private methods
283
- # @return [Boolean] true if the struct responds to the method
284
218
  #
285
- # @example
286
- # struct = LazyStruct.new(name: "John")
287
- # struct.respond_to?(:name) #=> true
288
- # struct.respond_to?(:missing) #=> false
289
- # struct.respond_to?(:to_h) #=> true
290
- #
291
- # @api private
219
+ # @return [Boolean] true if method is available or key exists in structure
292
220
  def respond_to_missing?(method_name, include_private = false)
293
221
  table.key?(symbolized_key(method_name)) || super
294
222
  end
295
223
 
296
- ##
297
224
  # Converts a key to a symbol for consistent internal storage.
298
- # This method normalizes all keys to symbols regardless of their input type,
299
- # ensuring consistent access patterns throughout the LazyStruct.
300
225
  #
301
- # @param key [Object] the key to convert to a symbol
302
- # @return [Symbol] the key converted to a symbol
303
- # @raise [TypeError] if the key cannot be converted to a symbol (doesn't respond to `to_sym`)
226
+ # @param key [Object] the key to convert to symbol
304
227
  #
305
- # @example Valid key conversion
306
- # symbolized_key("name") #=> :name
307
- # symbolized_key(:name) #=> :name
308
- # symbolized_key("123") #=> :"123"
228
+ # @return [Symbol] the symbolized key
309
229
  #
310
- # @example Invalid key conversion
311
- # symbolized_key(Object.new) #=> raises TypeError
312
- # symbolized_key(123) #=> raises TypeError
313
- #
314
- # @api private
230
+ # @raise [TypeError] if key cannot be converted to symbol
315
231
  def symbolized_key(key)
316
232
  key.to_sym
317
233
  rescue NoMethodError
@@ -2,54 +2,28 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # JSON log formatter for CMDx logging system.
5
+ # JSON log formatter that outputs structured log entries as JSON.
6
6
  #
7
- # Formats log entries as single-line JSON objects containing task execution metadata
8
- # including severity, timestamp, process ID, and serialized task information.
9
- # Ideal for structured logging systems that need to parse log data programmatically.
10
- #
11
- # @example Basic usage with global logger configuration
12
- # CMDx.configure do |config|
13
- # config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::Json.new)
14
- # end
15
- #
16
- # @example Task-specific formatter configuration
17
- # class ProcessOrderTask < CMDx::Task
18
- # task_settings!(log_format: CMDx::LogFormatters::Json.new)
19
- #
20
- # def call
21
- # logger.info "Processing order #{order_id}"
22
- # end
23
- # end
24
- #
25
- # @example Sample JSON output
26
- # {"severity":"INFO","pid":1234,"timestamp":"2022-07-17T18:43:15.000000","index":0,"chain_id":"018c2b95-b764-7615","type":"Task","class":"ProcessOrderTask","id":"018c2b95-b764-7615","tags":[],"state":"complete","status":"success","outcome":"success","metadata":{},"runtime":15,"origin":"CMDx"}
27
- #
28
- # @see CMDx::LogFormatters::PrettyJson For human-readable JSON formatting
29
- # @see CMDx::LoggerSerializer For details on serialized data structure
7
+ # This formatter converts log entries into JSON format, including metadata
8
+ # such as severity, process ID, and timestamp. Each log entry is output as
9
+ # a single line of JSON followed by a newline character.
30
10
  class Json
31
11
 
32
- # Formats a log entry as a single-line JSON string.
12
+ # Formats a log entry as a JSON string.
33
13
  #
34
- # Combines task execution metadata with severity, process ID, and timestamp
35
- # information to create a comprehensive JSON log entry suitable for
36
- # structured logging systems and log aggregation tools.
14
+ # @param severity [String] the log severity level (e.g., "INFO", "ERROR")
15
+ # @param time [Time] the timestamp when the log entry was created
16
+ # @param task [Object] the task object associated with the log entry
17
+ # @param message [String] the log message content
37
18
  #
38
- # @param severity [String] Log severity level (DEBUG, INFO, WARN, ERROR, FATAL)
39
- # @param time [Time] Timestamp when the log entry was created
40
- # @param task [CMDx::Task] Task instance being logged
41
- # @param message [Object] Log message or data to be included
19
+ # @return [String] the formatted JSON log entry with trailing newline
42
20
  #
43
- # @return [String] Single-line JSON string with newline terminator
21
+ # @raise [JSON::GeneratorError] if the log data cannot be serialized to JSON
44
22
  #
45
- # @example Success log entry
23
+ # @example Formatting a log entry
46
24
  # formatter = CMDx::LogFormatters::Json.new
47
- # output = formatter.call("INFO", Time.now, task, "Order processed")
48
- # # => {"severity":"INFO","pid":1234,...}\n
49
- #
50
- # @example Error log entry with metadata
51
- # output = formatter.call("ERROR", Time.now, task, error_details)
52
- # # => {"severity":"ERROR","pid":1234,"caused_failure":{...},...}\n
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"
53
27
  def call(severity, time, task, message)
54
28
  m = LoggerSerializer.call(severity, time, task, message).merge!(
55
29
  severity:,
@@ -2,54 +2,28 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Key-value log formatter for CMDx logging system.
5
+ # Key-value log formatter that outputs structured log entries as key=value pairs.
6
6
  #
7
- # Formats log entries as space-separated key=value pairs on a single line.
8
- # Provides a compact, structured format that is easily parseable by log
9
- # processing tools while remaining human-readable for basic inspection.
10
- #
11
- # @example Basic usage with global logger configuration
12
- # CMDx.configure do |config|
13
- # config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::KeyValue.new)
14
- # end
15
- #
16
- # @example Task-specific formatter configuration
17
- # class ProcessOrderTask < CMDx::Task
18
- # task_settings!(log_format: CMDx::LogFormatters::KeyValue.new)
19
- #
20
- # def call
21
- # logger.info "Processing order #{order_id}"
22
- # end
23
- # end
24
- #
25
- # @example Sample key-value output
26
- # severity=INFO pid=1234 timestamp=2022-07-17T18:43:15.000000 index=0 chain_id=018c2b95-b764-7615 type=Task class=ProcessOrderTask id=018c2b95-b764-7615 tags=[] state=complete status=success outcome=success metadata={} runtime=15 origin=CMDx
27
- #
28
- # @see CMDx::LogFormatters::PrettyKeyValue For ANSI-colorized key-value formatting
29
- # @see CMDx::LoggerSerializer For details on serialized data structure
7
+ # This formatter converts log entries into key-value format, including metadata
8
+ # such as severity, process ID, and timestamp. Each log entry is output as
9
+ # space-separated key=value pairs followed by a newline character.
30
10
  class KeyValue
31
11
 
32
- # Formats a log entry as space-separated key=value pairs.
12
+ # Formats a log entry as a key=value string.
33
13
  #
34
- # Combines task execution metadata with severity, process ID, and timestamp
35
- # information to create a compact key-value log entry suitable for
36
- # structured logging systems that prefer flat field formats.
14
+ # @param severity [String] the log severity level (e.g., "INFO", "ERROR")
15
+ # @param time [Time] the timestamp when the log entry was created
16
+ # @param task [Object] the task object associated with the log entry
17
+ # @param message [String] the log message content
37
18
  #
38
- # @param severity [String] Log severity level (DEBUG, INFO, WARN, ERROR, FATAL)
39
- # @param time [Time] Timestamp when the log entry was created
40
- # @param task [CMDx::Task] Task instance being logged
41
- # @param message [Object] Log message or data to be included
19
+ # @return [String] the formatted key=value log entry with trailing newline
42
20
  #
43
- # @return [String] Single-line key=value formatted string with newline terminator
21
+ # @raise [StandardError] if the log data cannot be serialized to key=value format
44
22
  #
45
- # @example Success log entry
23
+ # @example Formatting a log entry
46
24
  # formatter = CMDx::LogFormatters::KeyValue.new
47
- # output = formatter.call("INFO", Time.now, task, "Order processed")
48
- # # => "severity=INFO pid=1234 timestamp=... status=success\n"
49
- #
50
- # @example Error log entry with failure metadata
51
- # output = formatter.call("ERROR", Time.now, task, error_details)
52
- # # => "severity=ERROR pid=1234 ... caused_failure={...} threw_failure={...}\n"
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"
53
27
  def call(severity, time, task, message)
54
28
  m = LoggerSerializer.call(severity, time, task, message).merge!(
55
29
  severity:,
@@ -2,62 +2,28 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Line log formatter for CMDx logging system.
5
+ # Line log formatter that outputs log entries in a traditional line format.
6
6
  #
7
- # Formats log entries in a traditional single-line format similar to Ruby's
8
- # standard Logger output. Combines severity indicators, timestamps, process
9
- # information, and task details in a compact, readable format suitable for
10
- # standard logging environments.
11
- #
12
- # @example Basic usage with global logger configuration
13
- # CMDx.configure do |config|
14
- # config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
15
- # end
16
- #
17
- # @example Task-specific formatter configuration
18
- # class ProcessOrderTask < CMDx::Task
19
- # task_settings!(log_format: CMDx::LogFormatters::Line.new)
20
- #
21
- # def call
22
- # logger.info "Processing order #{order_id}"
23
- # end
24
- # end
25
- #
26
- # @example Sample line output
27
- # I, [2022-07-17T18:43:15.000000 #1234] INFO -- ProcessOrderTask: state=complete status=success outcome=success runtime=15
28
- #
29
- # @example Error line output with failure details
30
- # E, [2022-07-17T18:43:15.000000 #1234] ERROR -- ProcessOrderTask: state=interrupted status=failed outcome=failed caused_failure={...}
31
- #
32
- # @see CMDx::LogFormatters::PrettyLine For ANSI-colorized line formatting
33
- # @see CMDx::LoggerSerializer For details on serialized data structure
34
- # @see CMDx::Utils::LogTimestamp For timestamp formatting
7
+ # This formatter converts log entries into a human-readable line format,
8
+ # including metadata such as severity, process ID, and timestamp. Each log
9
+ # entry is output as a single line with structured information.
35
10
  class Line
36
11
 
37
- # Formats a log entry as a traditional single-line log entry.
12
+ # Formats a log entry as a line string.
38
13
  #
39
- # Creates a log entry in the format: "SEVERITY_INITIAL, [TIMESTAMP #PID] SEVERITY -- CLASS: MESSAGE"
40
- # where MESSAGE contains key=value pairs of task execution metadata.
14
+ # @param severity [String] the log severity level (e.g., "INFO", "ERROR")
15
+ # @param time [Time] the timestamp when the log entry was created
16
+ # @param task [Object] the task object associated with the log entry
17
+ # @param message [String] the log message content
41
18
  #
42
- # @param severity [String] Log severity level (DEBUG, INFO, WARN, ERROR, FATAL)
43
- # @param time [Time] Timestamp when the log entry was created
44
- # @param task [CMDx::Task] Task instance being logged
45
- # @param message [Object] Log message or data to be included
19
+ # @return [String] the formatted line log entry with trailing newline
46
20
  #
47
- # @return [String] Single-line formatted log entry with newline terminator
21
+ # @raise [NoMethodError] if the task object doesn't respond to expected methods
48
22
  #
49
- # @example Success log entry
23
+ # @example Formatting a log entry
50
24
  # formatter = CMDx::LogFormatters::Line.new
51
- # output = formatter.call("INFO", Time.now, task, "Order processed")
52
- # # => "I, [2022-07-17T18:43:15.000000 #1234] INFO -- ProcessOrderTask: state=complete status=success\n"
53
- #
54
- # @example Debug log entry with detailed metadata
55
- # output = formatter.call("DEBUG", Time.now, task, debug_info)
56
- # # => "D, [2022-07-17T18:43:15.000000 #1234] DEBUG -- ProcessOrderTask: index=0 chain_id=... metadata={...}\n"
57
- #
58
- # @example Error log entry with failure chain
59
- # output = formatter.call("ERROR", Time.now, task, error_details)
60
- # # => "E, [2022-07-17T18:43:15.000000 #1234] ERROR -- ProcessOrderTask: status=failed caused_failure={...} threw_failure={...}\n"
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"
61
27
  def call(severity, time, task, message)
62
28
  t = Utils::LogTimestamp.call(time.utc)
63
29
  m = LoggerSerializer.call(severity, time, task, message)