cmdx 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
data/lib/cmdx/logger.rb CHANGED
@@ -1,33 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Logger configuration and retrieval utilities for task execution.
4
+ # Logger management module for configuring and retrieving task-specific loggers.
5
5
  #
6
- # This module provides functionality to configure and retrieve logger instances
7
- # for task execution, applying task-specific settings such as formatter, level,
8
- # and program name when available.
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
9
  module Logger
10
10
 
11
11
  module_function
12
12
 
13
13
  # Configures and returns a logger instance for the given task.
14
14
  #
15
- # This method retrieves the logger from task settings and applies any
16
- # available configuration options including formatter, level, and program name.
17
- # The task itself is set as the logger's program name for identification.
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.
18
19
  #
19
- # @param task [Task] the task instance to configure logging for
20
+ # @param task [Task] the task instance containing logger configuration settings
20
21
  #
21
- # @return [Logger, nil] the configured logger instance or nil if no logger is set
22
+ # @return [Logger, nil] the configured logger instance, or nil if no logger is set
22
23
  #
23
24
  # @example Configure logger for a task
24
- # logger = CMDx::Logger.call(task)
25
- # logger.info("Task started")
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
26
32
  #
27
- # @example Logger with custom formatter
28
- # task.set_cmd_setting(:log_formatter, custom_formatter)
33
+ # task = MyTask.call
29
34
  # logger = CMDx::Logger.call(task)
30
- # logger.debug("Debug message")
35
+ # #=> Returns configured logger with DEBUG level and JSON formatter
31
36
  def call(task)
32
37
  logger = task.cmd_setting(:logger)
33
38
 
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # ANSI color formatting utilities for log severity levels.
4
+ # ANSI color formatting for logger severity levels and text output.
5
5
  #
6
- # This module provides functionality to apply ANSI color codes to log messages
7
- # based on their severity level. It maps standard log severity indicators to
8
- # appropriate colors for enhanced readability in terminal output.
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.
9
10
  module LoggerAnsi
10
11
 
