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.
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 +55 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +11 -132
  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 +101 -162
  85. data/lib/cmdx/validators/numeric.rb +95 -170
  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
@@ -2,36 +2,35 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # JSON log formatter that outputs structured log entries as JSON.
5
+ # Formats log messages as JSON for structured logging
6
6
  #
7
- # This formatter converts log entries into JSON format, including metadata
8
- # such as severity, process ID, and timestamp. Each log entry is output as
9
- # a single line of JSON followed by a newline character.
10
- class Json
7
+ # This formatter converts log entries into JSON format with standardized fields
8
+ # including severity, timestamp, program name, process ID, and formatted message.
9
+ # The output is suitable for log aggregation systems and structured analysis.
10
+ class JSON
11
11
 
12
- # Formats a log entry as a JSON string.
12
+ # Formats a log entry as a JSON string
13
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
14
+ # @param severity [String] The log level (e.g., "INFO", "ERROR", "DEBUG")
15
+ # @param time [Time] The timestamp when the log entry was created
16
+ # @param progname [String, nil] The program name or identifier
17
+ # @param message [Object] The log message content
18
18
  #
19
- # @return [String] the formatted JSON log entry with trailing newline
19
+ # @return [String] A JSON-formatted log entry with a trailing newline
20
20
  #
21
- # @raise [JSON::GeneratorError] if the log data cannot be serialized to JSON
22
- #
23
- # @example Formatting a log entry
24
- # formatter = CMDx::LogFormatters::Json.new
25
- # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # #=> "{\"severity\":\"INFO\",\"pid\":12345,\"timestamp\":\"2024-01-01T12:00:00Z\",\"message\":\"Task completed\"}\n"
27
- def call(severity, time, task, message)
28
- m = LoggerSerializer.call(severity, time, task, message).merge!(
21
+ # @example Basic usage
22
+ # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
+ # # => '{"severity":"INFO","timestamp":"2024-01-15T10:30:45.123456Z","progname":"MyApp","pid":12345,"message":"User logged in"}\n'
24
+ def call(severity, time, progname, message)
25
+ hash = {
29
26
  severity:,
27
+ timestamp: time.utc.iso8601(6),
28
+ progname:,
30
29
  pid: Process.pid,
31
- timestamp: Utils::LogTimestamp.call(time.utc)
32
- )
30
+ message: Utils::Format.to_log(message)
31
+ }
33
32
 
34
- JSON.dump(m) << "\n"
33
+ ::JSON.dump(hash) << "\n"
35
34
  end
36
35
 
37
36
  end
@@ -2,37 +2,35 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Key-value log formatter that outputs structured log entries as key=value pairs.
5
+ # Formats log messages as key-value pairs for structured logging
6
6
  #
7
- # This formatter converts log entries into key-value format, including metadata
8
- # such as severity, process ID, and timestamp. Each log entry is output as
9
- # space-separated key=value pairs followed by a newline character.
7
+ # This formatter converts log entries into key-value format with standardized fields
8
+ # including severity, timestamp, program name, process ID, and formatted message.
9
+ # The output is suitable for log parsing tools and human-readable structured logs.
10
10
  class KeyValue
11
11
 
12
- # Formats a log entry as a key=value string.
12
+ # Formats a log entry as a key-value string
13
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
14
+ # @param severity [String] The log level (e.g., "INFO", "ERROR", "DEBUG")
15
+ # @param time [Time] The timestamp when the log entry was created
16
+ # @param progname [String, nil] The program name or identifier
17
+ # @param message [Object] The log message content
18
18
  #
19
- # @return [String] the formatted key=value log entry with trailing newline
19
+ # @return [String] A key-value formatted log entry with a trailing newline
20
20
  #
