cmdx 0.5.0 → 1.0.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +31 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +203 -31
  23. data/docs/outcomes/statuses.md +275 -30
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +367 -25
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +405 -37
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -62
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -35
  121. data/lib/cmdx/run.rb +0 -39
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -20
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,8 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result inspection utility for generating human-readable result descriptions.
5
+ #
6
+ # The ResultInspector module provides functionality to convert result hash
7
+ # representations into formatted, human-readable strings. It handles special
8
+ # formatting for different result attributes and provides consistent ordering
9
+ # of result information.
10
+ #
11
+ # @example Basic result inspection
12
+ # result_hash = {
13
+ # class: "ProcessOrderTask",
14
+ # type: "Task",
15
+ # index: 0,
16
+ # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
17
+ # state: "complete",
18
+ # status: "success",
19
+ # outcome: "success",
20
+ # metadata: { order_id: 123 },
21
+ # runtime: 0.5
22
+ # }
23
+ #
24
+ # ResultInspector.call(result_hash)
25
+ # # => "ProcessOrderTask: type=Task index=0 id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=complete status=success outcome=success metadata={order_id: 123} runtime=0.5"
26
+ #
27
+ # @example Result with failure information
28
+ # failed_result = {
29
+ # class: "ProcessOrderTask",
30
+ # type: "Task",
31
+ # index: 1,
32
+ # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
33
+ # state: "interrupted",
34
+ # status: "failed",
35
+ # outcome: "failed",
36
+ # caused_failure: { index: 0, class: "ValidationTask", id: "018c2b95..." },
37
+ # threw_failure: { index: 0, class: "ValidationTask", id: "018c2b95..." }
38
+ # }
39
+ #
40
+ # ResultInspector.call(failed_result)
41
+ # # => "ProcessOrderTask: type=Task index=1 id=018c2b95... state=interrupted status=failed outcome=failed caused_failure=<[0] ValidationTask: 018c2b95...> threw_failure=<[0] ValidationTask: 018c2b95...>"
42
+ #
43
+ # @see CMDx::Result Result hash serialization via to_h
44
+ # @see CMDx::ResultSerializer Result-to-hash conversion
4
45
  module ResultInspector
5
46
 
