domainic-command 0.1.0.alpha.1.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +1 -1
- data/README.md +93 -2
- data/lib/domainic/command/class_methods.rb +181 -0
- data/lib/domainic/command/context/attribute.rb +157 -0
- data/lib/domainic/command/context/attribute_set.rb +96 -0
- data/lib/domainic/command/context/behavior.rb +132 -0
- data/lib/domainic/command/context/input_context.rb +55 -0
- data/lib/domainic/command/context/output_context.rb +55 -0
- data/lib/domainic/command/context/runtime_context.rb +126 -0
- data/lib/domainic/command/errors/error.rb +23 -0
- data/lib/domainic/command/errors/execution_error.rb +40 -0
- data/lib/domainic/command/instance_methods.rb +92 -0
- data/lib/domainic/command/result/error_set.rb +272 -0
- data/lib/domainic/command/result/status.rb +49 -0
- data/lib/domainic/command/result.rb +194 -0
- data/lib/domainic/command.rb +77 -0
- data/lib/domainic-command.rb +3 -0
- data/sig/domainic/command/class_methods.rbs +100 -0
- data/sig/domainic/command/context/attribute.rbs +104 -0
- data/sig/domainic/command/context/attribute_set.rbs +69 -0
- data/sig/domainic/command/context/behavior.rbs +82 -0
- data/sig/domainic/command/context/input_context.rbs +40 -0
- data/sig/domainic/command/context/output_context.rbs +40 -0
- data/sig/domainic/command/context/runtime_context.rbs +90 -0
- data/sig/domainic/command/errors/error.rbs +21 -0
- data/sig/domainic/command/errors/execution_error.rbs +32 -0
- data/sig/domainic/command/instance_methods.rbs +56 -0
- data/sig/domainic/command/result/error_set.rbs +186 -0
- data/sig/domainic/command/result/status.rbs +47 -0
- data/sig/domainic/command/result.rbs +149 -0
- data/sig/domainic/command.rbs +67 -0
- data/sig/domainic-command.rbs +1 -0
- data/sig/manifest.yaml +1 -0
- metadata +50 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37af16c660cced1f71598b53c4eebd3930683e76cbc5c5c32f715071b976dd58
|
4
|
+
data.tar.gz: 89dd55523e0a75bfd2ffacc209b1b3cb968eadadd413dfd4208772794a841919
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75144138e2240b1e90c5e16935747c3f8d5073d28cc957227ec661839441953357a95fdf60664ada068af81e6ade28c6dbe88fac2ff86cd0988526473d1a19bb
|
7
|
+
data.tar.gz: 7658065d96b7264d047461f69f4d3a4985988e17c663df668e3a91d98327f9460f17d5b39917c0f9abd5e9d06d30ca0d185f02185b514dd98e22c79a2e3010bb
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog], and this project adheres to [Break Versioning].
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [v0.1.0] - 2024-12-29
|
10
|
+
|
11
|
+
* Initial release
|
12
|
+
|
13
|
+
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
|
14
|
+
[Break Versioning]: https://www.taoensso.com/break-versioning
|
15
|
+
|
16
|
+
<!-- versions -->
|
17
|
+
|
18
|
+
[Unreleased]: https://github.com/domainic/domainic/compare/domainic-command-v0.1.0...HEAD
|
19
|
+
[v0.1.0]: https://github.com/domainic/domainic/compare/1fdeec3d5d3c6bfe61c2186ba848681c51469e90...domainic-command-v0.1.0
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,94 @@
|
|
1
|
-
# Domainic
|
1
|
+
# Domainic::Command
|
2
2
|
|
3
|
-
|
3
|
+
[](https://rubygems.org/gems/domainic-command)
|
4
|
+
[](./LICENSE)
|
5
|
+
[](https://rubydoc.info/gems/domainic-command/0.1.0)
|
6
|
+
[](https://github.com/domainic/domainic/issues?q=state%3Aopen%20label%3Adomainic-command%20)
|
7
|
+
|
8
|
+
A robust implementation of the Command pattern for Ruby applications, providing type-safe, self-documenting business
|
9
|
+
operations with standardized error handling and composable workflows.
|
10
|
+
|
11
|
+
Tired of scattered business logic and unclear error handling? Domainic::Command brings clarity to your domain operations
|
12
|
+
by:
|
13
|
+
|
14
|
+
* Enforcing explicit input/output contracts with type validation
|
15
|
+
* Providing consistent error handling and status reporting
|
16
|
+
* Enabling self-documenting business operations
|
17
|
+
* Supporting composable command workflows
|
18
|
+
* Maintaining thread safety for concurrent operations
|
19
|
+
|
20
|
+
## Quick Start
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class CreateUser
|
24
|
+
include Domainic::Command
|
25
|
+
|
26
|
+
# Define expected inputs with validation
|
27
|
+
argument :login, String, "The user's login", required: true
|
28
|
+
argument :password, String, "The user's password", required: true
|
29
|
+
|
30
|
+
# Define expected outputs
|
31
|
+
output :user, User, "The created user", required: true
|
32
|
+
output :created_at, Time, "When the user was created"
|
33
|
+
|
34
|
+
def execute
|
35
|
+
user = User.create!(login: context.login, password: context.password)
|
36
|
+
context.user = user
|
37
|
+
context.created_at = Time.current
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Success case
|
42
|
+
result = CreateUser.call(login: "user@example.com", password: "secret123")
|
43
|
+
result.successful? # => true
|
44
|
+
result.user # => #<User id: 1, login: "user@example.com">
|
45
|
+
|
46
|
+
# Failure case
|
47
|
+
result = CreateUser.call(login: "invalid")
|
48
|
+
result.failure? # => true
|
49
|
+
result.errors # => { password: ["is required"] }
|
50
|
+
```
|
51
|
+
|
52
|
+
## Installation
|
53
|
+
|
54
|
+
Add this line to your application's Gemfile:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
gem 'domainic-command'
|
58
|
+
```
|
59
|
+
|
60
|
+
Or install it yourself as:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
gem install domainic-command
|
64
|
+
```
|
65
|
+
|
66
|
+
## Key Features
|
67
|
+
|
68
|
+
* **Type-Safe Arguments**: Define and validate input parameters with clear type constraints
|
69
|
+
* **Explicit Outputs**: Specify expected return values and their requirements
|
70
|
+
* **Standardized Error Handling**: Consistent error reporting with detailed failure information
|
71
|
+
* **Thread Safety**: Built-in thread safety for class definition and execution
|
72
|
+
* **Command Composition**: Build complex workflows by combining simpler commands
|
73
|
+
* **Self-Documenting**: Generate clear documentation from your command definitions
|
74
|
+
* **Framework Agnostic**: Use with any Ruby application (Rails, Sinatra, pure Ruby)
|
75
|
+
|
76
|
+
## Documentation
|
77
|
+
|
78
|
+
For detailed usage instructions and examples, see [USAGE.md](./docs/USAGE.md).
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
We welcome contributions! Please see our
|
83
|
+
[Contributing Guidelines](https://github.com/domainic/domainic/wiki/CONTRIBUTING) for:
|
84
|
+
|
85
|
+
* Development setup and workflow
|
86
|
+
* Code style and documentation standards
|
87
|
+
* Testing requirements
|
88
|
+
* Pull request process
|
89
|
+
|
90
|
+
Before contributing, please review our [Code of Conduct](https://github.com/domainic/domainic/wiki/CODE_OF_CONDUCT).
|
91
|
+
|
92
|
+
## License
|
93
|
+
|
94
|
+
The gem is available as open source under the terms of the [MIT License](./LICENSE).
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/context/input_context'
|
4
|
+
require 'domainic/command/context/output_context'
|
5
|
+
require 'domainic/command/context/runtime_context'
|
6
|
+
|
7
|
+
module Domainic
|
8
|
+
module Command
|
9
|
+
# Class methods that are extended onto any class that includes {Command}. These methods provide
|
10
|
+
# the DSL for defining command inputs and outputs, as well as class-level execution methods.
|
11
|
+
#
|
12
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
13
|
+
# @since 0.1.0
|
14
|
+
module ClassMethods
|
15
|
+
# @rbs @input_context_class: singleton(Context::InputContext)
|
16
|
+
# @rbs @output_context_class: singleton(Context::OutputContext)
|
17
|
+
# @rbs @runtime_context_class: singleton(Context::RuntimeContext)
|
18
|
+
|
19
|
+
# Specifies an external input context class for the command
|
20
|
+
#
|
21
|
+
# @param input_context_class [Class] A subclass of {Context::InputContext}
|
22
|
+
#
|
23
|
+
# @raise [ArgumentError] If the provided class is not a subclass of {Context::InputContext}
|
24
|
+
# @return [void]
|
25
|
+
# @rbs (singleton(Context::InputContext) input_context_class) -> void
|
26
|
+
def accepts_arguments_matching(input_context_class)
|
27
|
+
unless input_context_class < Context::InputContext
|
28
|
+
raise ArgumentError, 'Input context class must be a subclass of Context::InputContext'
|
29
|
+
end
|
30
|
+
|
31
|
+
# @type self: Class & ClassMethods
|
32
|
+
@input_context_class = begin
|
33
|
+
const_set(:InputContext, Class.new(input_context_class))
|
34
|
+
const_get(:InputContext)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines an input argument for the command
|
39
|
+
#
|
40
|
+
# @overload argument(name, *type_validator_and_description, **options)
|
41
|
+
# @param name [String, Symbol] The name of the argument
|
42
|
+
# @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
|
43
|
+
# description arguments
|
44
|
+
# @param options [Hash] Configuration options for the argument
|
45
|
+
# @option options [Object] :default A static default value
|
46
|
+
# @option options [Proc] :default_generator A proc that generates the default value
|
47
|
+
# @option options [Object] :default_value Alias for :default
|
48
|
+
# @option options [String, nil] :desc Short description of the argument
|
49
|
+
# @option options [String, nil] :description Full description of the argument
|
50
|
+
# @option options [Boolean] :required Whether the argument is required
|
51
|
+
# @option options [Class, Module, Object, Proc] :type A type validator
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
# @rbs (
|
55
|
+
# String | Symbol name,
|
56
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
57
|
+
# ?default: untyped,
|
58
|
+
# ?default_generator: untyped,
|
59
|
+
# ?default_value: untyped,
|
60
|
+
# ?desc: String?,
|
61
|
+
# ?description: String?,
|
62
|
+
# ?required: bool,
|
63
|
+
# ?type: Class | Module | Object | Proc
|
64
|
+
# ) -> void
|
65
|
+
def argument(...)
|
66
|
+
input_context_class.argument(...)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Executes the command with the given context, handling any errors
|
70
|
+
#
|
71
|
+
# @param context [Hash] The input context for the command
|
72
|
+
#
|
73
|
+
# @return [Result] The result of the command execution
|
74
|
+
# @rbs (**untyped context) -> Result
|
75
|
+
def call(**context)
|
76
|
+
# @type self: Class & ClassMethods & InstanceMethods
|
77
|
+
new.call(**context)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Executes the command with the given context, raising any errors
|
81
|
+
#
|
82
|
+
# @param context [Hash] The input context for the command
|
83
|
+
#
|
84
|
+
# @raise [ExecutionError] If the command execution fails
|
85
|
+
# @return [Result] The result of the command execution
|
86
|
+
# @rbs (**untyped context) -> Result
|
87
|
+
def call!(**context)
|
88
|
+
# @type self: Class & ClassMethods & InstanceMethods
|
89
|
+
new.call!(**context)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Defines an output field for the command
|
93
|
+
#
|
94
|
+
# @overload output(name, *type_validator_and_description, **options)
|
95
|
+
# @param name [String, Symbol] The name of the output field
|
96
|
+
# @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
|
97
|
+
# description arguments
|
98
|
+
# @param options [Hash] Configuration options for the output
|
99
|
+
# @option options [Object] :default A static default value
|
100
|
+
# @option options [Proc] :default_generator A proc that generates the default value
|
101
|
+
# @option options [Object] :default_value Alias for :default
|
102
|
+
# @option options [String, nil] :desc Short description of the output
|
103
|
+
# @option options [String, nil] :description Full description of the output
|
104
|
+
# @option options [Boolean] :required Whether the output is required
|
105
|
+
# @option options [Class, Module, Object, Proc] :type A type validator
|
106
|
+
#
|
107
|
+
# @return [void]
|
108
|
+
# @rbs (
|
109
|
+
# String | Symbol name,
|
110
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
111
|
+
# ?default: untyped,
|
112
|
+
# ?default_generator: untyped,
|
113
|
+
# ?default_value: untyped,
|
114
|
+
# ?desc: String?,
|
115
|
+
# ?description: String?,
|
116
|
+
# ?required: bool,
|
117
|
+
# ?type: Class | Module | Object | Proc
|
118
|
+
# ) -> void
|
119
|
+
def output(...)
|
120
|
+
output_context_class.field(...)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Specifies an external output context class for the command
|
124
|
+
#
|
125
|
+
# @param output_context_class [Class] A subclass of {Context::OutputContext}
|
126
|
+
#
|
127
|
+
# @raise [ArgumentError] If the provided class is not a subclass of {Context::OutputContext}
|
128
|
+
# @return [void]
|
129
|
+
# @rbs (singleton(Context::OutputContext) output_context_class) -> void
|
130
|
+
def returns_data_matching(output_context_class)
|
131
|
+
unless output_context_class < Context::OutputContext
|
132
|
+
raise ArgumentError, 'Output context class must be a subclass of Context::OutputContext'
|
133
|
+
end
|
134
|
+
|
135
|
+
# @type self: Class & ClassMethods
|
136
|
+
@output_context_class = begin
|
137
|
+
const_set(:OutputContext, Class.new(output_context_class))
|
138
|
+
const_get(:OutputContext)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Returns the input context class for the command
|
145
|
+
#
|
146
|
+
# @return [Class] A subclass of {Context::InputContext}
|
147
|
+
# @rbs () -> singleton(Context::InputContext)
|
148
|
+
def input_context_class
|
149
|
+
# @type self: Class & ClassMethods
|
150
|
+
@input_context_class ||= begin
|
151
|
+
const_set(:InputContext, Class.new(Context::InputContext)) unless const_defined?(:InputContext)
|
152
|
+
const_get(:InputContext)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the output context class for the command
|
157
|
+
#
|
158
|
+
# @return [Class] A subclass of {Context::OutputContext}
|
159
|
+
# @rbs () -> singleton(Context::OutputContext)
|
160
|
+
def output_context_class
|
161
|
+
# @type self: Class & ClassMethods
|
162
|
+
@output_context_class ||= begin
|
163
|
+
const_set(:OutputContext, Class.new(Context::OutputContext)) unless const_defined?(:OutputContext)
|
164
|
+
const_get(:OutputContext)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns the runtime context class for the command
|
169
|
+
#
|
170
|
+
# @return [Class] A subclass of {Context::RuntimeContext}
|
171
|
+
# @rbs () -> singleton(Context::RuntimeContext)
|
172
|
+
def runtime_context_class
|
173
|
+
# @type self: Class & ClassMethods
|
174
|
+
@runtime_context_class ||= begin
|
175
|
+
const_set(:RuntimeContext, Class.new(Context::RuntimeContext)) unless const_defined?(:RuntimeContext)
|
176
|
+
const_get(:RuntimeContext)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domainic
|
4
|
+
module Command
|
5
|
+
module Context
|
6
|
+
# Represents an attribute within a command context. This class manages the lifecycle of an attribute, including
|
7
|
+
# its validation, default values, and metadata such as descriptions
|
8
|
+
#
|
9
|
+
# The `Attribute` class supports a variety of configuration options, such as marking an attribute as required,
|
10
|
+
# defining static or dynamic default values, and specifying custom validators. These features ensure that
|
11
|
+
# attributes conform to expected rules and provide useful metadata for documentation or runtime behavior
|
12
|
+
#
|
13
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
14
|
+
# @since 0.1.0
|
15
|
+
class Attribute
|
16
|
+
# Represents an undefined default value for an attribute. This is used internally to distinguish between
|
17
|
+
# an attribute with no default and one with a defined default
|
18
|
+
#
|
19
|
+
# @return [Object] A frozen object representing an undefined default value
|
20
|
+
UNDEFINED_DEFAULT = Object.new.freeze #: Object
|
21
|
+
private_constant :UNDEFINED_DEFAULT
|
22
|
+
|
23
|
+
# @rbs @default: untyped
|
24
|
+
# @rbs @description: String?
|
25
|
+
# @rbs @name: Symbol
|
26
|
+
# @rbs @required: bool
|
27
|
+
# @rbs @type_validator: Class | Module | Object | Proc
|
28
|
+
|
29
|
+
# The textual description of the attribute, providing metadata about its purpose or usage
|
30
|
+
#
|
31
|
+
# @return [String, nil] A description of the attribute
|
32
|
+
attr_reader :description #: String?
|
33
|
+
|
34
|
+
# The name of the attribute, uniquely identifying it within a command context
|
35
|
+
#
|
36
|
+
# @return [Symbol] The name of the attribute
|
37
|
+
attr_reader :name #: Symbol
|
38
|
+
|
39
|
+
# Create a new attribute instance
|
40
|
+
#
|
41
|
+
# @overload initialize(
|
42
|
+
# name, type_validator_or_description = nil, description_or_type_validator = nil, **options
|
43
|
+
# )
|
44
|
+
# @param name [String, Symbol] The {#name} of the attribute
|
45
|
+
# @param type_validator_or_description [Class, Module, Object, Proc, String, nil] A type validator or the
|
46
|
+
# {#description} of the attribute
|
47
|
+
# @param description_or_type_validator [Class, Module, Object, Proc, String, nil] The {#description} or a
|
48
|
+
# type_validator of the attribute
|
49
|
+
# @param options [Hash] Configuration options for the attribute
|
50
|
+
# @option options [Object] :default The {#default} of the attribute
|
51
|
+
# @option options [Proc] :default_generator An alias for :default
|
52
|
+
# @option options [Object] :default_value An alias for :default
|
53
|
+
# @option options [String, nil] :desc An alias for :description
|
54
|
+
# @option options [String, nil] :description The {#description} of the attribute
|
55
|
+
# @option options [Boolean] :required Whether the attribute is {#required?}
|
56
|
+
# @option options [Class, Module, Object, Proc] :type A type validator for the attribute value
|
57
|
+
#
|
58
|
+
# @return [Attribute] the new Attribute instance
|
59
|
+
# @rbs (
|
60
|
+
# String | Symbol name,
|
61
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
62
|
+
# ?default: untyped,
|
63
|
+
# ?default_generator: untyped,
|
64
|
+
# ?default_value: untyped,
|
65
|
+
# ?desc: String?,
|
66
|
+
# ?description: String?,
|
67
|
+
# ?required: bool,
|
68
|
+
# ?type: Class | Module | Object | Proc
|
69
|
+
# ) -> void
|
70
|
+
def initialize(name, *type_validator_and_description, **options)
|
71
|
+
symbolized_options = options.transform_keys(&:to_sym)
|
72
|
+
|
73
|
+
@name = name.to_sym
|
74
|
+
@required = symbolized_options[:required] == true
|
75
|
+
|
76
|
+
initialize_default(symbolized_options)
|
77
|
+
initialize_description_and_type_validator(type_validator_and_description, symbolized_options)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Retrieves the default value of the attribute. If a default generator is specified, it evaluates the generator
|
81
|
+
# and returns the result
|
82
|
+
#
|
83
|
+
# @return [Object, nil] The default value or the result of the generator
|
84
|
+
# @rbs () -> untyped
|
85
|
+
def default
|
86
|
+
return unless default?
|
87
|
+
|
88
|
+
@default.is_a?(Proc) ? @default.call : @default
|
89
|
+
end
|
90
|
+
|
91
|
+
# Determines whether the attribute has a default value defined
|
92
|
+
#
|
93
|
+
# @return [Boolean] `true` if a default is set; otherwise, `false`
|
94
|
+
# @rbs () -> bool
|
95
|
+
def default?
|
96
|
+
@default != UNDEFINED_DEFAULT
|
97
|
+
end
|
98
|
+
|
99
|
+
# Determines whether the attribute is marked as required
|
100
|
+
#
|
101
|
+
# @return [Boolean] `true` if the attribute is required; otherwise, `false`
|
102
|
+
# @rbs () -> bool
|
103
|
+
def required?
|
104
|
+
@required
|
105
|
+
end
|
106
|
+
|
107
|
+
# Validates the given value against the attribute's type validator
|
108
|
+
#
|
109
|
+
# @param value [Object] The value to validate
|
110
|
+
#
|
111
|
+
# @return [Boolean] `true` if the value is valid; otherwise, `false`
|
112
|
+
# @rbs (untyped value) -> bool
|
113
|
+
def valid?(value)
|
114
|
+
return false if value.nil? && required?
|
115
|
+
return true if @type_validator.nil?
|
116
|
+
|
117
|
+
validator = @type_validator
|
118
|
+
return validator.call(value) if validator.is_a?(Proc)
|
119
|
+
|
120
|
+
validator === value || value.is_a?(validator) # rubocop:disable Style/CaseEquality
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Initializes the {#default} value for the attribute, using the provided options
|
126
|
+
#
|
127
|
+
# @param options [Hash] Configuration options containing default-related keys
|
128
|
+
#
|
129
|
+
# @return [void]
|
130
|
+
# @rbs (Hash[Symbol, untyped] options) -> void
|
131
|
+
def initialize_default(options)
|
132
|
+
@default = if %i[default default_generator default_value].any? { |key| options.key?(key) }
|
133
|
+
options.values_at(:default, :default_generator, :default_value).compact.first
|
134
|
+
else
|
135
|
+
UNDEFINED_DEFAULT
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Initializes the description and type validator for the attribute based on the given arguments and options
|
140
|
+
#
|
141
|
+
# @param arguments [Array<Class, Module, Object, Proc, String, nil>] Arguments for validators or description
|
142
|
+
# @param options [Hash] Configuration options
|
143
|
+
#
|
144
|
+
# @return [void]
|
145
|
+
# @rbs (Array[(Class | Module | Object | Proc | String)?] arguments, Hash[Symbol, untyped] options) -> void
|
146
|
+
def initialize_description_and_type_validator(arguments, options)
|
147
|
+
@description = arguments.compact.find { |argument| argument.is_a?(String) } ||
|
148
|
+
options[:description] ||
|
149
|
+
options[:desc]
|
150
|
+
|
151
|
+
@type_validator = arguments.compact.find { |argument| !argument.is_a?(String) } ||
|
152
|
+
options[:type]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domainic
|
4
|
+
module Command
|
5
|
+
module Context
|
6
|
+
# A collection class for managing a set of command context attributes. This class provides a simple interface
|
7
|
+
# for storing, accessing, and iterating over {Attribute} instances.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# set = AttributeSet.new
|
11
|
+
# set.add(Attribute.new(:name))
|
12
|
+
# set[:name] # => #<Attribute name=:name>
|
13
|
+
#
|
14
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
15
|
+
# @since 0.1.0
|
16
|
+
class AttributeSet
|
17
|
+
# @rbs @lookup: Hash[Symbol, Attribute]
|
18
|
+
|
19
|
+
# Creates a new AttributeSet instance
|
20
|
+
#
|
21
|
+
# @return [AttributeSet]
|
22
|
+
# @rbs () -> void
|
23
|
+
def initialize
|
24
|
+
@lookup = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieves an attribute by name
|
28
|
+
#
|
29
|
+
# @param attribute_name [String, Symbol] The name of the attribute to retrieve
|
30
|
+
#
|
31
|
+
# @return [Attribute, nil] The attribute with the given name, or nil if not found
|
32
|
+
# @rbs (String | Symbol attribute_name) -> Attribute?
|
33
|
+
def [](attribute_name)
|
34
|
+
@lookup[attribute_name.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds an attribute to the set
|
38
|
+
#
|
39
|
+
# @param attribute [Attribute] The attribute to add
|
40
|
+
#
|
41
|
+
# @raise [ArgumentError] If the provided attribute is not an {Attribute} instance
|
42
|
+
# @return [void]
|
43
|
+
# @rbs (Attribute attribute) -> void
|
44
|
+
def add(attribute)
|
45
|
+
unless attribute.is_a?(Attribute)
|
46
|
+
raise ArgumentError, 'Attribute must be an instance of Domainic::Command::Context::Attribute'
|
47
|
+
end
|
48
|
+
|
49
|
+
@lookup[attribute.name] = attribute
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns all attributes in the set
|
53
|
+
#
|
54
|
+
# @return [Array<Attribute>] An array of all attributes
|
55
|
+
# @rbs () -> Array[Attribute]
|
56
|
+
def all
|
57
|
+
@lookup.values
|
58
|
+
end
|
59
|
+
|
60
|
+
# Iterates over each attribute in the set
|
61
|
+
#
|
62
|
+
# @yield [Attribute] Each attribute in the set
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
# @rbs () { (Attribute) -> untyped } -> void
|
66
|
+
def each(...)
|
67
|
+
all.each(...)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Iterates over each attribute in the set with an object
|
71
|
+
#
|
72
|
+
# @overload each_with_object(object)
|
73
|
+
# @param object [Object] The object to pass to the block
|
74
|
+
# @yield [Attribute, Object] Each attribute and the object
|
75
|
+
#
|
76
|
+
# @return [Object] The final state of the object
|
77
|
+
# @rbs [U] (U object) { (Attribute, U) -> untyped } -> U
|
78
|
+
def each_with_object(...)
|
79
|
+
all.each_with_object(...) # steep:ignore UnresolvedOverloading
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Ensure that Attributes are duplicated when the AttributeSet is duplicated
|
85
|
+
#
|
86
|
+
# @param source [AttributeSet] The source AttributeSet to copy
|
87
|
+
#
|
88
|
+
# @return [AttributeSet]
|
89
|
+
def initialize_copy(source)
|
90
|
+
@lookup = source.instance_variable_get(:@lookup).transform_values(&:dup)
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|