cmdx 1.0.1 → 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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,81 +1,28 @@
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
+ # Flexible struct-like object with symbol-based attribute access and dynamic assignment.
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 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.
60
9
  class LazyStruct
61
10
 
62
- ##
63
- # Initializes a new LazyStruct with the given data.
64
- # The input must respond to `to_h` for hash conversion.
11
+ # Creates a new LazyStruct instance with the provided attributes.
65
12
  #
66
- # @param args [Hash, #to_h] initial data for the struct
67
- # @raise [ArgumentError] if args doesn't respond to `to_h`
13
+ # @param args [Hash, #to_h] initial attributes for the struct
68
14
  #
69
- # @example With hash
70
- # struct = LazyStruct.new(name: "John", age: 30)
15
+ # @return [LazyStruct] a new LazyStruct instance
71
16
  #
72
- # @example With hash-like object
73
- # params = ActionController::Parameters.new(name: "John")
74
- # struct = LazyStruct.new(params)
17
+ # @raise [ArgumentError] if args doesn't respond to to_h
75
18
  #
76
- # @example Empty initialization
77
- # struct = LazyStruct.new
78
- # struct.name = "John" # Dynamic assignment
19
+ # @example Create with hash attributes
20
+ # struct = LazyStruct.new(name: "John", age: 30)
21
+ # struct.name #=> "John"
22
+ #
23
+ # @example Create with hash-like object
24
+ # struct = LazyStruct.new(OpenStruct.new(status: "active"))
25
+ # struct.status #=> "active"
79
26
  def initialize(args = {})
80
27
  unless args.respond_to?(:to_h)
81
28
  raise ArgumentError,
@@ -85,162 +32,160 @@ module CMDx
85
32
  @table = args.to_h.transform_keys { |k| symbolized_key(k) }
86
33
  end
87
34
 
88
- ##
89
- # Retrieves a value by key using hash-style access.
90
- # Keys are automatically converted to symbols.
35
+ # Retrieves the value for the specified key.
36
+ #
37
+ # @param key [Symbol, String] the key to look up
91
38
  #
92
- # @param key [Symbol, String] the key to retrieve
93
- # @return [Object, nil] the stored value or nil if not found
39
+ # @return [Object, nil] the value associated with the key, or nil if not found
94
40
  #
95
- # @example
96
- # struct[:name] #=> "John"
97
- # struct["name"] #=> "John"
98
- # struct[:missing] #=> nil
41
+ # @example Access attribute by symbol
42
+ # struct = LazyStruct.new(name: "John")
43
+ # struct[:name] #=> "John"
44
+ #
45
+ # @example Access attribute by string
46
+ # struct[:name] #=> "John"
47
+ # struct["name"] #=> "John"
99
48
  def [](key)
100
49
  table[symbolized_key(key)]
101
50
  end
102
51
 
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.
52
+ # Retrieves the value for the specified key or returns/yields a default.
106
53
  #
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
54
+ # @param key [Symbol, String] the key to look up
55
+ # @param args [Array] additional arguments passed to Hash#fetch
111
56
  #
112
- # @example With existing key
113
- # struct.fetch!(:name) #=> "John"
57
+ # @return [Object] the value associated with the key, or default value
114
58
  #
115
- # @example With default value
116
- # struct.fetch!(:missing, "default") #=> "default"
59
+ # @raise [KeyError] if key is not found and no default is provided
117
60
  #
118
- # @example With block default
119
- # struct.fetch!(:missing) { "computed default" } #=> "computed default"
61
+ # @example Fetch with default value
62
+ # struct = LazyStruct.new(name: "John")
63
+ # struct.fetch!(:age, 25) #=> 25
120
64
  #
121
- # @example Key not found
122
- # struct.fetch!(:missing) #=> raises KeyError
65
+ # @example Fetch with block default
66
+ # struct.fetch!(:missing) { "default" } #=> "default"
123
67
  def fetch!(key, ...)
124
68
  table.fetch(symbolized_key(key), ...)
125
69
  end
126
70
 
127
- ##
128
- # Stores a value by key, converting the key to a symbol.
71
+ # Stores a value for the specified key.
129
72
  #
130
- # @param key [Symbol, String] the key to store under
73
+ # @param key [Symbol, String] the key to store the value under
131
74
  # @param value [Object] the value to store
75
+ #
132
76
  # @return [Object] the stored value
133
77
  #
134
- # @example
135
- # struct.store!(:name, "John")
136
- # struct.store!("age", 30)
137
- # struct.name #=> "John"
138
- # struct.age #=> 30
78
+ # @example Store a value
79
+ # struct = LazyStruct.new
80
+ # struct.store!(:name, "John") #=> "John"
81
+ # struct.name #=> "John"
139
82
  def store!(key, value)
