cmdx 1.1.2 → 1.5.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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. 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
@@ -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
@@ -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
@@ -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