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