140
83
  table[symbolized_key(key)] = value
141
84
  end
142
85
  alias []= store!
143
86
 
144
- ##
145
- # Merges another hash-like object into this struct.
146
- # All keys from the source are converted to symbols.
87
+ # Merges the provided arguments into the struct's attributes.
88
+ #
89
+ # @param args [Hash, #to_h] attributes to merge into the struct
147
90
  #
148
- # @param args [Hash, #to_h] data to merge into this struct
149
91
  # @return [LazyStruct] self for method chaining
150
92
  #
151
- # @example
93
+ # @example Merge attributes
152
94
  # struct = LazyStruct.new(name: "John")
153
95
  # struct.merge!(age: 30, city: "NYC")
154
- # struct.to_h #=> {:name => "John", :age => 30, :city => "NYC"}
96
+ # struct.age #=> 30
155
97
  def merge!(args = {})
156
98
  args.to_h.each { |key, value| store!(symbolized_key(key), value) }
157
99
  self
158
100
  end
159
101
 
160
- ##
161
- # Deletes a key-value pair from the struct.
102
+ # Deletes the specified key from the struct.
162
103
  #
163
104
  # @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
105
+ # @param block [Proc] optional block to yield if key is not found
106
+ #
107
+ # @return [Object, nil] the deleted value, or result of block if key not found
108
+ #
109
+ # @example Delete an attribute
110
+ # struct = LazyStruct.new(name: "John", age: 30)
111
+ # struct.delete!(:age) #=> 30
112
+ # struct.age #=> nil
166
113
  #
167
- # @example
168
- # struct.delete!(:name) #=> "John"
169
- # struct.delete!(:missing) #=> nil
170
- # struct.delete!(:missing) { "not found" } #=> "not found"
114
+ # @example Delete with default block
115
+ # struct.delete!(:missing) { "not found" } #=> "not found"
171
116
  def delete!(key, &)
172
117
  table.delete(symbolized_key(key), &)
173
118
  end
174
119
  alias delete_field! delete!
175
120
 
176
- ##
177
- # Compares this struct with another for equality.
178
- # Two LazyStructs are equal if they have the same class and hash representation.
121
+ # Checks equality with another object.
179
122
  #
180
- # @param other [Object] object to compare with
181
- # @return [Boolean] true if structs are equal
123
+ # @param other [Object] the object to compare against
182
124
  #
183
- # @example
125
+ # @return [Boolean] true if other is a LazyStruct with identical attributes
126
+ #
127
+ # @example Compare structs
184
128
  # struct1 = LazyStruct.new(name: "John")
185
129
  # struct2 = LazyStruct.new(name: "John")
186
- # struct1 == struct2 #=> true
187
- # struct1.eql?(struct2) #=> true
130
+ # struct1.eql?(struct2) #=> true
188
131
  def eql?(other)
189
132
  other.is_a?(self.class) && (to_h == other.to_h)
190
133
  end
191
134
  alias == eql?
192
135
 
193
- ##
194
- # Retrieves nested values using a sequence of keys.
195
- # Similar to Hash#dig, safely navigates nested structures.
196
- #
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
201
- #
202
- # @example
203
- # struct = LazyStruct.new(user: {profile: {name: "John"}})
204
- # struct.dig(:user, :profile, :name) #=> "John"
205
- # struct.dig(:user, :missing, :name) #=> nil
136
+ # Extracts nested values using key path traversal.
137
+ #
138
+ # @param key [Symbol, String] the initial key to look up
139
+ # @param keys [Array<Symbol, String>] additional keys for nested traversal
140
+ #
141
+ # @return [Object, nil] the nested value, or nil if any key in the path is missing
142
+ #
143
+ # @example Dig into nested structure
144
+ # struct = LazyStruct.new(user: { profile: { name: "John" } })
145
+ # struct.dig(:user, :profile, :name) #=> "John"
146
+ # struct.dig(:user, :missing, :name) #=> nil
206
147
  def dig(key, *keys)
207
148
  table.dig(symbolized_key(key), *keys)
208
149
  end
209
150
 
210
- ##
211
151
  # Iterates over each key-value pair in the struct.
212
152
  #
213
- # @yieldparam key [Symbol] the key
214
- # @yieldparam value [Object] the value
215
- # @return [LazyStruct] self if block given, Enumerator otherwise
153
+ # @param block [Proc] the block to execute for each key-value pair
154
+ #
155
+ # @return [Enumerator, LazyStruct] an enumerator if no block given, self otherwise
216
156
  #