21
- # @raise [StandardError] if the log data cannot be serialized to key=value format
22
- #
23
- # @example Formatting a log entry
24
- # formatter = CMDx::LogFormatters::KeyValue.new
25
- # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # #=> "severity=INFO pid=12345 timestamp=2024-01-01T12:00:00Z message=Task completed\n"
27
- def call(severity, time, task, message)
28
- m = LoggerSerializer.call(severity, time, task, message).merge!(
21
+ # @example Basic usage
22
+ # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
+ # # => "severity=INFO timestamp=2024-01-15T10:30:45.123456Z progname=MyApp pid=12345 message=User logged in\n"
24
+ def call(severity, time, progname, message)
25
+ hash = {
29
26
  severity:,
27
+ timestamp: time.utc.iso8601(6),
28
+ progname:,
30
29
  pid: Process.pid,
31
- timestamp: Utils::LogTimestamp.call(time.utc)
32
- )
30
+ message: Utils::Format.to_log(message)
31
+ }
33
32
 
34
- m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
35
- m << "\n"
33
+ Utils::Format.to_str(hash) << "\n"
36
34
  end
37
35
 
38
36
  end
@@ -2,34 +2,27 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Line log formatter that outputs log entries in a traditional line format.
5
+ # Formats log messages as single-line text for human-readable logging
6
6
  #
7
- # This formatter converts log entries into a human-readable line format,
8
- # including metadata such as severity, process ID, and timestamp. Each log
9
- # entry is output as a single line with structured information.
7
+ # This formatter converts log entries into a compact single-line format with
8
+ # severity abbreviation, ISO8601 timestamp, process ID, and formatted message.
9
+ # The output is optimized for human readability and traditional log file formats.
10
10
  class Line
11
11
 
12
- # Formats a log entry as a line string.
12
+ # Formats a log entry as a single-line string
13
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
14
+ # @param severity [String] The log level (e.g., "INFO", "ERROR", "DEBUG")
15
+ # @param time [Time] The timestamp when the log entry was created
16
+ # @param progname [String, nil] The program name or identifier
17
+ # @param message [Object] The log message content
18
18
  #
19
- # @return [String] the formatted line log entry with trailing newline
19
+ # @return [String] A single-line formatted log entry with a trailing newline
20
20
  #
21
- # @raise [NoMethodError] if the task object doesn't respond to expected methods
22
- #
23
- # @example Formatting a log entry
24
- # formatter = CMDx::LogFormatters::Line.new
25
- # result = formatter.call("INFO", Time.now, task_object, "Task completed")
26
- # #=> "I, [2024-01-01T12:00:00.000Z #12345] INFO -- TaskClass: Task completed\n"
27
- def call(severity, time, task, message)
28
- t = Utils::LogTimestamp.call(time.utc)
29
- m = LoggerSerializer.call(severity, time, task, message)
30
- m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
31
-
32
- "#{severity[0]}, [#{t} ##{Process.pid}] #{severity} -- #{task.class.name}: #{m}\n"
21
+ # @example Basic usage
22
+ # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
+ # # => "I, [2024-01-15T10:30:45.123456Z #12345] INFO -- MyApp: User logged in\n"
24
+ def call(severity, time, progname, message)
25
+ "#{severity[0]}, [#{time.utc.iso8601(6)} ##{Process.pid}] #{severity} -- #{progname}: #{message}\n"
33
26
  end
34
27
 
35
28
  end
@@ -2,38 +2,37 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Logstash log formatter that outputs structured log entries in Logstash JSON format.
5
+ # Formats log messages as Logstash-compatible JSON for structured logging
6
6
  #
7
- # This formatter converts log entries into Logstash-compatible JSON format, including
8
- # required Logstash fields such as @version and @timestamp, along with metadata
9
- # such as severity and process ID. Each log entry is output as a single line of
10
- # JSON followed by a newline character.
7
+ # This formatter converts log entries into Logstash-compatible JSON format with
8
+ # standardized fields including @version, @timestamp, severity, program name,
9
+ # process ID, and formatted message. The output follows Logstash event format
10
+ # specifications for seamless integration with ELK stack and similar systems.
11
11
  class Logstash
12
12
 
