domainic-command 0.1.0.alpha.1.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|