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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/lazy_struct.rb
CHANGED
@@ -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
|
11
|
-
#
|
12
|
-
#
|
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
|
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
|
-
# @
|
70
|
-
# struct = LazyStruct.new(name: "John", age: 30)
|
15
|
+
# @return [LazyStruct] a new LazyStruct instance
|
71
16
|
#
|
72
|
-
# @
|
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
|
77
|
-
# struct = LazyStruct.new
|
78
|
-
# struct.name
|
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
|
-
#
|
90
|
-
#
|
35
|
+
# Retrieves the value for the specified key.
|
36
|
+
#
|
37
|
+
# @param key [Symbol, String] the key to look up
|
91
38
|
#
|
92
|
-
# @
|
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
|
97
|
-
# struct[
|
98
|
-
#
|
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
|
108
|
-
# @param args [Array]
|
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
|
-
# @
|
113
|
-
# struct.fetch!(:name) #=> "John"
|
57
|
+
# @return [Object] the value associated with the key, or default value
|
114
58
|
#
|
115
|
-
# @
|
116
|
-
# struct.fetch!(:missing, "default") #=> "default"
|
59
|
+
# @raise [KeyError] if key is not found and no default is provided
|
117
60
|
#
|
118
|
-
# @example
|
119
|
-
# struct.
|
61
|
+
# @example Fetch with default value
|
62
|
+
# struct = LazyStruct.new(name: "John")
|
63
|
+
# struct.fetch!(:age, 25) #=> 25
|
120
64
|
#
|
121
|
-
# @example
|
122
|
-
# struct.fetch!(:missing)
|
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.
|
136
|
-
# struct.store!("
|
137
|
-
# struct.name
|
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
|
-
#
|
146
|
-
#
|
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.
|
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
|
165
|
-
#
|
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!(:
|
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
|
181
|
-
# @return [Boolean] true if structs are equal
|
123
|
+
# @param other [Object] the object to compare against
|
182
124
|
#
|
183
|
-
# @
|
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
|
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
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
# @
|
199
|
-
#
|
200
|
-
# @
|
201
|
-
#
|
202
|
-
#
|
203
|
-
# struct
|
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
|
-
# @
|
214
|
-
#
|
215
|
-
# @return [LazyStruct]
|
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
|
227
|
-
#
|
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
|
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
|
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
|
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
|
-
# @
|
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
|
-
# @
|
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
|
-
# @
|
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
|
-
# @
|
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
|
-
# @
|
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
|
-
# @
|
311
|
-
# symbolized_key(Object.new) #=> raises TypeError
|
312
|
-
# symbolized_key(123) #=> raises TypeError
|
236
|
+
# @return [Symbol] the symbolized key
|
313
237
|
#
|
314
|
-
# @
|
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
|
5
|
+
# JSON log formatter that outputs structured log entries as JSON.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
12
|
+
# Formats a log entry as a JSON string.
|
33
13
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
21
|
+
# @raise [JSON::GeneratorError] if the log data cannot be serialized to JSON
|
44
22
|
#
|
45
|
-
# @example
|
23
|
+
# @example Formatting a log entry
|
46
24
|
# formatter = CMDx::LogFormatters::Json.new
|
47
|
-
#
|
48
|
-
#
|
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
|
5
|
+
# Key-value log formatter that outputs structured log entries as key=value pairs.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
12
|
+
# Formats a log entry as a key=value string.
|
33
13
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
21
|
+
# @raise [StandardError] if the log data cannot be serialized to key=value format
|
44
22
|
#
|
45
|
-
# @example
|
23
|
+
# @example Formatting a log entry
|
46
24
|
# formatter = CMDx::LogFormatters::KeyValue.new
|
47
|
-
#
|
48
|
-
#
|
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
|
5
|
+
# Line log formatter that outputs log entries in a traditional line format.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
12
|
+
# Formats a log entry as a line string.
|
38
13
|
#
|
39
|
-
#
|
40
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
21
|
+
# @raise [NoMethodError] if the task object doesn't respond to expected methods
|
48
22
|
#
|
49
|
-
# @example
|
23
|
+
# @example Formatting a log entry
|
50
24
|
# formatter = CMDx::LogFormatters::Line.new
|
51
|
-
#
|
52
|
-
#
|
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)
|