13
- # Formats a log entry as a Logstash-compatible JSON string.
13
+ # Formats a log entry as a Logstash-compatible JSON string
14
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
15
+ # @param severity [String] The log level (e.g., "INFO", "ERROR", "DEBUG")
16
+ # @param time [Time] The timestamp when the log entry was created
17
+ # @param progname [String, nil] The program name or identifier
18
+ # @param message [Object] The log message content
19
19
  #
20
- # @return [String] the formatted Logstash JSON log entry with trailing newline
20
+ # @return [String] A Logstash-compatible JSON-formatted log entry with a trailing newline
21
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::Logstash.new
26
- # result = formatter.call("INFO", Time.now, task_object, "Task completed")
27
- # #=> "{\"severity\":\"INFO\",\"pid\":12345,\"@version\":\"1\",\"@timestamp\":\"2024-01-01T12:00:00.000Z\",\"message\":\"Task completed\"}\n"
28
- def call(severity, time, task, message)
29
- m = LoggerSerializer.call(severity, time, task, message).merge!(
22
+ # @example Basic usage
23
+ # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
24
+ # # => '{"@version":"1","@timestamp":"2024-01-15T10:30:45.123456Z","severity":"INFO","progname":"MyApp","pid":12345,"message":"User logged in"}\n'
25
+ def call(severity, time, progname, message)
26
+ hash = {
27
+ "@version" => "1",
28
+ "@timestamp" => time.utc.iso8601(6),
30
29
  severity:,
30
+ progname:,
31
31
  pid: Process.pid,
32
- "@version" => "1",
33
- "@timestamp" => Utils::LogTimestamp.call(time.utc)
34
- )
32
+ message: Utils::Format.to_log(message)
33
+ }
35
34
 
36
- JSON.dump(m) << "\n"
35
+ ::JSON.dump(hash) << "\n"
37
36
  end
38
37
 
39
38
  end
@@ -2,34 +2,28 @@
2
2
 
3
3
  module CMDx
4
4
  module LogFormatters
5
- # Raw log formatter that outputs log messages using inspect format.
5
+ # Formats log messages as raw text without additional formatting
6
6
  #
7
- # This formatter provides a simple, unstructured log output by calling
8
- # inspect on the message content. It ignores severity, time, and task
9
- # metadata, focusing only on the raw message content. Each log entry
10
- # is output as an inspected string followed by a newline character.
7
+ # This formatter outputs log messages in their original form with minimal
8
+ # processing, adding only a trailing newline. It's useful for scenarios
9
+ # where you want to preserve the exact message content without metadata
10
+ # or structured formatting.
11
11
  class Raw
12
12
 
13
- # Formats a log entry as an inspected string.
13
+ # Formats a log entry as raw text
14
14
  #
15
- # @param _severity [String] the log severity level (ignored)
16
- # @param _time [Time] the timestamp when the log entry was created (ignored)
17
- # @param _task [Object] the task object associated with the log entry (ignored)
18
- # @param message [Object] the log message content to be inspected
15
+ # @param severity [String] The log level (e.g., "INFO", "ERROR", "DEBUG")
16
+ # @param time [Time] The timestamp when the log entry was created
17
+ # @param progname [String, nil] The program name or identifier
18
+ # @param message [Object] The log message content
19
19
  #
20
- # @return [String] the inspected message with trailing newline
20
+ # @return [String] The raw message with a trailing newline
21
21
  #
22
- # @example Formatting a log entry
23
- # formatter = CMDx::LogFormatters::Raw.new
24
- # result = formatter.call("INFO", Time.now, task_object, "Task completed")
25
- # #=> "\"Task completed\"\n"
26
- #
27
- # @example Formatting a complex object
28
- # formatter = CMDx::LogFormatters::Raw.new
29
- # result = formatter.call("DEBUG", Time.now, task_object, { status: :success, count: 42 })
30
- # #=> "{:status=>:success, :count=>42}\n"
31
- def call(_severity, _time, _task, message)
32
- message.inspect + "\n" # rubocop:disable Style/StringConcatenation
22
+ # @example Basic usage
23
+ # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
24
+ # # => "User logged in\n"
25
+ def call(severity, time, progname, message)
26
+ "#{message}\n"
33
27
  end
