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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +13 -12
- data/.cursor/prompts/yardoc.md +11 -6
- data/CHANGELOG.md +13 -2
- data/README.md +1 -0
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +124 -58
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +390 -94
- data/docs/configuration.md +181 -65
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +150 -125
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +181 -118
- data/docs/middlewares.md +150 -377
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +232 -281
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +260 -133
- data/docs/testing.md +191 -197
- data/docs/workflows.md +143 -98
- data/lib/cmdx/callback.rb +23 -19
- data/lib/cmdx/callback_registry.rb +1 -3
- data/lib/cmdx/chain_inspector.rb +23 -23
- data/lib/cmdx/chain_serializer.rb +38 -19
- data/lib/cmdx/coercion.rb +20 -12
- data/lib/cmdx/coercion_registry.rb +51 -32
- data/lib/cmdx/configuration.rb +84 -31
- data/lib/cmdx/context.rb +32 -21
- data/lib/cmdx/core_ext/hash.rb +13 -13
- data/lib/cmdx/core_ext/module.rb +1 -1
- data/lib/cmdx/core_ext/object.rb +12 -12
- data/lib/cmdx/correlator.rb +60 -39
- data/lib/cmdx/errors.rb +105 -131
- data/lib/cmdx/fault.rb +66 -45
- data/lib/cmdx/immutator.rb +20 -21
- data/lib/cmdx/lazy_struct.rb +78 -70
- data/lib/cmdx/log_formatters/json.rb +1 -1
- data/lib/cmdx/log_formatters/key_value.rb +1 -1
- data/lib/cmdx/log_formatters/line.rb +1 -1
- data/lib/cmdx/log_formatters/logstash.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
- data/lib/cmdx/log_formatters/raw.rb +2 -2
- data/lib/cmdx/logger.rb +19 -14
- data/lib/cmdx/logger_ansi.rb +33 -17
- data/lib/cmdx/logger_serializer.rb +85 -24
- data/lib/cmdx/middleware.rb +39 -21
- data/lib/cmdx/middleware_registry.rb +4 -3
- data/lib/cmdx/parameter.rb +151 -89
- data/lib/cmdx/parameter_inspector.rb +34 -21
- data/lib/cmdx/parameter_registry.rb +36 -30
- data/lib/cmdx/parameter_serializer.rb +21 -14
- data/lib/cmdx/result.rb +136 -135
- data/lib/cmdx/result_ansi.rb +31 -17
- data/lib/cmdx/result_inspector.rb +32 -27
- data/lib/cmdx/result_logger.rb +23 -14
- data/lib/cmdx/result_serializer.rb +65 -27
- data/lib/cmdx/task.rb +234 -113
- data/lib/cmdx/task_deprecator.rb +22 -25
- data/lib/cmdx/task_processor.rb +89 -88
- data/lib/cmdx/task_serializer.rb +27 -14
- data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
- data/lib/cmdx/validator.rb +25 -16
- data/lib/cmdx/validator_registry.rb +53 -31
- data/lib/cmdx/validators/exclusion.rb +1 -1
- data/lib/cmdx/validators/format.rb +2 -2
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +2 -2
- data/lib/cmdx/validators/numeric.rb +3 -3
- data/lib/cmdx/validators/presence.rb +2 -2
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +54 -33
- data/lib/generators/cmdx/task_generator.rb +6 -6
- data/lib/generators/cmdx/workflow_generator.rb +6 -6
- 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
|
4
|
+
# Logger management module for configuring and retrieving task-specific loggers.
|
5
5
|
#
|
6
|
-
# This module provides functionality to
|
7
|
-
#
|
8
|
-
#
|
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
|
-
#
|
16
|
-
#
|
17
|
-
# The
|
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
|
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
|
-
#
|
25
|
-
#
|
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
|
-
#
|
28
|
-
# task.set_cmd_setting(:log_formatter, custom_formatter)
|
33
|
+
# task = MyTask.call
|
29
34
|
# logger = CMDx::Logger.call(task)
|
30
|
-
# logger
|
35
|
+
# #=> Returns configured logger with DEBUG level and JSON formatter
|
31
36
|
def call(task)
|
32
37
|
logger = task.cmd_setting(:logger)
|
33
38
|
|
data/lib/cmdx/logger_ansi.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# ANSI color formatting
|
4
|
+
# ANSI color formatting for logger severity levels and text output.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
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
|
22
|
+
# Applies ANSI color formatting to text based on severity level indication.
|
22
23
|
#
|
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.
|
24
28
|
#
|
25
|
-
# @
|
29
|
+
# @param s [String] the text to format, typically starting with a severity indicator
|
26
30
|
#
|
27
|
-
# @
|
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
|
31
|
-
#
|
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
|
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
|
-
# @
|
53
|
+
# @return [Symbol] the color symbol corresponding to the severity level, or :default if not found
|
39
54
|
#
|
40
|
-
# @
|
55
|
+
# @example Get color for debug severity
|
56
|
+
# LoggerAnsi.color("DEBUG: Message") #=> :blue
|
41
57
|
#
|
42
|
-
# @example Get color for
|
43
|
-
#
|
58
|
+
# @example Get color for error severity
|
59
|
+
# LoggerAnsi.color("ERROR: Failed") #=> :red
|
44
60
|
#
|
45
61
|
# @example Get color for unknown severity
|
46
|
-
#
|
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
|
-
#
|
4
|
+
# Logger serialization module for converting messages and task data into structured log format.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# hash
|
8
|
-
#
|
9
|
-
#
|
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
|
-
#
|
19
|
+
# Serializes a log message with task context into structured hash format.
|
19
20
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
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
|
25
|
-
# @param
|
26
|
-
# @param task [CMDx::Task]
|
27
|
-
# @param message [Object]
|
28
|
-
# @param options [Hash]
|
29
|
-
# @option options [Boolean] :ansi_colorize
|
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]
|
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
|
-
# @
|
34
|
-
#
|
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
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
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)
|
data/lib/cmdx/middleware.rb
CHANGED
@@ -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
|
4
|
+
# Base class for implementing middleware functionality in task processing pipelines.
|
5
5
|
#
|
6
|
-
# Middleware provides a way to wrap task execution with custom
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
-
#
|
15
|
-
#
|
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
|
-
# @
|
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
|
-
#
|
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
|
30
|
-
# Subclasses must override this method to provide their specific
|
31
|
-
#
|
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
|
34
|
-
# @param _callable [Proc] the callable
|
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
|
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
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
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
|
-
#
|
82
|
+
# #=> { TimeoutMiddleware => [[30], {}, nil] }
|
82
83
|
def to_h
|
83
84
|
registry.transform_values do |config|
|
84
85
|
args, kwargs, block = config
|