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.
- checksums.yaml +4 -4
- data/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +101 -49
- data/README.md +2 -1
- data/docs/ai_prompts.md +10 -0
- data/docs/basics/call.md +11 -2
- data/docs/basics/chain.md +10 -1
- data/docs/basics/context.md +9 -0
- data/docs/basics/setup.md +9 -0
- data/docs/callbacks.md +14 -37
- data/docs/configuration.md +68 -27
- data/docs/getting_started.md +11 -0
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +10 -1
- data/docs/interruptions/faults.md +11 -2
- data/docs/interruptions/halt.md +9 -0
- data/docs/logging.md +14 -4
- data/docs/middlewares.md +53 -43
- data/docs/outcomes/result.md +9 -0
- data/docs/outcomes/states.md +9 -0
- data/docs/outcomes/statuses.md +9 -0
- data/docs/parameters/coercions.md +58 -38
- data/docs/parameters/defaults.md +10 -1
- data/docs/parameters/definitions.md +9 -0
- data/docs/parameters/namespacing.md +9 -0
- data/docs/parameters/validations.md +8 -67
- data/docs/testing.md +22 -13
- data/docs/tips_and_tricks.md +9 -0
- data/docs/workflows.md +14 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -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 +57 -171
- data/lib/cmdx/context.rb +22 -165
- 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 +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- 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 +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- 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 +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- 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 +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -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 +46 -339
- 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 +61 -11
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +35 -0
- data/lib/locales/cs.yml +35 -0
- data/lib/locales/da.yml +35 -0
- data/lib/locales/de.yml +35 -0
- data/lib/locales/el.yml +35 -0
- data/lib/locales/en.yml +19 -20
- data/lib/locales/es.yml +19 -20
- data/lib/locales/fi.yml +35 -0
- data/lib/locales/fr.yml +35 -0
- data/lib/locales/he.yml +35 -0
- data/lib/locales/hi.yml +35 -0
- data/lib/locales/it.yml +35 -0
- data/lib/locales/ja.yml +35 -0
- data/lib/locales/ko.yml +35 -0
- data/lib/locales/nl.yml +35 -0
- data/lib/locales/no.yml +35 -0
- data/lib/locales/pl.yml +35 -0
- data/lib/locales/pt.yml +35 -0
- data/lib/locales/ru.yml +35 -0
- data/lib/locales/sv.yml +35 -0
- data/lib/locales/th.yml +35 -0
- data/lib/locales/tr.yml +35 -0
- data/lib/locales/vi.yml +35 -0
- data/lib/locales/zh.yml +35 -0
- metadata +57 -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,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
|
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 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
|
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
|
-
# @
|
70
|
-
# struct = LazyStruct.new(name: "John", age: 30)
|
16
|
+
# @return [LazyStruct] a new LazyStruct instance
|
71
17
|
#
|
72
|
-
# @
|
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
|
77
|
-
# struct = LazyStruct.new
|
78
|
-
# struct.name
|
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
|
-
# @
|
96
|
-
#
|
97
|
-
#
|
98
|
-
# struct
|
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
|
108
|
-
# @param args [Array] default value
|
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
|
-
# @
|
113
|
-
# struct.fetch!(:name) #=> "John"
|
55
|
+
# @return [Object] the value associated with the key, or default if not found
|
114
56
|
#
|
115
|
-
# @
|
116
|
-
# struct.fetch!(:missing, "default") #=> "default"
|
57
|
+
# @raise [KeyError] if key is not found and no default is provided
|
117
58
|
#
|
118
|
-
# @example
|
119
|
-
# struct.
|
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
|
122
|
-
# struct.fetch!(:missing)
|
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.
|
136
|
-
# struct.store!("
|
137
|
-
# struct
|
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
|
-
#
|
146
|
-
#
|
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
|
-
# @
|
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.
|
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
|
-
# @
|
168
|
-
#
|
169
|
-
#
|
170
|
-
# struct.
|
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
|
-
# @
|
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
|
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
|
198
|
-
# @param keys [Array
|
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
|
-
# @
|
203
|
-
#
|
204
|
-
#
|
205
|
-
# struct.
|
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
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
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
|
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
|
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
|
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
|
261
|
-
# @
|
197
|
+
# @param args [Array] method arguments
|
198
|
+
# @param _kwargs [Hash] keyword arguments (unused)
|
199
|
+
# @param block [Proc] optional block (unused)
|
262
200
|
#
|
263
|
-
# @
|
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
|
268
|
-
# struct
|
269
|
-
#
|
270
|
-
#
|
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
|
-
# @
|
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
|
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
|
-
# @
|
306
|
-
# symbolized_key("name") #=> :name
|
307
|
-
# symbolized_key(:name) #=> :name
|
308
|
-
# symbolized_key("123") #=> :"123"
|
228
|
+
# @return [Symbol] the symbolized key
|
309
229
|
#
|
310
|
-
# @
|
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
|
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
|
-
# # => {"severity"
|
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
|
-
# # => "severity=INFO pid=
|
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
|
-
# # => "I, [
|
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)
|