34
28
 
35
29
  end
@@ -1,110 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Registry for managing middleware definitions and execution within tasks.
4
+ # Registry for managing middleware components in a task execution chain.
5
5
  #
6
- # This registry handles the registration and execution of middleware that can
7
- # wrap task execution, providing cross-cutting concerns like logging, timing,
8
- # authentication, and error handling.
6
+ # The MiddlewareRegistry maintains an ordered list of middleware components
7
+ # that can be inserted, removed, and executed in sequence. Each middleware
8
+ # can be configured with specific options and is executed in the order
9
+ # they were registered.
9
10
  class MiddlewareRegistry
10
11
 
11
- # @return [Hash] hash containing middleware classes/objects and their configurations
12
12
  attr_reader :registry
13
+ alias to_a registry
13
14
 
14
- # Initializes a new middleware registry.
15
+ # Initialize a new middleware registry.
15
16
  #
16
- # @param registry [Hash] optional hash of initial middleware configurations
17
+ # @param registry [Array] Initial array of middleware entries
17
18
  #
18
- # @return [MiddlewareRegistry] a new middleware registry instance
19
+ # @example
20
+ # registry = MiddlewareRegistry.new
21
+ # registry = MiddlewareRegistry.new([[MyMiddleware, {option: 'value'}]])
22
+ def initialize(registry = [])
23
+ @registry = registry
24
+ end
25
+
26
+ # Create a duplicate of the registry with duplicated middleware entries.
19
27
  #
20
- # @example Creating an empty registry
21
- # MiddlewareRegistry.new
28
+ # @return [MiddlewareRegistry] A new registry instance with duplicated entries
22
29
  #
23
- # @example Creating a registry with initial middleware
24
- # MiddlewareRegistry.new(TimeoutMiddleware => [[], {timeout: 30}, nil])
25
- def initialize(registry = {})
26
- @registry = registry.to_h
30
+ # @example
31
+ # new_registry = registry.dup
32
+ def dup
33
+ self.class.new(registry.map(&:dup))
27
34
  end
28
35
 
29
- # Registers a middleware with the registry.
36
+ # Register a middleware component in the registry.
30
37
  #
31
- # @param middleware [Class, Object] the middleware class or instance to register
32
- # @param args [Array] positional arguments to pass to middleware initialization
33
- # @param kwargs [Hash] keyword arguments to pass to middleware initialization
34
- # @param block [Proc] optional block to pass to middleware initialization
38
+ # @param middleware [Object] The middleware object to register
39
+ # @param at [Integer] Position to insert the middleware (default: -1, end of list)
40
+ # @param options [Hash] Configuration options for the middleware
41
+ # @option options [Symbol] :key Configuration key for the middleware
42
+ # @option options [Object] :value Configuration value for the middleware
35
43
  #
36
- # @return [MiddlewareRegistry] self for method chaining
44
+ # @return [MiddlewareRegistry] Returns self for method chaining
37
45
  #
38
- # @example Register a middleware class
39
- # registry.register(TimeoutMiddleware, 30)
46
+ # @example
47
+ # registry.register(LoggingMiddleware, at: 0, log_level: :debug)
48
+ # registry.register(AuthMiddleware, at: -1, timeout: 30)
49
+ def register(middleware, at: -1, **options)
50
+ registry.insert(at, [middleware, options])
51
+ self
52
+ end
53
+
54
+ # Remove a middleware component from the registry.
55
+ #
56
+ # @param middleware [Object] The middleware object to remove
40
57
  #
41
- # @example Register a middleware with keyword arguments
42
- # registry.register(LoggingMiddleware, level: :info)
58
+ # @return [MiddlewareRegistry] Returns self for method chaining
43
59
  #
