cmdx 1.1.2 → 1.5.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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +55 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/CHANGELOG.md +11 -132
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +101 -162
- data/lib/cmdx/validators/numeric.rb +95 -170
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -67
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -58
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
data/lib/cmdx/fault.rb
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# Base fault class for handling task execution failures and interruptions.
|
5
|
-
#
|
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.
|
11
|
-
class Fault < Error
|
12
|
-
|
13
|
-
cmdx_attr_delegator :task, :chain, :context,
|
14
|
-
to: :result
|
15
|
-
|
16
|
-
# @return [CMDx::Result] the result object that caused this fault
|
17
|
-
attr_reader :result
|
18
|
-
|
19
|
-
# Creates a new fault instance from a task execution result.
|
20
|
-
#
|
21
|
-
# @param result [CMDx::Result] the task result that caused the fault
|
22
|
-
#
|
23
|
-
# @return [CMDx::Fault] the newly created fault instance
|
24
|
-
#
|
25
|
-
# @example Create fault from failed task result
|
26
|
-
# result = SomeTask.call(invalid_data: true)
|
27
|
-
# fault = CMDx::Fault.new(result)
|
28
|
-
# fault.task #=> SomeTask instance
|
29
|
-
def initialize(result)
|
30
|
-
@result = result
|
31
|
-
super(result.metadata[:reason] || I18n.t("cmdx.faults.unspecified", default: "no reason given"))
|
32
|
-
end
|
33
|
-
|
34
|
-
class << self
|
35
|
-
|
36
|
-
# Builds a specific fault type based on the result's status.
|
37
|
-
#
|
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.
|
41
|
-
#
|
42
|
-
# @param result [CMDx::Result] the task result to build a fault from
|
43
|
-
#
|
44
|
-
# @return [CMDx::Fault] an instance of the appropriate fault subclass
|
45
|
-
#
|
46
|
-
# @raise [NameError] if no fault class exists for the result status
|
47
|
-
#
|
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
|
55
|
-
# fault = CMDx::Fault.build(result)
|
56
|
-
# fault.class #=> CMDx::Failed
|
57
|
-
def build(result)
|
58
|
-
fault = CMDx.const_get(result.status.capitalize)
|
59
|
-
fault.new(result)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Creates a fault matcher that matches faults from specific task classes.
|
63
|
-
#
|
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
|
-
#
|
68
|
-
# @param tasks [Array<Class>] one or more task classes to match against
|
69
|
-
#
|
70
|
-
# @return [Class] a fault matcher class that responds to case equality
|
71
|
-
#
|
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)
|
86
|
-
# end
|
87
|
-
def for?(*tasks)
|
88
|
-
temp_fault = Class.new(self) do
|
89
|
-
def self.===(other)
|
90
|
-
other.is_a?(superclass) && @tasks.any? { |task| other.task.is_a?(task) }
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
temp_fault.tap { |c| c.instance_variable_set(:@tasks, tasks) }
|
95
|
-
end
|
96
|
-
|
97
|
-
# Creates a fault matcher using a custom block for matching criteria.
|
98
|
-
#
|
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.
|
103
|
-
#
|
104
|
-
# @param block [Proc] a block that receives a fault and returns boolean
|
105
|
-
#
|
106
|
-
# @return [Class] a fault matcher class that responds to case equality
|
107
|
-
#
|
108
|
-
# @raise [ArgumentError] if no block is provided
|
109
|
-
#
|
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)
|
124
|
-
# end
|
125
|
-
def matches?(&block)
|
126
|
-
raise ArgumentError, "block required" unless block_given?
|
127
|
-
|
128
|
-
temp_fault = Class.new(self) do
|
129
|
-
def self.===(other)
|
130
|
-
other.is_a?(superclass) && @block.call(other)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
temp_fault.tap { |c| c.instance_variable_set(:@block, block) }
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
end
|
140
|
-
end
|
data/lib/cmdx/immutator.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# Provides object immutability functionality for tasks and their associated objects.
|
5
|
-
#
|
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
|
-
module Immutator
|
11
|
-
|
12
|
-
module_function
|
13
|
-
|
14
|
-
# Freezes a task and its associated objects to prevent further modification.
|
15
|
-
#
|
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
|
-
#
|
21
|
-
# @param task [CMDx::Task] the task instance to freeze along with its associated objects
|
22
|
-
#
|
23
|
-
# @return [void] returns nil when freezing is skipped, otherwise no meaningful return value
|
24
|
-
#
|
25
|
-
# @example Freeze a task after execution
|
26
|
-
# task = MyTask.call(user_id: 123)
|
27
|
-
# CMDx::Immutator.call(task)
|
28
|
-
# task.frozen? #=> true
|
29
|
-
# task.result.frozen? #=> true
|
30
|
-
#
|
31
|
-
# @example Skip freezing during testing
|
32
|
-
# ENV["SKIP_CMDX_FREEZING"] = "true"
|
33
|
-
# task = MyTask.call(user_id: 123)
|
34
|
-
# CMDx::Immutator.call(task)
|
35
|
-
# task.frozen? #=> false
|
36
|
-
def call(task)
|
37
|
-
# Stubbing on frozen objects is not allowed
|
38
|
-
skip_freezing = ENV.fetch("SKIP_CMDX_FREEZING", false)
|
39
|
-
return if Coercions::Boolean.call(skip_freezing)
|
40
|
-
|
41
|
-
task.freeze
|
42
|
-
task.result.freeze
|
43
|
-
return unless task.result.index.zero?
|
44
|
-
|
45
|
-
task.context.freeze
|
46
|
-
task.chain.freeze
|
47
|
-
|
48
|
-
Chain.clear
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
data/lib/cmdx/lazy_struct.rb
DELETED
@@ -1,246 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# Flexible struct-like object with symbol-based attribute access and dynamic assignment.
|
5
|
-
#
|
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.
|
9
|
-
class LazyStruct
|
10
|
-
|
11
|
-
# Creates a new LazyStruct instance with the provided attributes.
|
12
|
-
#
|
13
|
-
# @param args [Hash, #to_h] initial attributes for the struct
|
14
|
-
#
|
15
|
-
# @return [LazyStruct] a new LazyStruct instance
|
16
|
-
#
|
17
|
-
# @raise [ArgumentError] if args doesn't respond to to_h
|
18
|
-
#
|
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"
|
26
|
-
def initialize(args = {})
|
27
|
-
unless args.respond_to?(:to_h)
|
28
|
-
raise ArgumentError,
|
29
|
-
"must be respond to `to_h`"
|
30
|
-
end
|
31
|
-
|
32
|
-
@table = args.to_h.transform_keys { |k| symbolized_key(k) }
|
33
|
-
end
|
34
|
-
|
35
|
-
# Retrieves the value for the specified key.
|
36
|
-
#
|
37
|
-
# @param key [Symbol, String] the key to look up
|
38
|
-
#
|
39
|
-
# @return [Object, nil] the value associated with the key, or nil if not found
|
40
|
-
#
|
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"
|
48
|
-
def [](key)
|
49
|
-
table[symbolized_key(key)]
|
50
|
-
end
|
51
|
-
|
52
|
-
# Retrieves the value for the specified key or returns/yields a default.
|
53
|
-
#
|
54
|
-
# @param key [Symbol, String] the key to look up
|
55
|
-
# @param args [Array] additional arguments passed to Hash#fetch
|
56
|
-
#
|
57
|
-
# @return [Object] the value associated with the key, or default value
|
58
|
-
#
|
59
|
-
# @raise [KeyError] if key is not found and no default is provided
|
60
|
-
#
|
61
|
-
# @example Fetch with default value
|
62
|
-
# struct = LazyStruct.new(name: "John")
|
63
|
-
# struct.fetch!(:age, 25) #=> 25
|
64
|
-
#
|
65
|
-
# @example Fetch with block default
|
66
|
-
# struct.fetch!(:missing) { "default" } #=> "default"
|
67
|
-
def fetch!(key, ...)
|
68
|
-
table.fetch(symbolized_key(key), ...)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Stores a value for the specified key.
|
72
|
-
#
|
73
|
-
# @param key [Symbol, String] the key to store the value under
|
74
|
-
# @param value [Object] the value to store
|
75
|
-
#
|
76
|
-
# @return [Object] the stored value
|
77
|
-
#
|
78
|
-
# @example Store a value
|
79
|
-
# struct = LazyStruct.new
|
80
|
-
# struct.store!(:name, "John") #=> "John"
|
81
|
-
# struct.name #=> "John"
|
82
|
-
def store!(key, value)
|
83
|
-
table[symbolized_key(key)] = value
|
84
|
-
end
|
85
|
-
alias []= store!
|
86
|
-
|
87
|
-
# Merges the provided arguments into the struct's attributes.
|
88
|
-
#
|
89
|
-
# @param args [Hash, #to_h] attributes to merge into the struct
|
90
|
-
#
|
91
|
-
# @return [LazyStruct] self for method chaining
|
92
|
-
#
|
93
|
-
# @example Merge attributes
|
94
|
-
# struct = LazyStruct.new(name: "John")
|
95
|
-
# struct.merge!(age: 30, city: "NYC")
|
96
|
-
# struct.age #=> 30
|
97
|
-
def merge!(args = {})
|
98
|
-
args.to_h.each { |key, value| store!(symbolized_key(key), value) }
|
99
|
-
self
|
100
|
-
end
|
101
|
-
|
102
|
-
# Deletes the specified key from the struct.
|
103
|
-
#
|
104
|
-
# @param key [Symbol, String] the key to delete
|
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
|
113
|
-
#
|
114
|
-
# @example Delete with default block
|
115
|
-
# struct.delete!(:missing) { "not found" } #=> "not found"
|
116
|
-
def delete!(key, &)
|
117
|
-
table.delete(symbolized_key(key), &)
|
118
|
-
end
|
119
|
-
alias delete_field! delete!
|
120
|
-
|
121
|
-
# Checks equality with another object.
|
122
|
-
#
|
123
|
-
# @param other [Object] the object to compare against
|
124
|
-
#
|
125
|
-
# @return [Boolean] true if other is a LazyStruct with identical attributes
|
126
|
-
#
|
127
|
-
# @example Compare structs
|
128
|
-
# struct1 = LazyStruct.new(name: "John")
|
129
|
-
# struct2 = LazyStruct.new(name: "John")
|
130
|
-
# struct1.eql?(struct2) #=> true
|
131
|
-
def eql?(other)
|
132
|
-
other.is_a?(self.class) && (to_h == other.to_h)
|
133
|
-
end
|
134
|
-
alias == eql?
|
135
|
-
|
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
|
147
|
-
def dig(key, *keys)
|
148
|
-
table.dig(symbolized_key(key), *keys)
|
149
|
-
end
|
150
|
-
|
151
|
-
# Iterates over each key-value pair in the struct.
|
152
|
-
#
|
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
|
156
|
-
#
|
157
|
-
# @example Iterate over pairs
|
158
|
-
# struct = LazyStruct.new(name: "John", age: 30)
|
159
|
-
# struct.each_pair { |key, value| puts "#{key}: #{value}" }
|
160
|
-
# # Output: name: John
|
161
|
-
# # age: 30
|
162
|
-
def each_pair(&)
|
163
|
-
table.each_pair(&)
|
164
|
-
end
|
165
|
-
|
166
|
-
# Converts the struct to a hash representation.
|
167
|
-
#
|
168
|
-
# @param block [Proc] optional block for transforming key-value pairs
|
169
|
-
#
|
170
|
-
# @return [Hash] a hash containing all the struct's attributes
|
171
|
-
#
|
172
|
-
# @example Convert to hash
|
173
|
-
# struct = LazyStruct.new(name: "John", age: 30)
|
174
|
-
# struct.to_h #=> { name: "John", age: 30 }
|
175
|
-
#
|
176
|
-
# @example Convert with transformation
|
177
|
-
# struct.to_h { |k, v| [k.to_s, v.to_s] } #=> { "name" => "John", "age" => "30" }
|
178
|
-
def to_h(&)
|
179
|
-
table.to_h(&)
|
180
|
-
end
|
181
|
-
|
182
|
-
# Returns a string representation of the struct for debugging.
|
183
|
-
#
|
184
|
-
# @return [String] a formatted string showing the class name and attributes
|
185
|
-
#
|
186
|
-
# @example Inspect struct
|
187
|
-
# struct = LazyStruct.new(name: "John", age: 30)
|
188
|
-
# struct.inspect #=> "#<CMDx::LazyStruct :name=\"John\" :age=30>"
|
189
|
-
def inspect
|
190
|
-
"#<#{self.class.name}#{table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
|
191
|
-
end
|
192
|
-
alias to_s inspect
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
# Returns the internal hash table storing the struct's attributes.
|
197
|
-
#
|
198
|
-
# @return [Hash] the internal attribute storage
|
199
|
-
def table
|
200
|
-
@table ||= {}
|
201
|
-
end
|
202
|
-
|
203
|
-
# Handles dynamic method calls for attribute access and assignment.
|
204
|
-
#
|
205
|
-
# @param method_name [Symbol] the method name being called
|
206
|
-
# @param args [Array] arguments passed to the method
|
207
|
-
# @param _kwargs [Hash] keyword arguments (unused)
|
208
|
-
# @param block [Proc] block passed to the method (unused)
|
209
|
-
#
|
210
|
-
# @return [Object, nil] the attribute value for getters, or the assigned value for setters
|
211
|
-
#
|
212
|
-
# @example Dynamic attribute access
|
213
|
-
# struct = LazyStruct.new(name: "John")
|
214
|
-
# struct.name #=> "John"
|
215
|
-
# struct.age = 30 #=> 30
|
216
|
-
def method_missing(method_name, *args, **_kwargs, &)
|
217
|
-
table.fetch(symbolized_key(method_name)) do
|
218
|
-
store!(method_name[0..-2], args.first) if method_name.end_with?("=")
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Checks if the struct responds to a method name.
|
223
|
-
#
|
224
|
-
# @param method_name [Symbol] the method name to check
|
225
|
-
# @param include_private [Boolean] whether to include private methods
|
226
|
-
#
|
227
|
-
# @return [Boolean] true if the struct has the attribute or responds to the method
|
228
|
-
def respond_to_missing?(method_name, include_private = false)
|
229
|
-
table.key?(symbolized_key(method_name)) || super
|
230
|
-
end
|
231
|
-
|
232
|
-
# Converts a key to a symbol for consistent internal storage.
|
233
|
-
#
|
234
|
-
# @param key [Symbol, String, Object] the key to convert
|
235
|
-
#
|
236
|
-
# @return [Symbol] the symbolized key
|
237
|
-
#
|
238
|
-
# @raise [TypeError] if the key cannot be converted to a symbol
|
239
|
-
def symbolized_key(key)
|
240
|
-
key.to_sym
|
241
|
-
rescue NoMethodError
|
242
|
-
raise TypeError, "#{key} is not a symbol nor a string"
|
243
|
-
end
|
244
|
-
|
245
|
-
end
|
246
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
module LogFormatters
|
5
|
-
# Pretty JSON log formatter that outputs structured log entries as formatted JSON.
|
6
|
-
#
|
7
|
-
# This formatter converts log entries into pretty-printed JSON format with proper
|
8
|
-
# indentation and line breaks, including metadata such as severity, process ID,
|
9
|
-
# and timestamp. Each log entry is output as a multi-line JSON structure followed
|
10
|
-
# by a newline character, making it human-readable while maintaining structure.
|
11
|
-
class PrettyJson
|
12
|
-
|
13
|
-
# Formats a log entry as a pretty-printed JSON string.
|
14
|
-
#
|
15
|
-
# @param severity [String] the log severity level (e.g., "INFO", "ERROR")
|
16
|
-
# @param time [Time] the timestamp when the log entry was created
|
17
|
-
# @param task [Object] the task object associated with the log entry
|
18
|
-
# @param message [String] the log message content
|
19
|
-
#
|
20
|
-
# @return [String] the formatted pretty JSON log entry with trailing newline
|
21
|
-
#
|
22
|
-
# @raise [JSON::GeneratorError] if the log data cannot be serialized to JSON
|
23
|
-
#
|
24
|
-
# @example Formatting a log entry
|
25
|
-
# formatter = CMDx::LogFormatters::PrettyJson.new
|
26
|
-
# result = formatter.call("INFO", Time.now, task_object, "Task completed")
|
27
|
-
# #=> "{\n \"severity\": \"INFO\",\n \"pid\": 12345,\n \"timestamp\": \"2024-01-01T12:00:00Z\",\n \"message\": \"Task completed\"\n}\n"
|
28
|
-
def call(severity, time, task, message)
|
29
|
-
m = LoggerSerializer.call(severity, time, task, message).merge!(
|
30
|
-
severity:,
|
31
|
-
pid: Process.pid,
|
32
|
-
timestamp: Utils::LogTimestamp.call(time.utc)
|
33
|
-
)
|
34
|
-
|
35
|
-
JSON.pretty_generate(m) << "\n"
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
module LogFormatters
|
5
|
-
# Pretty key-value log formatter that outputs structured log entries as human-readable key=value pairs.
|
6
|
-
#
|
7
|
-
# This formatter converts log entries into a space-separated key=value format with ANSI coloring
|
8
|
-
# for enhanced readability in terminal output. Each log entry includes metadata such as severity,
|
9
|
-
# process ID, and timestamp, with each entry terminated by a newline character.
|
10
|
-
class PrettyKeyValue
|
11
|
-
|
12
|
-
# Formats a log entry as a colorized key=value string.
|
13
|
-
#
|
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
|
18
|
-
#
|
19
|
-
# @return [String] the formatted key=value log entry with ANSI colors and trailing newline
|
20
|
-
#
|
21
|
-
# @example Formatting a log entry
|
22
|
-
# formatter = CMDx::LogFormatters::PrettyKeyValue.new
|
23
|
-
# result = formatter.call("INFO", Time.now, task_object, "Task completed")
|
24
|
-
# #=> "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
|
25
|
-
def call(severity, time, task, message)
|
26
|
-
m = LoggerSerializer.call(severity, time, task, message, ansi_colorize: true).merge!(
|
27
|
-
severity:,
|
28
|
-
pid: Process.pid,
|
29
|
-
timestamp: Utils::LogTimestamp.call(time.utc)
|
30
|
-
)
|
31
|
-
|
32
|
-
m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
|
33
|
-
m << "\n"
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
module LogFormatters
|
5
|
-
# Pretty line log formatter that outputs human-readable log entries with ANSI colors.
|
6
|
-
#
|
7
|
-
# This formatter converts log entries into a traditional log line format with
|
8
|
-
# color-coded severity levels, timestamps, and process information. The output
|
9
|
-
# is designed to be easily readable in terminal environments that support ANSI
|
10
|
-
# color codes.
|
11
|
-
class PrettyLine
|
12
|
-
|
13
|
-
# Formats a log entry as a colorized human-readable line.
|
14
|
-
#
|
15
|
-
# @param severity [String] the log severity level (e.g., "INFO", "ERROR")
|
16
|
-
# @param time [Time] the timestamp when the log entry was created
|
17
|
-
# @param task [Object] the task object associated with the log entry
|
18
|
-
# @param message [String] the log message content
|
19
|
-
#
|
20
|
-
# @return [String] the formatted log line with ANSI colors and trailing newline
|
21
|
-
#
|
22
|
-
# @raise [NoMethodError] if the task object doesn't respond to class or name methods
|
23
|
-
# @raise [StandardError] if LoggerSerializer, LoggerAnsi, or LogTimestamp fail
|
24
|
-
#
|
25
|
-
# @example Formatting a log entry
|
26
|
-
# formatter = CMDx::LogFormatters::PrettyLine.new
|
27
|
-
# result = formatter.call("INFO", Time.now, task_object, "Task completed")
|
28
|
-
# #=> "\e[32mI\e[0m, [2024-01-01T12:00:00.000Z #12345] \e[32mINFO\e[0m -- MyTask: Task completed\n"
|
29
|
-
def call(severity, time, task, message)
|
30
|
-
i = LoggerAnsi.call(severity[0])
|
31
|
-
s = LoggerAnsi.call(severity)
|
32
|
-
t = Utils::LogTimestamp.call(time.utc)
|
33
|
-
m = LoggerSerializer.call(severity, time, task, message, ansi_colorize: true)
|
34
|
-
m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
|
35
|
-
|
36
|
-
"#{i}, [#{t} ##{Process.pid}] #{s} -- #{task.class.name}: #{m}\n"
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/cmdx/logger.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# Logger management module for configuring and retrieving task-specific loggers.
|
5
|
-
#
|
6
|
-
# This module provides functionality to extract and configure logger instances
|
7
|
-
# from task settings, applying formatter, level, and progname configurations
|
8
|
-
# when available. It serves as a central point for logger setup during task execution.
|
9
|
-
module Logger
|
10
|
-
|
11
|
-
module_function
|
12
|
-
|
13
|
-
# Configures and returns a logger instance for the given task.
|
14
|
-
#
|
15
|
-
# Extracts the logger from task settings and applies additional configuration
|
16
|
-
# such as formatter, log level, and progname if they are specified in the
|
17
|
-
# task's command settings. The progname is set to the task instance itself
|
18
|
-
# for better log traceability.
|
19
|
-
#
|
20
|
-
# @param task [Task] the task instance containing logger configuration settings
|
21
|
-
#
|
22
|
-
# @return [Logger, nil] the configured logger instance, or nil if no logger is set
|
23
|
-
#
|
24
|
-
# @example Configure logger for a task
|
25
|
-
# class MyTask < CMDx::Task
|
26
|
-
# cmd setting!(
|
27
|
-
# logger: Logger.new($stdout),
|
28
|
-
# log_level: Logger::DEBUG,
|
29
|
-
# log_formatter: CMDx::LogFormatters::JSON.new
|
30
|
-
# )
|
31
|
-
# end
|
32
|
-
#
|
33
|
-
# task = MyTask.call
|
34
|
-
# logger = CMDx::Logger.call(task)
|
35
|
-
# #=> Returns configured logger with DEBUG level and JSON formatter
|
36
|
-
def call(task)
|
37
|
-
logger = task.cmd_setting(:logger)
|
38
|
-
|
39
|
-
unless logger.nil?
|
40
|
-
logger.formatter = task.cmd_setting(:log_formatter) if task.cmd_setting?(:log_formatter)
|
41
|
-
logger.level = task.cmd_setting(:log_level) if task.cmd_setting?(:log_level)
|
42
|
-
logger.progname = task
|
43
|
-
end
|
44
|
-
|
45
|
-
logger
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
data/lib/cmdx/logger_ansi.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# ANSI color formatting for logger severity levels and text output.
|
5
|
-
#
|
6
|
-
# LoggerAnsi provides utility methods for applying ANSI color codes to logger
|
7
|
-
# severity indicators and general text formatting. It maps standard logger
|
8
|
-
# severity levels to appropriate colors for enhanced readability in terminal output,
|
9
|
-
# delegating actual color application to the AnsiColor utility module.
|
10
|
-
module LoggerAnsi
|
11
|
-
|
12
|
-
SEVERITY_COLORS = {
|
13
|
-
"D" => :blue, # DEBUG
|
14
|
-
"I" => :green, # INFO
|
15
|
-
"W" => :yellow, # WARN
|
16
|
-
"E" => :red, # ERROR
|
17
|
-
"F" => :magenta # FATAL
|
18
|
-
}.freeze
|
19
|
-
|
20
|
-
module_function
|
21
|
-
|
22
|
-
# Applies ANSI color formatting to text based on severity level indication.
|
23
|
-
#
|
24
|
-
# This method extracts the color for the given text based on its first character
|
25
|
-
# (typically a severity indicator) and applies both the determined color and bold
|
26
|
-
# formatting using the AnsiColor utility. The method provides consistent color
|
27
|
-
# formatting for logger output across the CMDx framework.
|
28
|
-
#
|
29
|
-
# @param s [String] the text to format, typically starting with a severity indicator
|
30
|
-
#
|
31
|
-
# @return [String] the formatted text with ANSI color and bold styling applied
|
32
|
-
#
|
33
|
-
# @example Format debug severity text
|
34
|
-
# LoggerAnsi.call("DEBUG: Starting process") #=> "\e[1;34;49mDEBUG: Starting process\e[0m"
|
35
|
-
#
|
36
|
-
# @example Format error severity text
|
37
|
-
# LoggerAnsi.call("ERROR: Operation failed") #=> "\e[1;31;49mERROR: Operation failed\e[0m"
|
38
|
-
#
|
39
|
-
# @example Format text with unknown severity
|
40
|
-
# LoggerAnsi.call("CUSTOM: Message") #=> "\e[1;39;49mCUSTOM: Message\e[0m"
|
41
|
-
def call(s)
|
42
|
-
Utils::AnsiColor.call(s, color: color(s), mode: :bold)
|
43
|
-
end
|
44
|
-
|
45
|
-
# Determines the appropriate color for text based on its severity indicator.
|
46
|
-
#
|
47
|
-
# This method extracts the first character from the provided text and maps it
|
48
|
-
# to a corresponding color defined in SEVERITY_COLORS. If no matching severity
|
49
|
-
# is found, it returns the default color to ensure consistent formatting behavior.
|
50
|
-
#
|
51
|
-
# @param s [String] the text to analyze, typically starting with a severity indicator
|
52
|
-
#
|
53
|
-
# @return [Symbol] the color symbol corresponding to the severity level, or :default if not found
|
54
|
-
#
|
55
|
-
# @example Get color for debug severity
|
56
|
-
# LoggerAnsi.color("DEBUG: Message") #=> :blue
|
57
|
-
#
|
58
|
-
# @example Get color for error severity
|
59
|
-
# LoggerAnsi.color("ERROR: Failed") #=> :red
|
60
|
-
#
|
61
|
-
# @example Get color for unknown severity
|
62
|
-
# LoggerAnsi.color("UNKNOWN: Text") #=> :default
|
63
|
-
def color(s)
|
64
|
-
SEVERITY_COLORS[s[0]] || :default
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|