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/coercion.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Base class for implementing
|
4
|
+
# Base class for implementing parameter coercion functionality in task processing.
|
5
5
|
#
|
6
|
-
# Coercions are used to convert parameter values from one type to another
|
7
|
-
#
|
6
|
+
# Coercions are used to convert parameter values from one type to another during
|
7
|
+
# task execution, enabling automatic type conversion and normalization. All coercion
|
8
8
|
# implementations must inherit from this class and implement the abstract call method.
|
9
9
|
class Coercion
|
10
10
|
|
@@ -16,10 +16,13 @@ module CMDx
|
|
16
16
|
# @return [Object] the coerced value
|
17
17
|
#
|
18
18
|
# @raise [UndefinedCallError] when the coercion subclass doesn't implement call
|
19
|
+
# @raise [CoercionError] when coercion fails in subclass implementations
|
19
20
|
#
|
20
21
|
# @example Execute a coercion on a value
|
21
|
-
#
|
22
|
-
#
|
22
|
+
# StringCoercion.call(123) #=> "123"
|
23
|
+
#
|
24
|
+
# @example Execute with options
|
25
|
+
# CustomCoercion.call("value", strict: true) #=> processed_value
|
23
26
|
def self.call(value, options = {})
|
24
27
|
new.call(value, options)
|
25
28
|
end
|
@@ -27,21 +30,26 @@ module CMDx
|
|
27
30
|
# Abstract method that must be implemented by coercion subclasses.
|
28
31
|
#
|
29
32
|
# This method contains the actual coercion logic to convert the input
|
30
|
-
# value to the desired type. Subclasses must override this method
|
31
|
-
# provide their specific coercion implementation.
|
33
|
+
# value to the desired type. Subclasses must override this method
|
34
|
+
# to provide their specific coercion implementation.
|
32
35
|
#
|
33
|
-
# @param
|
34
|
-
# @param
|
36
|
+
# @param value [Object] the value to be coerced (unused in base class)
|
37
|
+
# @param options [Hash] additional options for the coercion (unused in base class)
|
35
38
|
#
|
36
39
|
# @return [Object] the coerced value
|
37
40
|
#
|
38
41
|
# @raise [UndefinedCallError] always raised in the base class
|
42
|
+
# @raise [CoercionError] when coercion fails in subclass implementations
|
39
43
|
#
|
40
44
|
# @example Implement in a subclass
|
41
|
-
#
|
42
|
-
#
|
45
|
+
# class StringCoercion < CMDx::Coercion
|
46
|
+
# def call(value, _options = {})
|
47
|
+
# String(value)
|
48
|
+
# rescue ArgumentError, TypeError
|
49
|
+
# raise CoercionError, "could not coerce into a string"
|
50
|
+
# end
|
43
51
|
# end
|
44
|
-
def call(
|
52
|
+
def call(value, options = {}) # rubocop:disable Lint/UnusedMethodArgument
|
45
53
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
46
54
|
end
|
47
55
|
|
@@ -1,28 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Registry for managing type coercion
|
4
|
+
# Registry for managing parameter type coercion functionality.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# CoercionRegistry provides a centralized system for storing, accessing, and
|
7
|
+
# executing type coercions during task parameter processing. It maintains an
|
8
|
+
# internal registry of coercion type keys mapped to their corresponding coercion
|
9
|
+
# classes or callables, supporting both built-in framework coercions and custom
|
10
|
+
# user-defined coercions for flexible type conversion during task execution.
|
9
11
|
class CoercionRegistry
|
10
12
|
|
11
|
-
# The internal hash storing coercion definitions.
|
12
|
-
#
|
13
13
|
# @return [Hash] hash containing coercion type keys and coercion class/callable values
|
14
14
|
attr_reader :registry
|
15
15
|
|
16
|
-
#
|
16
|
+
# Creates a new coercion registry with built-in coercion types.
|
17
17
|
#
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# Initializes the registry with all standard framework coercions including
|
19
|
+
# primitive types (string, integer, float, boolean), date/time types,
|
20
|
+
# collection types (array, hash), numeric types (big_decimal, rational, complex),
|
21
|
+
# and the virtual coercion type for parameter definitions without type conversion.
|
20
22
|
#
|
21
|
-
# @return [CoercionRegistry] a new
|
23
|
+
# @return [CoercionRegistry] a new registry instance with built-in coercions
|
22
24
|
#
|
23
|
-
# @example
|
25
|
+
# @example Create a new coercion registry
|
24
26
|
# registry = CoercionRegistry.new
|
25
|
-
# registry.registry
|
27
|
+
# registry.registry.keys
|
28
|
+
# #=> [:array, :big_decimal, :boolean, :complex, :date, :datetime, :float, :hash, :integer, :rational, :string, :time, :virtual]
|
26
29
|
def initialize
|
27
30
|
@registry = {
|
28
31
|
array: Coercions::Array,
|
@@ -41,44 +44,60 @@ module CMDx
|
|
41
44
|
}
|
42
45
|
end
|
43
46
|
|
44
|
-
# Registers a
|
47
|
+
# Registers a new coercion type in the registry.
|
48
|
+
#
|
49
|
+
# Adds or overwrites a coercion type mapping in the registry, allowing custom
|
50
|
+
# coercions to be used during task parameter processing. The coercion can be
|
51
|
+
# a class that responds to `call`, a callable object, or a symbol/string
|
52
|
+
# representing a method to invoke on the task instance.
|
45
53
|
#
|
46
|
-
# @param type [Symbol] the type identifier
|
47
|
-
# @param coercion [
|
54
|
+
# @param type [Symbol] the coercion type identifier to register
|
55
|
+
# @param coercion [Class, Proc, Symbol, String] the coercion implementation
|
48
56
|
#
|
49
|
-
# @return [CoercionRegistry]
|
57
|
+
# @return [CoercionRegistry] self for method chaining
|
50
58
|
#
|
51
|
-
# @example
|
52
|
-
# registry.register(:
|
59
|
+
# @example Register a custom coercion class
|
60
|
+
# registry.register(:temperature, TemperatureCoercion)
|
53
61
|
#
|
54
|
-
# @example
|
55
|
-
# registry.register(:upcase,
|
62
|
+
# @example Register a coercion proc
|
63
|
+
# registry.register(:upcase, proc { |value, options| value.to_s.upcase })
|
56
64
|
#
|
57
|
-
# @example
|
58
|
-
# registry.register(:
|
65
|
+
# @example Register a method symbol
|
66
|
+
# registry.register(:custom_parse, :parse_custom_format)
|
59
67
|
def register(type, coercion)
|
60
68
|
registry[type] = coercion
|
61
69
|
self
|
62
70
|
end
|
63
71
|
|
64
|
-
# Executes a coercion
|
72
|
+
# Executes a coercion by type on the provided value.
|
65
73
|
#
|
66
|
-
#
|
74
|
+
# Looks up and executes the coercion implementation for the specified type,
|
75
|
+
# applying it to the provided value with optional configuration. Handles
|
76
|
+
# different coercion implementation types including callable objects,
|
77
|
+
# method symbols/strings, and coercion classes.
|
78
|
+
#
|
79
|
+
# @param task [CMDx::Task] the task instance for context when calling methods
|
67
80
|
# @param type [Symbol] the coercion type to execute
|
68
81
|
# @param value [Object] the value to be coerced
|
69
|
-
# @param options [Hash] additional options
|
82
|
+
# @param options [Hash] additional options passed to the coercion
|
83
|
+
# @option options [Object] any any additional configuration for the coercion
|
70
84
|
#
|
71
85
|
# @return [Object] the coerced value
|
72
86
|
#
|
73
|
-
# @raise [UnknownCoercionError] when the coercion type is not registered
|
87
|
+
# @raise [UnknownCoercionError] when the specified coercion type is not registered
|
88
|
+
# @raise [CoercionError] when the coercion fails to convert the value
|
89
|
+
#
|
90
|
+
# @example Execute a built-in coercion
|
91
|
+
# registry.call(task, :integer, "123")
|
92
|
+
# #=> 123
|
74
93
|
#
|
75
|
-
# @example
|
76
|
-
# registry.call(task, :
|
77
|
-
#
|
94
|
+
# @example Execute with options
|
95
|
+
# registry.call(task, :date, "2024-01-15", format: "%Y-%m-%d")
|
96
|
+
# #=> #<Date: 2024-01-15>
|
78
97
|
#
|
79
|
-
# @example
|
80
|
-
# registry.call(task, :
|
81
|
-
#
|
98
|
+
# @example Handle unknown coercion type
|
99
|
+
# registry.call(task, :unknown_type, "value")
|
100
|
+
# #=> raises UnknownCoercionError
|
82
101
|
def call(task, type, value, options = {})
|
83
102
|
raise UnknownCoercionError, "unknown coercion #{type}" unless registry.key?(type)
|
84
103
|
|
data/lib/cmdx/configuration.rb
CHANGED
@@ -3,7 +3,15 @@
|
|
3
3
|
module CMDx
|
4
4
|
|
5
5
|
# Global configuration class for CMDx framework settings.
|
6
|
-
#
|
6
|
+
#
|
7
|
+
# Manages logging, middleware, callbacks, coercions, validators, and halt conditions
|
8
|
+
# for the entire CMDx framework. The Configuration class provides centralized control
|
9
|
+
# over framework behavior including task execution flow, error handling, and component
|
10
|
+
# registration. All settings configured here become defaults for tasks and workflows
|
11
|
+
# unless explicitly overridden at the task or workflow level.
|
12
|
+
#
|
13
|
+
# The configuration system supports both global and per-task customization, allowing
|
14
|
+
# fine-grained control over framework behavior while maintaining sensible defaults.
|
7
15
|
class Configuration
|
8
16
|
|
9
17
|
DEFAULT_HALT = "failed"
|
@@ -29,13 +37,18 @@ module CMDx
|
|
29
37
|
# @return [String, Array<String>] Result statuses that halt workflow execution
|
30
38
|
attr_accessor :workflow_halt
|
31
39
|
|
32
|
-
#
|
40
|
+
# Creates a new configuration instance with default settings.
|
41
|
+
#
|
42
|
+
# Initializes all configuration attributes with sensible defaults including
|
43
|
+
# a stdout logger with line formatting, empty registries for extensibility
|
44
|
+
# components, and default halt conditions for both tasks and workflows.
|
33
45
|
#
|
34
|
-
# @
|
35
|
-
# config = CMDx::Configuration.new
|
36
|
-
# config.logger.level = Logger::DEBUG
|
46
|
+
# @return [Configuration] a new configuration instance with default settings
|
37
47
|
#
|
38
|
-
# @
|
48
|
+
# @example Create a new configuration
|
49
|
+
# config = Configuration.new
|
50
|
+
# config.logger.class #=> Logger
|
51
|
+
# config.task_halt #=> "failed"
|
39
52
|
def initialize
|
40
53
|
@logger = ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
|
41
54
|
@middlewares = MiddlewareRegistry.new
|
@@ -46,14 +59,26 @@ module CMDx
|
|
46
59
|
@workflow_halt = DEFAULT_HALT
|
47
60
|
end
|
48
61
|
|
49
|
-
#
|
62
|
+
# Converts the configuration to a hash representation.
|
50
63
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
64
|
+
# Creates a hash containing all configuration attributes for serialization,
|
65
|
+
# inspection, or transfer between processes. The hash includes all registries
|
66
|
+
# and settings in their current state.
|
67
|
+
#
|
68
|
+
# @return [Hash] hash representation of the configuration
|
69
|
+
# @option return [Logger] :logger the configured logger instance
|
70
|
+
# @option return [MiddlewareRegistry] :middlewares the middleware registry
|
71
|
+
# @option return [CallbackRegistry] :callbacks the callback registry
|
72
|
+
# @option return [CoercionRegistry] :coercions the coercion registry
|
73
|
+
# @option return [ValidatorRegistry] :validators the validator registry
|
74
|
+
# @option return [String, Array<String>] :task_halt the task halt configuration
|
75
|
+
# @option return [String, Array<String>] :workflow_halt the workflow halt configuration
|
55
76
|
#
|
56
|
-
# @
|
77
|
+
# @example Convert configuration to hash
|
78
|
+
# config = Configuration.new
|
79
|
+
# hash = config.to_h
|
80
|
+
# hash[:logger].class #=> Logger
|
81
|
+
# hash[:task_halt] #=> "failed"
|
57
82
|
def to_h
|
58
83
|
{
|
59
84
|
logger: @logger,
|
@@ -70,33 +95,49 @@ module CMDx
|
|
70
95
|
|
71
96
|
module_function
|
72
97
|
|
73
|
-
#
|
74
|
-
# Creates a new configuration if none exists.
|
98
|
+
# Returns the current global configuration instance.
|
75
99
|
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
100
|
+
# Provides access to the singleton configuration instance used by the entire
|
101
|
+
# CMDx framework. Creates a new configuration with default settings if none
|
102
|
+
# exists. This method is thread-safe and ensures only one configuration
|
103
|
+
# instance exists per process.
|
104
|
+
#
|
105
|
+
# @return [Configuration] the current global configuration instance
|
79
106
|
#
|
80
|
-
# @
|
107
|
+
# @example Access global configuration
|
108
|
+
# config = CMDx.configuration
|
109
|
+
# config.logger.level = Logger::DEBUG
|
110
|
+
# config.task_halt = ["failed", "skipped"]
|
81
111
|
def configuration
|
82
112
|
return @configuration if @configuration
|
83
113
|
|
84
114
|
@configuration ||= Configuration.new
|
85
115
|
end
|
86
116
|
|
87
|
-
#
|
117
|
+
# Configures the global CMDx settings using a block.
|
88
118
|
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
# config.logger.level = Logger::DEBUG
|
93
|
-
# end
|
119
|
+
# Yields the current configuration instance to the provided block for
|
120
|
+
# modification. This is the recommended way to configure CMDx as it
|
121
|
+
# provides a clean DSL-like interface for setting up the framework.
|
94
122
|
#
|
95
|
-
# @
|
123
|
+
# @param block [Proc] configuration block that receives the configuration instance
|
96
124
|
#
|
97
|
-
# @return [Configuration]
|
125
|
+
# @return [Configuration] the configured configuration instance
|
98
126
|
#
|
99
|
-
# @raise [ArgumentError]
|
127
|
+
# @raise [ArgumentError] if no block is provided
|
128
|
+
#
|
129
|
+
# @example Configure CMDx settings
|
130
|
+
# CMDx.configure do |config|
|
131
|
+
# config.logger.level = Logger::INFO
|
132
|
+
# config.task_halt = ["failed", "skipped"]
|
133
|
+
# config.middlewares.register(CMDx::Middlewares::Timeout.new(seconds: 30))
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# @example Configure with custom logger
|
137
|
+
# CMDx.configure do |config|
|
138
|
+
# config.logger = Rails.logger
|
139
|
+
# config.logger.formatter = CMDx::LogFormatters::JSON.new
|
140
|
+
# end
|
100
141
|
def configure
|
101
142
|
raise ArgumentError, "block required" unless block_given?
|
102
143
|
|
@@ -105,13 +146,25 @@ module CMDx
|
|
105
146
|
config
|
106
147
|
end
|
107
148
|
|
108
|
-
#
|
149
|
+
# Resets the global configuration to default settings.
|
150
|
+
#
|
151
|
+
# Creates a new configuration instance with default settings, discarding
|
152
|
+
# any existing configuration. This is useful for testing scenarios or
|
153
|
+
# when you need to start with a clean configuration state.
|
154
|
+
#
|
155
|
+
# @return [Configuration] a new configuration instance with default settings
|
156
|
+
#
|
157
|
+
# @example Reset to defaults
|
158
|
+
# CMDx.configure { |c| c.task_halt = ["failed", "skipped"] }
|
159
|
+
# CMDx.configuration.task_halt #=> ["failed", "skipped"]
|
109
160
|
#
|
110
|
-
# @example
|
111
161
|
# CMDx.reset_configuration!
|
112
|
-
# CMDx.configuration.task_halt
|
162
|
+
# CMDx.configuration.task_halt #=> "failed"
|
113
163
|
#
|
114
|
-
# @
|
164
|
+
# @example Use in test setup
|
165
|
+
# RSpec.configure do |config|
|
166
|
+
# config.before(:each) { CMDx.reset_configuration! }
|
167
|
+
# end
|
115
168
|
def reset_configuration!
|
116
169
|
@configuration = Configuration.new
|
117
170
|
end
|
data/lib/cmdx/context.rb
CHANGED
@@ -1,38 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Execution context container for task parameter storage and access.
|
5
5
|
#
|
6
|
-
# Context provides
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# Context provides normalized parameter storage for task execution, inheriting
|
7
|
+
# from LazyStruct to provide flexible attribute access patterns. It serves as
|
8
|
+
# the primary interface for storing and retrieving execution parameters, allowing
|
9
|
+
# both hash-style and method-style attribute access with automatic key normalization.
|
10
|
+
# Context instances are used throughout task execution to maintain parameter state
|
11
|
+
# and provide consistent data access across the task lifecycle.
|
10
12
|
class Context < LazyStruct
|
11
13
|
|
12
|
-
# Creates or returns a
|
14
|
+
# Creates or returns a Context instance from the provided input.
|
13
15
|
#
|
14
|
-
# This method
|
15
|
-
#
|
16
|
-
#
|
16
|
+
# This factory method normalizes various input types into a proper Context instance,
|
17
|
+
# ensuring consistent context handling throughout the framework. If the input is
|
18
|
+
# already a Context instance and not frozen, it returns the input unchanged to
|
19
|
+
# avoid unnecessary object creation. Otherwise, it creates a new Context instance
|
20
|
+
# with the provided data.
|
17
21
|
#
|
18
|
-
# @param context [Hash, Context, Object] input data to
|
22
|
+
# @param context [Hash, Context, Object] input data to convert to Context
|
23
|
+
# @option context [Object] any any attribute keys and values for context initialization
|
19
24
|
#
|
20
25
|
# @return [Context] a Context instance containing the provided data
|
21
26
|
#
|
22
|
-
# @
|
27
|
+
# @example Create context from hash
|
28
|
+
# context = Context.build(user_id: 123, action: "process")
|
29
|
+
# context.user_id #=> 123
|
30
|
+
# context.action #=> "process"
|
23
31
|
#
|
24
|
-
# @example
|
25
|
-
# Context.
|
26
|
-
#
|
32
|
+
# @example Return existing unfrozen context
|
33
|
+
# existing = Context.new(status: "active")
|
34
|
+
# result = Context.build(existing)
|
35
|
+
# result.equal?(existing) #=> true
|
27
36
|
#
|
28
|
-
# @example
|
29
|
-
#
|
30
|
-
# Context.build(
|
31
|
-
#
|
37
|
+
# @example Create new context from frozen context
|
38
|
+
# frozen_context = Context.new(data: "test").freeze
|
39
|
+
# new_context = Context.build(frozen_context)
|
40
|
+
# new_context.equal?(frozen_context) #=> false
|
41
|
+
# new_context.data #=> "test"
|
32
42
|
#
|
33
|
-
# @example
|
34
|
-
# Context.build
|
35
|
-
#
|
43
|
+
# @example Create context from empty input
|
44
|
+
# context = Context.build
|
45
|
+
# context.class #=> CMDx::Context
|
46
|
+
# context.to_h #=> {}
|
36
47
|
def self.build(context = {})
|
37
48
|
return context if context.is_a?(self) && !context.frozen?
|
38
49
|
|
data/lib/cmdx/core_ext/hash.rb
CHANGED
@@ -16,13 +16,13 @@ module CMDx
|
|
16
16
|
#
|
17
17
|
# @example Fetch with symbol key
|
18
18
|
# hash = { name: "John", "age" => 30 }
|
19
|
-
# hash.cmdx_fetch(:name)
|
20
|
-
# hash.cmdx_fetch(:age)
|
19
|
+
# hash.cmdx_fetch(:name) #=> "John"
|
20
|
+
# hash.cmdx_fetch(:age) #=> 30
|
21
21
|
#
|
22
22
|
# @example Fetch with string key
|
23
23
|
# hash = { name: "John", "age" => 30 }
|
24
|
-
# hash.cmdx_fetch("name")
|
25
|
-
# hash.cmdx_fetch("age")
|
24
|
+
# hash.cmdx_fetch("name") #=> "John"
|
25
|
+
# hash.cmdx_fetch("age") #=> 30
|
26
26
|
def cmdx_fetch(key)
|
27
27
|
case key
|
28
28
|
when Symbol then fetch(key) { self[key.to_s] }
|
@@ -40,11 +40,11 @@ module CMDx
|
|
40
40
|
#
|
41
41
|
# @example Check key existence
|
42
42
|
# hash = { name: "John", "age" => 30 }
|
43
|
-
# hash.cmdx_key?(:name)
|
44
|
-
# hash.cmdx_key?("name")
|
45
|
-
# hash.cmdx_key?(:age)
|
46
|
-
# hash.cmdx_key?("age")
|
47
|
-
# hash.cmdx_key?(:missing)
|
43
|
+
# hash.cmdx_key?(:name) #=> true
|
44
|
+
# hash.cmdx_key?("name") #=> true
|
45
|
+
# hash.cmdx_key?(:age) #=> true
|
46
|
+
# hash.cmdx_key?("age") #=> true
|
47
|
+
# hash.cmdx_key?(:missing) #=> false
|
48
48
|
def cmdx_key?(key)
|
49
49
|
key?(key) || key?(
|
50
50
|
case key
|
@@ -66,10 +66,10 @@ module CMDx
|
|
66
66
|
#
|
67
67
|
# @example Check method or key response
|
68
68
|
# hash = { name: "John", "age" => 30 }
|
69
|
-
# hash.cmdx_respond_to?(:keys)
|
70
|
-
# hash.cmdx_respond_to?(:name)
|
71
|
-
# hash.cmdx_respond_to?("age")
|
72
|
-
# hash.cmdx_respond_to?(:missing)
|
69
|
+
# hash.cmdx_respond_to?(:keys) #=> true (method exists)
|
70
|
+
# hash.cmdx_respond_to?(:name) #=> true (key exists)
|
71
|
+
# hash.cmdx_respond_to?("age") #=> true (key exists)
|
72
|
+
# hash.cmdx_respond_to?(:missing) #=> false
|
73
73
|
def cmdx_respond_to?(key, include_private = false)
|
74
74
|
respond_to?(key.to_sym, include_private) || cmdx_key?(key)
|
75
75
|
rescue NoMethodError
|
data/lib/cmdx/core_ext/module.rb
CHANGED
data/lib/cmdx/core_ext/object.rb
CHANGED
@@ -18,14 +18,14 @@ module CMDx
|
|
18
18
|
# @return [Object, nil] the result of the method call, proc evaluation, or hash access; nil if not found
|
19
19
|
#
|
20
20
|
# @example Try calling a method
|
21
|
-
# "hello".cmdx_try(:upcase)
|
22
|
-
# "hello".cmdx_try(:missing)
|
21
|
+
# "hello".cmdx_try(:upcase) #=> "HELLO"
|
22
|
+
# "hello".cmdx_try(:missing) #=> nil
|
23
23
|
#
|
24
24
|
# @example Try evaluating a proc
|
25
|
-
# obj.cmdx_try(-> { self.class.name })
|
25
|
+
# obj.cmdx_try(-> { self.class.name }) #=> "String"
|
26
26
|
#
|
27
27
|
# @example Try accessing a hash key
|
28
|
-
# {name: "John"}.cmdx_try(:name)
|
28
|
+
# {name: "John"}.cmdx_try(:name) #=> "John"
|
29
29
|
def cmdx_try(key, *args, **kwargs, &)
|
30
30
|
if key.is_a?(Proc)
|
31
31
|
return instance_eval(&key) unless is_a?(Module) || key.inspect.include?("(lambda)")
|
@@ -53,13 +53,13 @@ module CMDx
|
|
53
53
|
# @return [Boolean] true if conditions are met, false otherwise
|
54
54
|
#
|
55
55
|
# @example Evaluate with if condition
|
56
|
-
# user.cmdx_eval(if: :active?)
|
56
|
+
# user.cmdx_eval(if: :active?) #=> true if user.active? is truthy
|
57
57
|
#
|
58
58
|
# @example Evaluate with unless condition
|
59
|
-
# user.cmdx_eval(unless: :banned?)
|
59
|
+
# user.cmdx_eval(unless: :banned?) #=> true if user.banned? is falsy
|
60
60
|
#
|
61
61
|
# @example Evaluate with both conditions
|
62
|
-
# user.cmdx_eval(if: :active?, unless: :banned?)
|
62
|
+
# user.cmdx_eval(if: :active?, unless: :banned?) #=> true if active and not banned
|
63
63
|
def cmdx_eval(options = {})
|
64
64
|
if options[:if] && options[:unless]
|
65
65
|
cmdx_try(options[:if]) && !cmdx_try(options[:unless])
|
@@ -81,13 +81,13 @@ module CMDx
|
|
81
81
|
# @return [Object] the result of method call, proc evaluation, or the value itself
|
82
82
|
#
|
83
83
|
# @example Yield a method call
|
84
|
-
# "hello".cmdx_yield(:upcase)
|
84
|
+
# "hello".cmdx_yield(:upcase) #=> "HELLO"
|
85
85
|
#
|
86
86
|
# @example Yield a static value
|
87
|
-
# obj.cmdx_yield("static")
|
87
|
+
# obj.cmdx_yield("static") #=> "static"
|
88
88
|
#
|
89
89
|
# @example Yield a proc
|
90
|
-
# obj.cmdx_yield(-> { Time.now })
|
90
|
+
# obj.cmdx_yield(-> { Time.now }) #=> 2023-01-01 12:00:00 UTC
|
91
91
|
def cmdx_yield(key, ...)
|
92
92
|
if key.is_a?(Symbol) || key.is_a?(String)
|
93
93
|
return key unless respond_to?(key, true)
|
@@ -108,10 +108,10 @@ module CMDx
|
|
108
108
|
# @return [Object] the result of calling the object, or the object itself if not callable
|
109
109
|
#
|
110
110
|
# @example Invoke a proc
|
111
|
-
# proc { "hello" }.cmdx_call
|
111
|
+
# proc { "hello" }.cmdx_call #=> "hello"
|
112
112
|
#
|
113
113
|
# @example Invoke a non-callable object
|
114
|
-
# "hello".cmdx_call
|
114
|
+
# "hello".cmdx_call #=> "hello"
|
115
115
|
def cmdx_call(...)
|
116
116
|
return self unless respond_to?(:call)
|
117
117
|
|