44
- # @example Register a middleware with a block
45
- # registry.register(CustomMiddleware) { |task| puts "Processing #{task.id}" }
46
- def register(middleware, *args, **kwargs, &block)
47
- registry[middleware] = [args, kwargs, block]
60
+ # @example
61
+ # registry.deregister(LoggingMiddleware)
62
+ def deregister(middleware)
63
+ registry.reject! { |mw, _opts| mw == middleware }
48
64
  self
49
65
  end
50
66
 
51
- # Executes all registered middleware around the provided task.
67
+ # Execute the middleware chain for a given task.
52
68
  #
53
- # @param task [Task] the task instance to execute middleware around
54
- # @param block [Proc] the block to execute after all middleware processing
69
+ # @param task [Object] The task object to process through middleware
55
70
  #
56
- # @return [Object] the result of the middleware chain execution
71
+ # @yield [task] Block to execute after all middleware processing
72
+ # @yieldparam task [Object] The processed task object
57
73
  #
58
- # @raise [ArgumentError] if no block is provided
74
+ # @return [Object] Result of the block execution
59
75
  #
60
- # @example Execute middleware around a task
61
- # registry.call(task) { |task| task.process }
76
+ # @raise [ArgumentError] When no block is provided
62
77
  #
63
- # @example Execute with early return if no middleware
64
- # registry.call(task) { |task| puts "No middleware to execute" }
65
- def call(task, &)
78
+ # @example
79
+ # result = registry.call!(my_task) do |processed_task|
80
+ # processed_task.execute
81
+ # end
82
+ def call!(task, &)
66
83
  raise ArgumentError, "block required" unless block_given?
67
84
 
68
- return yield(task) if registry.empty?
69
-
70
- build_chain(&).call(task)
71
- end
72
-
73
- # Returns a hash representation of the registry.
74
- #
75
- # @return [Hash] deep copy of registry with duplicated configuration arrays
76
- # @option return [Array] args duplicated positional arguments array
77
- # @option return [Hash] kwargs duplicated keyword arguments hash
78
- # @option return [Proc, nil] block the original block reference (not duplicated)
79
- #
80
- # @example Getting registry hash
81
- # registry.to_h
82
- # #=> { TimeoutMiddleware => [[30], {}, nil] }
83
- def to_h
84
- registry.transform_values do |config|
85
- args, kwargs, block = config
86
- [args.dup, kwargs.dup, block]
87
- end
85
+ recursively_call_middleware(0, task, &)
88
86
  end
89
87
 
90
88
  private
91
89
 
92
- # Builds the middleware execution chain by wrapping middleware around the call block.
93
- #
94
- # @param call_block [Proc] the final block to execute after all middleware
95
- #
96
- # @return [Proc] the complete middleware chain as a callable proc
97
- #
98
- # @example Building a middleware chain (internal use)
99
- # build_chain { |task| task.process }
100
- def build_chain(&call_block)
101
- registry.reverse_each.reduce(call_block) do |next_callable, (middleware, config)|
102
- proc do |task|
103
- args, kwargs, block = config
104
- instance = middleware.respond_to?(:new) ? middleware.new(*args, **kwargs, &block) : middleware
105
- instance.call(task, next_callable)
106
- end
107
- end
90
+ # Recursively execute middleware in the chain.
91
+ #
92
+ # @param index [Integer] Current middleware index in the chain
93
+ # @param task [Object] The task object being processed
94
+ #
95
+ # @yield [task] Block to execute after middleware processing
96
+ # @yieldparam task [Object] The processed task object
97
+ #
98
+ # @return [Object] Result of the block execution or next middleware call
99
+ def recursively_call_middleware(index, task, &block)
100
+ return yield(task) if index >= registry.size
101
+
102
+ middleware, options = registry[index]
103
+ middleware.call(task, **options) { recursively_call_middleware(index + 1, task, &block) }
108
104
  end
109
105
 
110
106
  end