11
12
  SEVERITY_COLORS = {
@@ -18,32 +19,47 @@ module CMDx
18
19
 
19
20
  module_function
20
21
 
21
- # Applies ANSI color formatting to a log message based on its severity level.
22
+ # Applies ANSI color formatting to text based on severity level indication.
22
23
  #
23
- # @param s [String] the log message string to format
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.
24
28
  #
25
- # @return [String] the formatted message with ANSI color codes applied
29
+ # @param s [String] the text to format, typically starting with a severity indicator
26
30
  #
27
- # @example Format a debug message
28
- # CMDx::LoggerAnsi.call("DEBUG: Starting process") #=> "\e[1;34;49mDEBUG: Starting process\e[0m"
31
+ # @return [String] the formatted text with ANSI color and bold styling applied
29
32
  #
30
- # @example Format an error message
31
- # CMDx::LoggerAnsi.call("ERROR: Connection failed") #=> "\e[1;31;49mERROR: Connection failed\e[0m"
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"
32
41
  def call(s)
33
42
  Utils::AnsiColor.call(s, color: color(s), mode: :bold)
34
43
  end
35
44
 
36
- # Determines the appropriate color for a log message based on its severity level.
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
37
52
  #
38
- # @param s [String] the log message string to analyze
53
+ # @return [Symbol] the color symbol corresponding to the severity level, or :default if not found
39
54
  #
40
- # @return [Symbol] the color symbol corresponding to the severity level
55
+ # @example Get color for debug severity
56
+ # LoggerAnsi.color("DEBUG: Message") #=> :blue
41
57
  #
42
- # @example Get color for debug message
43
- # CMDx::LoggerAnsi.color("DEBUG: message") #=> :blue
58
+ # @example Get color for error severity
59
+ # LoggerAnsi.color("ERROR: Failed") #=> :red
44
60
  #
45
61
  # @example Get color for unknown severity
46
- # CMDx::LoggerAnsi.color("UNKNOWN: message") #=> :default
62
+ # LoggerAnsi.color("UNKNOWN: Text") #=> :default
47
63
  def color(s)
48
64
  SEVERITY_COLORS[s[0]] || :default
49
65
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Serializes log messages for structured logging output.
4
+ # Logger serialization module for converting messages and task data into structured log format.
5
5
  #
6
- # This module provides functionality to convert log messages into a structured
7
- # hash format suitable for various logging formatters. It handles special
8
- # processing for Result objects, including optional ANSI colorization of
9
- # specific keys and merging of task serialization data.
6
+ # LoggerSerializer provides functionality to serialize task execution messages into a
7
+ # standardized hash representation suitable for logging systems. It handles both result
8
+ # objects and arbitrary messages, applying consistent formatting with optional ANSI
9
+ # colorization for terminal output. The serializer intelligently processes different
10
+ # message types and enriches log data with task metadata and origin information.
10
11
  module LoggerSerializer
11
12
 
12
13
  COLORED_KEYS = %i[
@@ -15,30 +16,90 @@ module CMDx
15
16
 
16
17
  module_function
17
18
 
18
- # Converts a log message into a structured hash format.
19
+ # Serializes a log message with task context into structured hash format.
19
20
  #
20
- # Processes the message based on its type - if it's a Result object,
21
- # optionally colorizes specific keys. For non-Result messages, merges
22
- # task serialization data and the original message.
21
+ # Converts log messages into a standardized hash representation suitable for
22
+ # various logging systems and output formats. When the message is a Result object,
23
+ # it extracts the result's hash representation and optionally applies ANSI colors
24
+ # to specific keys for enhanced terminal visibility. For non-result messages,
25
+ # it enriches the log entry with task metadata from TaskSerializer. All log
26
+ # entries are tagged with CMDx origin for source identification.
23
27
  #
24
- # @param _severity [String] The log severity level (unused but kept for compatibility)
25
- # @param _time [Time] The log timestamp (unused but kept for compatibility)
26
- # @param task [CMDx::Task] The task instance associated with the log message
27
- # @param message [Object] The message to be serialized (can be a Result or any object)
28
- # @param options [Hash] Additional options for serialization
29
- # @option options [Boolean] :ansi_colorize Whether to apply ANSI colorization to Result objects
28
+ # @param severity [Symbol] the log severity level (not used in current implementation)
29
+ # @param time [Time] the timestamp of the log entry (not used in current implementation)
30
+ # @param task [CMDx::Task, CMDx::Workflow] the task or workflow instance providing context
31
+ # @param message [CMDx::Result, Object] the primary message content to serialize
32
+ # @param options [Hash] additional options for serialization behavior
33
+ # @option options [Boolean] :ansi_colorize whether to apply ANSI colors to result keys
30
34
  #
31
- # @return [Hash] A structured hash representation of the log message with origin set to "CMDx"
35
+ # @return [Hash] a structured hash containing the serialized log message and metadata
36
+ # @option return [String] :origin always set to "CMDx" for source identification
37
+ # @option return [Integer] :index the task's position index in the execution chain (when message is not Result)
38
+ # @option return [String] :chain_id the unique identifier of the task's execution chain (when message is not Result)
39
+ # @option return [String] :type the task type, either "Task" or "Workflow" (when message is not Result)
40
+ # @option return [String] :class the full class name of the task (when message is not Result)
41
+ # @option return [String] :id the unique identifier of the task instance (when message is not Result)
42
+ # @option return [Array] :tags the tags associated with the task from cmd settings (when message is not Result)
43
+ # @option return [Object] :message the original message content (when message is not Result)
44
+ # @option return [Symbol] :state the execution state with optional ANSI colors (when message is Result)
45
+ # @option return [Symbol] :status the execution status with optional ANSI colors (when message is Result)
46
+ # @option return [Symbol] :outcome the execution outcome with optional ANSI colors (when message is Result)
47
+ # @option return [Hash] :metadata additional metadata from result (when message is Result)
48
+ # @option return [Float] :runtime execution runtime in seconds (when message is Result)
32
49
  #
33
- # @example Serializing a Result object with colorization
34
- # result = CMDx::Result.new(task)
35
- # LoggerSerializer.call("info", Time.now, task, result, ansi_colorize: true)
36
- # # => { state: "\e[32msuccess\e[0m", status: "complete", origin: "CMDx", ... }
50
+ # @raise [NoMethodError] if task doesn't respond to required methods for TaskSerializer
51
+ # @raise [NoMethodError] if result message doesn't respond to to_h method
37
52
  #
38
- # @example Serializing a plain message
39
- # LoggerSerializer.call("info", Time.now, task, "Processing user data")
40
- # # => { index: 1, chain_id: "abc123", type: "Task", message: "Processing user data", origin: "CMDx", ... }
41
- def call(_severity, _time, task, message, **options)
53
+ # @example Serialize a result message with ANSI colors
54
+ # task = ProcessDataTask.call(data: "test")
55
+ # LoggerSerializer.call(:info, Time.now, task, task.result, ansi_colorize: true)
56
+ # #=> {
57
+ # # origin: "CMDx",
58
+ # # index: 0,
59
+ # # chain_id: "abc123",
60
+ # # type: "Task",
61
+ # # class: "ProcessDataTask",
62
+ # # id: "def456",
63
+ # # tags: [],
64
+ # # state: "\e[0;32;49mcomplete\e[0m",
65
+ # # status: "\e[0;32;49msuccess\e[0m",
66
+ # # outcome: "\e[0;32;49mgood\e[0m",
67
+ # # metadata: {},
68
+ # # runtime: 0.045
69
+ # # }
70
+ #
71
+ # @example Serialize a string message with task context
72
+ # task = MyTask.new(context: {data: "test"})
73
+ # LoggerSerializer.call(:warn, Time.now, task, "Processing started")
74
+ # #=> {
75
+ # # origin: "CMDx",
76
+ # # index: 0,
77
+ # # chain_id: "abc123",
78
+ # # type: "Task",
79
+ # # class: "MyTask",
80
+ # # id: "def456",
81
+ # # tags: [],
82
+ # # message: "Processing started"
83
+ # # }
84
+ #
85
+ # @example Serialize a result message without colors
86
+ # task = ValidationTask.call(email: "invalid")
87
+ # LoggerSerializer.call(:error, Time.now, task, task.result)
88
+ # #=> {
89
+ # # origin: "CMDx",
90
+ # # index: 1,
91
+ # # chain_id: "xyz789",
92
+ # # type: "Task",
93
+ # # class: "ValidationTask",
94
+ # # id: "ghi012",
95
+ # # tags: [],
96
+ # # state: :interrupted,
97
+ # # status: :failed,
98
+ # # outcome: :bad,
99
+ # # metadata: { reason: "Invalid email format" },
100
+ # # runtime: 0.012
101
+ # # }
102
+ def call(severity, time, task, message, **options) # rubocop:disable Lint/UnusedMethodArgument
42
103
  m = message.is_a?(Result) ? message.to_h : {}
43
104
 
44
105
  if options.delete(:ansi_colorize) && message.is_a?(Result)
@@ -1,48 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Base class for implementing middleware functionality in task execution.
4
+ # Base class for implementing middleware functionality in task processing pipelines.
5
5
  #
6
- # Middleware provides a way to wrap task execution with custom behavior
7
- # such as logging, timing, authentication, or other cross-cutting concerns.
8
- # All middleware implementations must inherit from this class and implement
9
- # the abstract call method.
6
+ # Middleware provides a way to wrap task execution with custom logic that runs before
7
+ # and after task processing. Middleware can be used for cross-cutting concerns such as
8
+ # logging, authentication, caching, error handling, and other aspects that should be
9
+ # applied consistently across multiple tasks. All middleware implementations must
10
+ # inherit from this class and implement the abstract call method.
10
11
  class Middleware
11
12
 
12
13
  # Executes middleware by creating a new instance and calling it.
13
14
  #
14
- # @param task [Task] the task instance being wrapped by the middleware
15
- # @param callable [Proc] the callable object to execute within the middleware
15
+ # This class method provides a convenient way to execute middleware without
16
+ # manually instantiating the middleware class. It creates a new instance
17
+ # and delegates to the instance call method with the provided arguments.
16
18
  #
17
- # @return [Object] the result of the middleware execution
19
+ # @param task [CMDx::Task] the task instance being processed
20
+ # @param callable [Proc] the callable that executes the next middleware or task logic
21
+ #
22
+ # @return [Object] the result returned by the middleware implementation
18
23
  #
19
24
  # @raise [UndefinedCallError] when the middleware subclass doesn't implement call
20
25
  #
21
26
  # @example Execute middleware on a task
22
- # MyMiddleware.call(task, -> { task.process })
27
+ # class LoggingMiddleware < CMDx::Middleware
28
+ # def call(task, callable)
29
+ # task.logger.info "Starting #{task.class.name}"
30
+ # result = callable.call
31
+ # task.logger.info "Completed #{task.class.name}"
32
+ # result
33
+ # end
34
+ # end
35
+ #
36
+ # LoggingMiddleware.call(my_task, -> { my_task.process })
23
37
  def self.call(task, callable)
24
38
  new.call(task, callable)
25
39
  end
26
40
 
27
41
  # Abstract method that must be implemented by middleware subclasses.
28
42
  #
29
- # This method contains the actual middleware logic to be executed.
30
- # Subclasses must override this method to provide their specific
31
- # middleware implementation that wraps the callable execution.
43
+ # This method contains the actual middleware logic that wraps task execution.
44
+ # Subclasses must override this method to provide their specific middleware
45
+ # implementation. The method should call the provided callable to continue
46
+ # the middleware chain or execute the task logic.
32
47
  #
33
- # @param _task [Task] the task instance being wrapped by the middleware
34
- # @param _callable [Proc] the callable object to execute within the middleware
48
+ # @param _task [CMDx::Task] the task instance being processed
49
+ # @param _callable [Proc] the callable that executes the next middleware or task logic
35
50
  #
36
- # @return [Object] the result of the middleware execution
51
+ # @return [Object] the result of the middleware processing
37
52
  #
38
53
  # @raise [UndefinedCallError] always raised in the base class
39
54
  #
40
- # @example Implement in a subclass
41
- # def call(task, callable)
42
- # puts "Before #{task.class.name} execution"
43
- # result = callable.call
44
- # puts "After #{task.class.name} execution"
45
- # result
55
+ # @example Implement middleware in a subclass
56
+ # class TimingMiddleware < CMDx::Middleware
57
+ # def call(task, callable)
58
+ # start_time = Time.now
59
+ # result = callable.call
60
+ # duration = Time.now - start_time
61
+ # task.logger.info "Task completed in #{duration}s"
62
+ # result
63
+ # end
46
64
  # end
47
65
  def call(_task, _callable)
48
66
  raise UndefinedCallError, "call method not defined in #{self.class.name}"
@@ -8,8 +8,6 @@ module CMDx
8
8
  # authentication, and error handling.
9
9
  class MiddlewareRegistry
10
10
 
11
- # The internal hash storing middleware definitions and their configurations.
12
- #
13
11
  # @return [Hash] hash containing middleware classes/objects and their configurations
14
12
  attr_reader :registry
15
13
 
@@ -75,10 +73,13 @@ module CMDx
75
73
  # Returns a hash representation of the registry.
76
74
  #
77
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)
78
79
  #
79
80
  # @example Getting registry hash
80
81
  # registry.to_h
81
- # # => { TimeoutMiddleware => [[30], {}, nil] }
82
+ # #=> { TimeoutMiddleware => [[30], {}, nil] }
82
83
  def to_h
83
84
  registry.transform_values do |config|
84
85
  args, kwargs, block = config