47
+ # Ordered keys for consistent result inspection output.
48
+ #
49
+ # Defines the order in which result attributes are displayed in the
50
+ # inspection string, ensuring consistent and logical presentation.
6
51
  ORDERED_KEYS = %i[
7
52
  class type index id state status outcome metadata
8
53
  tags pid runtime caused_failure threw_failure
@@ -10,6 +55,37 @@ module CMDx
10
55
 
11
56
  module_function
12
57
 
58
+ # Converts a result hash to a human-readable string representation.
59
+ #
60
+ # Formats result data into a structured string with proper ordering and
61
+ # special handling for different attribute types. The class name appears
62
+ # first followed by a colon, and failure references are specially formatted.
63
+ #
64
+ # @param result [Hash] The result hash to inspect
65
+ # @return [String] Formatted result description
66
+ #
67
+ # @example Simple result inspection
68
+ # ResultInspector.call(result_hash)
69
+ # # => "ProcessOrderTask: type=Task index=0 id=018c2b95... state=complete status=success"
70
+ #
71
+ # @example Result with metadata
72
+ # result_with_metadata = { class: "MyTask", metadata: { user_id: 123, action: "create" } }
73
+ # ResultInspector.call(result_with_metadata)
74
+ # # => "MyTask: metadata={user_id: 123, action: create}"
75
+ #
76
+ # @example Result with failure references
77
+ # result_with_failures = {
78
+ # class: "MainTask",
79
+ # caused_failure: { index: 2, class: "SubTask", id: "abc123" },
80
+ # threw_failure: { index: 1, class: "HelperTask", id: "def456" }
81
+ # }
82
+ # ResultInspector.call(result_with_failures)
83
+ # # => "MainTask: caused_failure=<[2] SubTask: abc123> threw_failure=<[1] HelperTask: def456>"
84
+ #
85
+ # @example Result with runtime information
86
+ # result_with_runtime = { class: "SlowTask", runtime: 2.5, pid: 1234 }
87
+ # ResultInspector.call(result_with_runtime)
88
+ # # => "SlowTask: runtime=2.5 pid=1234"
13
89
  def call(result)
14
90
  ORDERED_KEYS.filter_map do |key|
15
91
  next unless result.key?(key)
@@ -1,16 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result-specific logging module for task execution outcomes.
5
+ #
6
+ # The ResultLogger module provides specialized logging functionality for
7
+ # CMDx task results. It automatically maps result statuses to appropriate
8
+ # log severity levels and handles conditional logging based on logger
9
+ # availability and configuration.
10
+ #
11
+ # @example Successful task result logging
12
+ # task = ProcessOrderTask.call(order_id: 123)
13
+ # ResultLogger.call(task.result)
14
+ # # Logs at INFO level: "ProcessOrderTask completed successfully"
15
+ #
16
+ # @example Failed task result logging
17
+ # task = ProcessOrderTask.call(invalid_params)
18
+ # ResultLogger.call(task.result)
19
+ # # Logs at ERROR level: "ProcessOrderTask failed with errors"
20
+ #
21
+ # @example Skipped task result logging
22
+ # task = ProcessOrderTask.new
23
+ # task.skip!(reason: "Order already processed")
24
+ # ResultLogger.call(task.result)
25
+ # # Logs at WARN level: "ProcessOrderTask was skipped"
26
+ #
27
+ # @example Integration with task execution
28
+ # class ProcessOrderTask < CMDx::Task
29
+ # def call
30
+ # # Task logic here
31
+ # end
32
+ #
33
+ # # ResultLogger.call is automatically invoked after task execution
34
+ # end
35
+ #
36
+ # @see CMDx::Result Result object status and state management
37
+ # @see CMDx::Logger Logger configuration and setup
38
+ # @see CMDx::Task Task execution and result handling
4
39
  module ResultLogger
5
40
 
41
+ # Mapping of result statuses to corresponding log severity levels.
42
+ #
43
+ # Maps CMDx result status constants to Ruby Logger severity levels
44
+ # to ensure appropriate logging levels for different task outcomes.
6
45
  STATUS_TO_SEVERITY = {
7
- Result::SUCCESS => :info,
8
- Result::SKIPPED => :warn,
9
- Result::FAILED => :error
46
+ Result::SUCCESS => :info, # Successful task completion
47
+ Result::SKIPPED => :warn, # Task was skipped
48
+ Result::FAILED => :error # Task execution failed
10
49
  }.freeze
11
50
 
12
51
  module_function
13
52
 
53
+ # Logs a task result at the appropriate severity level.
54
+ #
55
+ # Determines the appropriate log severity based on the result status
56
+ # and logs the result object using the task's configured logger.
57
+ # Does nothing if no logger is configured for the task.
58
+ #
59
+ # @param result [CMDx::Result] The task result to log
60
+ # @return [void]
61
+ #
62
+ # @example Logging a successful result
63
+ # task = ProcessOrderTask.call(order_id: 123)
64
+ # ResultLogger.call(task.result)
65
+ # # Logs at INFO level with result details
66
+ #
67
+ # @example Logging a failed result
68
+ # task = ProcessOrderTask.new
69
+ # task.fail!(reason: "Invalid order ID")
70
+ # ResultLogger.call(task.result)
71
+ # # Logs at ERROR level with failure details
72
+ #
73
+ # @example Logging a skipped result
74
+ # task = ProcessOrderTask.new
75
+ # task.skip!(reason: "Order already processed")
76
+ # ResultLogger.call(task.result)
77
+ # # Logs at WARN level with skip reason
78
+ #
79
+ # @example No logger configured
80
+ # class SimpleTask < CMDx::Task
81
+ # # No logger setting
82
+ # end
83
+ #
84
+ # task = SimpleTask.call
85
+ # ResultLogger.call(task.result) # Does nothing - no logger available
86
+ #
87
+ # @example Custom logger configuration
88
+ # class MyTask < CMDx::Task
89
+ # task_settings!(
90
+ # logger: Logger.new(STDOUT),
91
+ # log_formatter: CMDx::LogFormatters::Json.new
92
+ # )
93
+ # end
94
+ #
95
+ # task = MyTask.call
96
+ # ResultLogger.call(task.result) # Logs in JSON format to STDOUT
97
+ #
98
+ # @note This method is typically called automatically by the CMDx framework
99
+ # after task execution completes, ensuring that all task results are
100
+ # properly logged according to their outcome.
14
101
  def call(result)
15
102
  logger = result.task.send(:logger)
16
103
  return if logger.nil?
@@ -1,8 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result serialization utility for converting Result objects to hash representations.
5
+ #
6
+ # The ResultSerializer module provides functionality to serialize Result instances
7
+ # into structured hash representations suitable for inspection, logging, debugging,
8
+ # and data interchange. It handles failure chain information and integrates with
9
+ # TaskSerializer for comprehensive result data.
10
+ #
11
+ # @example Basic result serialization
12
+ # task = ProcessOrderTask.call(order_id: 123)
13
+ # result = task.result
14
+ #
15
+ # ResultSerializer.call(result)
16
+ # # => {
17
+ # # class: "ProcessOrderTask",
18
+ # # type: "Task",
19
+ # # index: 0,
20
+ # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
21
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
22
+ # # tags: [],
23
+ # # state: "complete",
24
+ # # status: "success",
25
+ # # outcome: "success",
26
+ # # metadata: {},
27
+ # # runtime: 0.5
28
+ # # }
29
+ #
30
+ # @example Failed result serialization
31
+ # task = ProcessOrderTask.new
32
+ # task.fail!(reason: "Invalid order data", code: 422)
33
+ # result = task.result
34
+ #
35
+ # ResultSerializer.call(result)
36
+ # # => {
37
+ # # class: "ProcessOrderTask",
38
+ # # type: "Task",
39
+ # # index: 0,
40
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
41
+ # # state: "interrupted",
42
+ # # status: "failed",
43
+ # # outcome: "failed",
44
+ # # metadata: { reason: "Invalid order data", code: 422 },
45
+ # # runtime: 0.1,
46
+ # # caused_failure: { ... }, # Failure chain information
47
+ # # threw_failure: { ... }
48
+ # # }
49
+ #
50
+ # @example Result with failure chain
51
+ # # When a result has failure chain information, it's included but
52
+ # # stripped of recursive caused_failure/threw_failure to prevent cycles
53
+ # ResultSerializer.call(result_with_failures)
54
+ # # => {
55
+ # # # ... standard result data ...
56
+ # # caused_failure: {
57
+ # # class: "ValidationTask",
58
+ # # index: 1,
59
+ # # state: "interrupted",
60
+ # # status: "failed"
61
+ # # # caused_failure and threw_failure are stripped to prevent recursion
62
+ # # },
63
+ # # threw_failure: {
64
+ # # class: "ProcessingTask",
65
+ # # index: 2,
66
+ # # state: "interrupted",
67
+ # # status: "failed"
68
+ # # # caused_failure and threw_failure are stripped to prevent recursion
69
+ # # }
70
+ # # }
71
+ #
72
+ # @see CMDx::Result Result object creation and state management
73
+ # @see CMDx::TaskSerializer Task serialization functionality
74
+ # @see CMDx::ResultInspector Human-readable result formatting
4
75
  module ResultSerializer
5
76
 
77
+ # Proc for stripping failure chain information to prevent recursion.
78
+ #
79
+ # This proc is used to include failure chain information (caused_failure
80
+ # and threw_failure) while preventing infinite recursion by stripping
81
+ # the same fields from nested failure objects.
6
82
  STRIP_FAILURE = proc do |h, r, k|
7
83
  unless r.send(:"#{k}?")
8
84
  # Strip caused/threw failures since its the same info as the log line
@@ -12,6 +88,67 @@ module CMDx
12
88
 
13
89
  module_function
14
90
 
91
+ # Converts a Result object to a hash representation.
92
+ #
93
+ # Serializes a Result instance into a structured hash containing all
94
+ # relevant result information including task data, execution state,
95
+ # status, metadata, runtime, and failure chain information.
96
+ #
97
+ # @param result [CMDx::Result] The result object to serialize
98
+ # @return [Hash] Structured hash representation of the result
99
+ #
100
+ # @example Successful result serialization
101
+ # result = ProcessOrderTask.call(order_id: 123).result
102
+ # ResultSerializer.call(result)
103
+ # # => {
104
+ # # class: "ProcessOrderTask",
105
+ # # type: "Task",
106
+ # # index: 0,
107
+ # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
108
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
109
+ # # tags: [],
110
+ # # state: "complete",
111
+ # # status: "success",
112
+ # # outcome: "success",
113
+ # # metadata: {},
114
+ # # runtime: 0.5
115
+ # # }
116
+ #
117
+ # @example Failed result with metadata
118
+ # task = ProcessOrderTask.new
119
+ # task.fail!(reason: "Validation failed", errors: ["Invalid email"])
120
+ # result = task.result
121
+ #
122
+ # ResultSerializer.call(result)
123
+ # # => {
124
+ # # class: "ProcessOrderTask",
125
+ # # type: "Task",
126
+ # # index: 0,
127
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
128
+ # # state: "interrupted",
129
+ # # status: "failed",
130
+ # # outcome: "failed",
131
+ # # metadata: { reason: "Validation failed", errors: ["Invalid email"] },
132
+ # # runtime: 0.1
133
+ # # }
134
+ #
135
+ # @example Skipped result serialization
136
+ # task = ProcessOrderTask.new
137
+ # task.skip!(reason: "Order already processed")
138
+ # result = task.result
139
+ #
140
+ # ResultSerializer.call(result)
141
+ # # => {
142
+ # # class: "ProcessOrderTask",
143
+ # # type: "Task",
144
+ # # index: 0,
145
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
146
+ # # state: "interrupted",
147
+ # # status: "skipped",
148
+ # # outcome: "skipped",
149
+ # # metadata: { reason: "Order already processed" },
150
+ # # runtime: 0.05
151
+ # # }
15
152
  def call(result)
16
153
  TaskSerializer.call(result.task).tap do |hash|
17
154
  hash.merge!(