217
- # @example
157
+ # @example Iterate over pairs
158
+ # struct = LazyStruct.new(name: "John", age: 30)
218
159
  # struct.each_pair { |key, value| puts "#{key}: #{value}" }
160
+ # # Output: name: John
161
+ # # age: 30
219
162
  def each_pair(&)
220
163
  table.each_pair(&)
221
164
  end
222
165
 
223
- ##
224
166
  # Converts the struct to a hash representation.
225
167
  #
226
- # @param block [Proc] optional block for hash transformation
227
- # @return [Hash] hash representation with symbol keys
168
+ # @param block [Proc] optional block for transforming key-value pairs
169
+ #
170
+ # @return [Hash] a hash containing all the struct's attributes
228
171
  #
229
- # @example
172
+ # @example Convert to hash
230
173
  # struct = LazyStruct.new(name: "John", age: 30)
231
- # 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" }
232
178
  def to_h(&)
233
179
  table.to_h(&)
234
180
  end
235
181
 
236
- ##
237
- # Returns a string representation of the struct showing all key-value pairs.
182
+ # Returns a string representation of the struct for debugging.
238
183
  #
239
- # @return [String] formatted string representation
184
+ # @return [String] a formatted string showing the class name and attributes
240
185
  #
241
- # @example
186
+ # @example Inspect struct
242
187
  # struct = LazyStruct.new(name: "John", age: 30)
243
- # struct.inspect #=> '#<CMDx::LazyStruct:name="John" :age=30>'
188
+ # struct.inspect #=> "#<CMDx::LazyStruct :name=\"John\" :age=30>"
244
189
  def inspect
245
190
  "#<#{self.class.name}#{table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
246
191
  end
@@ -248,70 +193,49 @@ module CMDx
248
193
 
249
194
  private
250
195
 
196
+ # Returns the internal hash table storing the struct's attributes.
197
+ #
198
+ # @return [Hash] the internal attribute storage
251
199
  def table
252
200
  @table ||= {}
253
201
  end
254
202
 
255
- ##
256
203
  # Handles dynamic method calls for attribute access and assignment.
257
- # Getter methods return the stored value, setter methods (ending with =) store values.
258
204
  #
259
205
  # @param method_name [Symbol] the method name being called
260
206
  # @param args [Array] arguments passed to the method
261
- # @return [Object] the stored value for getters, the assigned value for setters
262
- #
263
- # @example Getter methods
264
- # struct.name # Calls method_missing(:name)
265
- # struct.undefined # Calls method_missing(:undefined) => nil
207
+ # @param _kwargs [Hash] keyword arguments (unused)
208
+ # @param block [Proc] block passed to the method (unused)
266
209
  #
267
- # @example Setter methods
268
- # struct.name = "John" # Calls method_missing(:name=, "John")
210
+ # @return [Object, nil] the attribute value for getters, or the assigned value for setters
269
211
  #
270
- # @api private
212
+ # @example Dynamic attribute access
213
+ # struct = LazyStruct.new(name: "John")
214
+ # struct.name #=> "John"
215
+ # struct.age = 30 #=> 30
271
216
  def method_missing(method_name, *args, **_kwargs, &)
272
217
  table.fetch(symbolized_key(method_name)) do
273
218
  store!(method_name[0..-2], args.first) if method_name.end_with?("=")
274
219
  end
275
220
  end
276
221
 
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.
222
+ # Checks if the struct responds to a method name.
280
223
  #
281
224
  # @param method_name [Symbol] the method name to check
282
225
  # @param include_private [Boolean] whether to include private methods
283
- # @return [Boolean] true if the struct responds to the method
284
- #
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
226
  #
291
- # @api private
227
+ # @return [Boolean] true if the struct has the attribute or responds to the method
292
228
  def respond_to_missing?(method_name, include_private = false)
293
229
  table.key?(symbolized_key(method_name)) || super
294
230
  end
295
231
 
296
- ##
297
232
  # 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
- #
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`)
304
233
  #
305
- # @example Valid key conversion
306
- # symbolized_key("name") #=> :name
307
- # symbolized_key(:name) #=> :name
308
- # symbolized_key("123") #=> :"123"
234
+ # @param key [Symbol, String, Object] the key to convert
309
235
  #
310
- # @example Invalid key conversion
311
- # symbolized_key(Object.new) #=> raises TypeError
312
- # symbolized_key(123) #=> raises TypeError
236
+ # @return [Symbol] the symbolized key
313
237
  #
314
- # @api private
238
+ # @raise [TypeError] if the key cannot be converted to a symbol
315
239
  def symbolized_key(key)
316
240
  key.to_sym
317
241
  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)