dry-core 0.8.1 → 1.0.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/CHANGELOG.md +43 -2
- data/LICENSE +1 -1
- data/dry-core.gemspec +1 -0
- data/lib/dry/core/class_attributes.rb +1 -3
- data/lib/dry/core/container/config.rb +34 -0
- data/lib/dry/core/container/configuration.rb +37 -0
- data/lib/dry/core/container/item/callable.rb +22 -0
- data/lib/dry/core/container/item/factory.rb +29 -0
- data/lib/dry/core/container/item/memoizable.rb +49 -0
- data/lib/dry/core/container/item.rb +53 -0
- data/lib/dry/core/container/mixin.rb +311 -0
- data/lib/dry/core/container/namespace.rb +47 -0
- data/lib/dry/core/container/namespace_dsl.rb +63 -0
- data/lib/dry/core/container/registry.rb +52 -0
- data/lib/dry/core/container/resolver.rb +90 -0
- data/lib/dry/core/container/stub.rb +57 -0
- data/lib/dry/core/container.rb +27 -0
- data/lib/dry/core/equalizer.rb +15 -11
- data/lib/dry/core/errors.rb +1 -1
- data/lib/dry/core/memoizable.rb +4 -6
- data/lib/dry/core/version.rb +1 -1
- data/lib/dry/core.rb +35 -0
- metadata +29 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 518b0050fe58a57f837980c65799982ee76a803f684288b58bb47fb7a59e91f6
|
4
|
+
data.tar.gz: 77db1195074bfd0e6de6a33a0e3b14b25c7d82e0cab202a9738d1a1ffa7f28de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9dedf1b3b3b2f1b08a0d8b2fd382d9b96bc2dafdce30a53a3cef75869bc037090bd9b30a16f032d075dd95a55212f25f6ef055af1bf1473b5083609524c65fbb
|
7
|
+
data.tar.gz: 861672b79b3286c54d4886a02b861488e30d86e9bb8ab625cd1eac32cf45b571c51e61537c69a5d48d5cd45a9f87682dc8602b7f84bb9b908774e0e8db6906f3
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,47 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
-
## 0.
|
3
|
+
## 1.0.0 2022-11-04
|
4
|
+
|
5
|
+
|
6
|
+
### Added
|
7
|
+
|
8
|
+
- Import dry-container as `Dry::Core::Container` (via #77) (@solnic)
|
9
|
+
|
10
|
+
|
11
|
+
[Compare v0.9.1...v1.0.0](https://github.com/dry-rb/dry-core/compare/v0.9.1...v1.0.0)
|
12
|
+
|
13
|
+
## 0.9.1 2022-10-18
|
14
|
+
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- Correct missing constant for IDENTITY (issue #75 fixed via #76) (@poloka)
|
19
|
+
|
20
|
+
[Compare v0.9.0...v0.9.1](https://github.com/dry-rb/dry-core/compare/v0.9.0...v0.9.1)
|
21
|
+
|
22
|
+
## 0.9.0 2022-10-15
|
23
|
+
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
|
27
|
+
- dry-core now uses zeitwerk for autoloading (@solnic)
|
28
|
+
|
29
|
+
[Compare v0.8.1...v0.9.0](https://github.com/dry-rb/dry-core/compare/v0.8.1...v0.9.0)
|
30
|
+
|
31
|
+
## 0.8.1 2022-07-27
|
32
|
+
|
33
|
+
|
34
|
+
### Fixed
|
35
|
+
|
36
|
+
- [memoizable] plays better with inheritance.
|
37
|
+
There were cases when cached values from base claesses were used, see #70 (@flash-gordon)
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
[Compare v0.8.0...v0.8.1](https://github.com/dry-rb/dry-core/compare/v0.8.0...v0.8.1)
|
42
|
+
|
43
|
+
## 0.8.0 2022-07-15
|
44
|
+
|
4
45
|
|
5
46
|
### Added
|
6
47
|
|
@@ -12,7 +53,7 @@
|
|
12
53
|
This changes the order of returned subclasses (immediate subclasses now go first) (@flash-gordon)
|
13
54
|
|
14
55
|
|
15
|
-
[Compare v0.7.1...v0.8.0](https://github.com/dry-rb/dry-core/compare/v0.7.1...
|
56
|
+
[Compare v0.7.1...v0.8.0](https://github.com/dry-rb/dry-core/compare/v0.7.1...v0.8.0)
|
16
57
|
|
17
58
|
## 0.7.1 2021-07-10
|
18
59
|
|
data/LICENSE
CHANGED
data/dry-core.gemspec
CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
# to update dependencies edit project.yml
|
32
32
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
33
|
+
spec.add_runtime_dependency "zeitwerk", "~> 2.6"
|
33
34
|
|
34
35
|
spec.add_development_dependency "bundler"
|
35
36
|
spec.add_development_dependency "rake"
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/core/constants"
|
4
|
-
require "dry/core/errors"
|
5
4
|
|
6
5
|
module Dry
|
7
6
|
module Core
|
@@ -10,7 +9,6 @@ module Dry
|
|
10
9
|
# @api public
|
11
10
|
module ClassAttributes
|
12
11
|
include Constants
|
13
|
-
|
14
12
|
# Specify what attributes a class will use
|
15
13
|
#
|
16
14
|
# @example
|
@@ -86,7 +84,7 @@ module Dry
|
|
86
84
|
elsif type === value # rubocop:disable Style/CaseEquality
|
87
85
|
instance_variable_set(ivar, coerce.call(value))
|
88
86
|
else
|
89
|
-
raise
|
87
|
+
raise InvalidClassAttributeValueError.new(name, value)
|
90
88
|
end
|
91
89
|
end
|
92
90
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# @api public
|
7
|
+
class Config
|
8
|
+
DEFAULT_NAMESPACE_SEPARATOR = "."
|
9
|
+
DEFAULT_RESOLVER = Resolver.new
|
10
|
+
DEFAULT_REGISTRY = Registry.new
|
11
|
+
|
12
|
+
# @api public
|
13
|
+
attr_accessor :namespace_separator
|
14
|
+
|
15
|
+
# @api public
|
16
|
+
attr_accessor :resolver
|
17
|
+
|
18
|
+
# @api public
|
19
|
+
attr_accessor :registry
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def initialize(
|
23
|
+
namespace_separator: DEFAULT_NAMESPACE_SEPARATOR,
|
24
|
+
resolver: DEFAULT_RESOLVER,
|
25
|
+
registry: DEFAULT_REGISTRY
|
26
|
+
)
|
27
|
+
@namespace_separator = namespace_separator
|
28
|
+
@resolver = resolver
|
29
|
+
@registry = registry
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# @api public
|
7
|
+
module Configuration
|
8
|
+
# Use dry/configurable if it's available
|
9
|
+
begin
|
10
|
+
require "dry/configurable"
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def self.extended(klass)
|
14
|
+
super
|
15
|
+
klass.class_eval do
|
16
|
+
extend Dry::Configurable
|
17
|
+
|
18
|
+
setting :namespace_separator, default: Config::DEFAULT_NAMESPACE_SEPARATOR
|
19
|
+
setting :resolver, default: Config::DEFAULT_RESOLVER
|
20
|
+
setting :registry, default: Config::DEFAULT_REGISTRY
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
# @api private
|
25
|
+
def config
|
26
|
+
@config ||= Container::Config.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def configure
|
32
|
+
yield config
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
class Item
|
7
|
+
# Callable class to returns a item call
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
class Callable < Item
|
12
|
+
# Returns the result of item call or item
|
13
|
+
#
|
14
|
+
# @return [Mixed]
|
15
|
+
def call
|
16
|
+
callable? ? item.call : item
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
class Item
|
7
|
+
# Factory for create an Item to register inside of container
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class Factory
|
11
|
+
# Creates an Item Memoizable or Callable
|
12
|
+
# @param [Mixed] item
|
13
|
+
# @param [Hash] options
|
14
|
+
#
|
15
|
+
# @raise [Dry::Core::Container::Error]
|
16
|
+
#
|
17
|
+
# @return [Dry::Core::Container::Item::Base]
|
18
|
+
def call(item, options = {})
|
19
|
+
if options[:memoize]
|
20
|
+
Item::Memoizable.new(item, options)
|
21
|
+
else
|
22
|
+
Item::Callable.new(item, options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
class Item
|
7
|
+
# Memoizable class to store and execute item calls
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
class Memoizable < Item
|
12
|
+
# @return [Mutex] the stored mutex
|
13
|
+
attr_reader :memoize_mutex
|
14
|
+
|
15
|
+
# Returns a new Memoizable instance
|
16
|
+
#
|
17
|
+
# @param [Mixed] item
|
18
|
+
# @param [Hash] options
|
19
|
+
#
|
20
|
+
# @raise [Dry::Core::Container::Error]
|
21
|
+
#
|
22
|
+
# @return [Dry::Core::Container::Item::Base]
|
23
|
+
def initialize(item, options = {})
|
24
|
+
super
|
25
|
+
raise_not_supported_error unless callable?
|
26
|
+
|
27
|
+
@memoize_mutex = ::Mutex.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the result of item call using a syncronized mutex
|
31
|
+
#
|
32
|
+
# @return [Dry::Core::Container::Item::Base]
|
33
|
+
def call
|
34
|
+
memoize_mutex.synchronize do
|
35
|
+
@memoized_item ||= item.call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @private
|
42
|
+
def raise_not_supported_error
|
43
|
+
raise ::Dry::Core::Container::Error, "Memoize only supported for a block or a proc"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# Base class to abstract Memoizable and Callable implementations
|
7
|
+
#
|
8
|
+
# @api abstract
|
9
|
+
#
|
10
|
+
class Item
|
11
|
+
# @return [Mixed] the item to be solved later
|
12
|
+
attr_reader :item
|
13
|
+
|
14
|
+
# @return [Hash] the options to memoize, call or no.
|
15
|
+
attr_reader :options
|
16
|
+
|
17
|
+
# @api abstract
|
18
|
+
def initialize(item, options = {})
|
19
|
+
@item = item
|
20
|
+
@options = {
|
21
|
+
call: item.is_a?(::Proc) && item.parameters.empty?
|
22
|
+
}.merge(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api abstract
|
26
|
+
def call
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
def value?
|
32
|
+
!callable?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def callable?
|
37
|
+
options[:call]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Build a new item with transformation applied
|
41
|
+
#
|
42
|
+
# @private
|
43
|
+
def map(func)
|
44
|
+
if callable?
|
45
|
+
self.class.new(-> { func.(item.call) }, options)
|
46
|
+
else
|
47
|
+
self.class.new(func.(item), options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/hash"
|
4
|
+
require "dry/core/constants"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Core
|
8
|
+
class Container
|
9
|
+
include Dry::Core::Constants
|
10
|
+
|
11
|
+
# @api public
|
12
|
+
Error = Class.new(StandardError)
|
13
|
+
|
14
|
+
# Error raised when key is not defined in the registry
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
KeyError = Class.new(::KeyError)
|
18
|
+
|
19
|
+
if defined?(DidYouMean::KeyErrorChecker)
|
20
|
+
DidYouMean.correct_error(KeyError, DidYouMean::KeyErrorChecker)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Mixin to expose Inversion of Control (IoC) container behaviour
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
#
|
27
|
+
# class MyClass
|
28
|
+
# extend Dry::Core::Container::Mixin
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# MyClass.register(:item, 'item')
|
32
|
+
# MyClass.resolve(:item)
|
33
|
+
# => 'item'
|
34
|
+
#
|
35
|
+
# class MyObject
|
36
|
+
# include Dry::Core::Container::Mixin
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# container = MyObject.new
|
40
|
+
# container.register(:item, 'item')
|
41
|
+
# container.resolve(:item)
|
42
|
+
# => 'item'
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
#
|
46
|
+
# rubocop:disable Metrics/ModuleLength
|
47
|
+
module Mixin
|
48
|
+
PREFIX_NAMESPACE = lambda do |namespace, key, config|
|
49
|
+
[namespace, key].join(config.namespace_separator)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @private
|
53
|
+
def self.extended(base)
|
54
|
+
hooks_mod = ::Module.new do
|
55
|
+
def inherited(subclass)
|
56
|
+
subclass.instance_variable_set(:@_container, @_container.dup)
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
base.class_eval do
|
62
|
+
extend Configuration
|
63
|
+
extend hooks_mod
|
64
|
+
|
65
|
+
@_container = ::Concurrent::Hash.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private
|
70
|
+
module Initializer
|
71
|
+
def initialize(*args, &block)
|
72
|
+
@_container = ::Concurrent::Hash.new
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @private
|
78
|
+
def self.included(base)
|
79
|
+
base.class_eval do
|
80
|
+
extend Configuration
|
81
|
+
prepend Initializer
|
82
|
+
|
83
|
+
def config
|
84
|
+
self.class.config
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Register an item with the container to be resolved later
|
90
|
+
#
|
91
|
+
# @param [Mixed] key
|
92
|
+
# The key to register the container item with (used to resolve)
|
93
|
+
# @param [Mixed] contents
|
94
|
+
# The item to register with the container (if no block given)
|
95
|
+
# @param [Hash] options
|
96
|
+
# Options to pass to the registry when registering the item
|
97
|
+
# @yield
|
98
|
+
# If a block is given, contents will be ignored and the block
|
99
|
+
# will be registered instead
|
100
|
+
#
|
101
|
+
# @return [Dry::Core::Container::Mixin] self
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def register(key, contents = nil, options = EMPTY_HASH, &block)
|
105
|
+
if block_given?
|
106
|
+
item = block
|
107
|
+
options = contents if contents.is_a?(::Hash)
|
108
|
+
else
|
109
|
+
item = contents
|
110
|
+
end
|
111
|
+
|
112
|
+
config.registry.call(_container, key, item, options)
|
113
|
+
|
114
|
+
self
|
115
|
+
rescue FrozenError
|
116
|
+
raise FrozenError,
|
117
|
+
"can't modify frozen #{self.class} (when attempting to register '#{key}')"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Resolve an item from the container
|
121
|
+
#
|
122
|
+
# @param [Mixed] key
|
123
|
+
# The key for the item you wish to resolve
|
124
|
+
# @yield
|
125
|
+
# Fallback block to call when a key is missing. Its result will be returned
|
126
|
+
# @yieldparam [Mixed] key Missing key
|
127
|
+
#
|
128
|
+
# @return [Mixed]
|
129
|
+
#
|
130
|
+
# @api public
|
131
|
+
def resolve(key, &block)
|
132
|
+
config.resolver.call(_container, key, &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Resolve an item from the container
|
136
|
+
#
|
137
|
+
# @param [Mixed] key
|
138
|
+
# The key for the item you wish to resolve
|
139
|
+
#
|
140
|
+
# @return [Mixed]
|
141
|
+
#
|
142
|
+
# @api public
|
143
|
+
# @see Dry::Core::Container::Mixin#resolve
|
144
|
+
def [](key)
|
145
|
+
resolve(key)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Merge in the items of the other container
|
149
|
+
#
|
150
|
+
# @param [Dry::Core::Container] other
|
151
|
+
# The other container to merge in
|
152
|
+
# @param [Symbol, nil] namespace
|
153
|
+
# Namespace to prefix other container items with, defaults to nil
|
154
|
+
#
|
155
|
+
# @return [Dry::Core::Container::Mixin] self
|
156
|
+
#
|
157
|
+
# @api public
|
158
|
+
def merge(other, namespace: nil, &block)
|
159
|
+
if namespace
|
160
|
+
_container.merge!(
|
161
|
+
other._container.each_with_object(::Concurrent::Hash.new) { |(key, item), hsh|
|
162
|
+
hsh[PREFIX_NAMESPACE.call(namespace, key, config)] = item
|
163
|
+
},
|
164
|
+
&block
|
165
|
+
)
|
166
|
+
else
|
167
|
+
_container.merge!(other._container, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Check whether an item is registered under the given key
|
174
|
+
#
|
175
|
+
# @param [Mixed] key
|
176
|
+
# The key you wish to check for registration with
|
177
|
+
#
|
178
|
+
# @return [Bool]
|
179
|
+
#
|
180
|
+
# @api public
|
181
|
+
def key?(key)
|
182
|
+
config.resolver.key?(_container, key)
|
183
|
+
end
|
184
|
+
|
185
|
+
# An array of registered names for the container
|
186
|
+
#
|
187
|
+
# @return [Array<String>]
|
188
|
+
#
|
189
|
+
# @api public
|
190
|
+
def keys
|
191
|
+
config.resolver.keys(_container)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Calls block once for each key in container, passing the key as a parameter.
|
195
|
+
#
|
196
|
+
# If no block is given, an enumerator is returned instead.
|
197
|
+
#
|
198
|
+
# @return [Dry::Core::Container::Mixin] self
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def each_key(&block)
|
202
|
+
config.resolver.each_key(_container, &block)
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
# Calls block once for each key/value pair in the container, passing the key and
|
207
|
+
# the registered item parameters.
|
208
|
+
#
|
209
|
+
# If no block is given, an enumerator is returned instead.
|
210
|
+
#
|
211
|
+
# @return [Enumerator]
|
212
|
+
#
|
213
|
+
# @api public
|
214
|
+
#
|
215
|
+
# @note In discussions with other developers, it was felt that being able to iterate
|
216
|
+
# over not just the registered keys, but to see what was registered would be
|
217
|
+
# very helpful. This is a step toward doing that.
|
218
|
+
def each(&block)
|
219
|
+
config.resolver.each(_container, &block)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Decorates an item from the container with specified decorator
|
223
|
+
#
|
224
|
+
# @return [Dry::Core::Container::Mixin] self
|
225
|
+
#
|
226
|
+
# @api public
|
227
|
+
def decorate(key, with: nil, &block)
|
228
|
+
key = key.to_s
|
229
|
+
original = _container.delete(key) do
|
230
|
+
raise KeyError, "Nothing registered with the key #{key.inspect}"
|
231
|
+
end
|
232
|
+
|
233
|
+
if with.is_a?(Class)
|
234
|
+
decorator = with.method(:new)
|
235
|
+
elsif block.nil? && !with.respond_to?(:call)
|
236
|
+
raise Error, "Decorator needs to be a Class, block, or respond to the `call` method"
|
237
|
+
else
|
238
|
+
decorator = with || block
|
239
|
+
end
|
240
|
+
|
241
|
+
_container[key] = original.map(decorator)
|
242
|
+
self
|
243
|
+
end
|
244
|
+
|
245
|
+
# Evaluate block and register items in namespace
|
246
|
+
#
|
247
|
+
# @param [Mixed] namespace
|
248
|
+
# The namespace to register items in
|
249
|
+
#
|
250
|
+
# @return [Dry::Core::Container::Mixin] self
|
251
|
+
#
|
252
|
+
# @api public
|
253
|
+
def namespace(namespace, &block)
|
254
|
+
::Dry::Core::Container::NamespaceDSL.new(
|
255
|
+
self,
|
256
|
+
namespace,
|
257
|
+
config.namespace_separator,
|
258
|
+
&block
|
259
|
+
)
|
260
|
+
|
261
|
+
self
|
262
|
+
end
|
263
|
+
|
264
|
+
# Import a namespace
|
265
|
+
#
|
266
|
+
# @param [Dry::Core::Container::Namespace] namespace
|
267
|
+
# The namespace to import
|
268
|
+
#
|
269
|
+
# @return [Dry::Core::Container::Mixin] self
|
270
|
+
#
|
271
|
+
# @api public
|
272
|
+
def import(namespace)
|
273
|
+
namespace(namespace.name, &namespace.block)
|
274
|
+
|
275
|
+
self
|
276
|
+
end
|
277
|
+
|
278
|
+
# Freeze the container. Nothing can be registered after freezing
|
279
|
+
#
|
280
|
+
# @api public
|
281
|
+
def freeze
|
282
|
+
super
|
283
|
+
_container.freeze
|
284
|
+
self
|
285
|
+
end
|
286
|
+
|
287
|
+
# @private no, really
|
288
|
+
def _container
|
289
|
+
@_container
|
290
|
+
end
|
291
|
+
|
292
|
+
# @api public
|
293
|
+
def dup
|
294
|
+
copy = super
|
295
|
+
copy.instance_variable_set(:@_container, _container.dup)
|
296
|
+
copy
|
297
|
+
end
|
298
|
+
|
299
|
+
# @api public
|
300
|
+
def clone
|
301
|
+
copy = super
|
302
|
+
unless copy.frozen?
|
303
|
+
copy.instance_variable_set(:@_container, _container.dup)
|
304
|
+
end
|
305
|
+
copy
|
306
|
+
end
|
307
|
+
end
|
308
|
+
# rubocop:enable Metrics/ModuleLength
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# Create a namespace to be imported
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# ns = Dry::Core::Container::Namespace.new('name') do
|
11
|
+
# register('item', 'item')
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# container = Dry::Core::Container.new
|
15
|
+
#
|
16
|
+
# container.import(ns)
|
17
|
+
#
|
18
|
+
# container.resolve('name.item')
|
19
|
+
# => 'item'
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
class Namespace
|
24
|
+
# @return [Mixed] The namespace (name)
|
25
|
+
attr_reader :name
|
26
|
+
|
27
|
+
# @return [Proc] The block to be executed when the namespace is imported
|
28
|
+
attr_reader :block
|
29
|
+
|
30
|
+
# Create a new namespace
|
31
|
+
#
|
32
|
+
# @param [Mixed] name
|
33
|
+
# The name of the namespace
|
34
|
+
# @yield
|
35
|
+
# The block to evaluate when the namespace is imported
|
36
|
+
#
|
37
|
+
# @return [Dry::Core::Container::Namespace]
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def initialize(name, &block)
|
41
|
+
@name = name
|
42
|
+
@block = block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Core
|
7
|
+
class Container
|
8
|
+
# @api private
|
9
|
+
class NamespaceDSL < ::SimpleDelegator
|
10
|
+
# DSL for defining namespaces
|
11
|
+
#
|
12
|
+
# @param [Dry::Core::Container::Mixin] container
|
13
|
+
# The container
|
14
|
+
# @param [String] namespace
|
15
|
+
# The namespace (name)
|
16
|
+
# @param [String] namespace_separator
|
17
|
+
# The namespace separator
|
18
|
+
# @yield
|
19
|
+
# The block to evaluate to define the namespace
|
20
|
+
#
|
21
|
+
# @return [Mixed]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def initialize(container, namespace, namespace_separator, &block)
|
25
|
+
@namespace = namespace
|
26
|
+
@namespace_separator = namespace_separator
|
27
|
+
|
28
|
+
super(container)
|
29
|
+
|
30
|
+
if block.arity.zero?
|
31
|
+
instance_eval(&block)
|
32
|
+
else
|
33
|
+
yield self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def register(key, *args, &block)
|
38
|
+
super(namespaced(key), *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def namespace(namespace, &block)
|
42
|
+
super(namespaced(namespace), &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def import(namespace)
|
46
|
+
namespace(namespace.name, &namespace.block)
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve(key)
|
52
|
+
super(namespaced(key))
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def namespaced(key)
|
58
|
+
[@namespace, key].join(@namespace_separator)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# Default registry for registering items with the container
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Registry
|
10
|
+
# @private
|
11
|
+
def initialize
|
12
|
+
@_mutex = ::Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Register an item with the container to be resolved later
|
16
|
+
#
|
17
|
+
# @param [Concurrent::Hash] container
|
18
|
+
# The container
|
19
|
+
# @param [Mixed] key
|
20
|
+
# The key to register the container item with (used to resolve)
|
21
|
+
# @param [Mixed] item
|
22
|
+
# The item to register with the container
|
23
|
+
# @param [Hash] options
|
24
|
+
# @option options [Symbol] :call
|
25
|
+
# Whether the item should be called when resolved
|
26
|
+
#
|
27
|
+
# @raise [Dry::Core::Container::KeyError]
|
28
|
+
# If an item is already registered with the given key
|
29
|
+
#
|
30
|
+
# @return [Mixed]
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def call(container, key, item, options)
|
34
|
+
key = key.to_s.dup.freeze
|
35
|
+
|
36
|
+
@_mutex.synchronize do
|
37
|
+
if container.key?(key)
|
38
|
+
raise KeyError, "There is already an item registered with the key #{key.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
container[key] = factory.call(item, options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def factory
|
47
|
+
@factory ||= Container::Item::Factory.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
# Default resolver for resolving items from container
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Resolver
|
10
|
+
# Resolve an item from the container
|
11
|
+
#
|
12
|
+
# @param [Concurrent::Hash] container
|
13
|
+
# The container
|
14
|
+
# @param [Mixed] key
|
15
|
+
# The key for the item you wish to resolve
|
16
|
+
# @yield
|
17
|
+
# Fallback block to call when a key is missing. Its result will be returned
|
18
|
+
# @yieldparam [Mixed] key Missing key
|
19
|
+
#
|
20
|
+
# @raise [KeyError]
|
21
|
+
# If the given key is not registered with the container (and no block provided)
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# @return [Mixed]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def call(container, key)
|
28
|
+
item = container.fetch(key.to_s) do
|
29
|
+
if block_given?
|
30
|
+
return yield(key)
|
31
|
+
else
|
32
|
+
raise KeyError.new(%(key not found: "#{key}"), key: key.to_s, receiver: container)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
item.call
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check whether an items is registered under the given key
|
40
|
+
#
|
41
|
+
# @param [Concurrent::Hash] container
|
42
|
+
# The container
|
43
|
+
# @param [Mixed] key
|
44
|
+
# The key you wish to check for registration with
|
45
|
+
#
|
46
|
+
# @return [Bool]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def key?(container, key)
|
50
|
+
container.key?(key.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
# An array of registered names for the container
|
54
|
+
#
|
55
|
+
# @return [Array]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def keys(container)
|
59
|
+
container.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
# Calls block once for each key in container, passing the key as a parameter.
|
63
|
+
#
|
64
|
+
# If no block is given, an enumerator is returned instead.
|
65
|
+
#
|
66
|
+
# @return Hash
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def each_key(container, &block)
|
70
|
+
container.each_key(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calls block once for each key in container, passing the key and
|
74
|
+
# the registered item parameters.
|
75
|
+
#
|
76
|
+
# If no block is given, an enumerator is returned instead.
|
77
|
+
#
|
78
|
+
# @return Key, Value
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
# @note In discussions with other developers, it was felt that being able
|
82
|
+
# to iterate over not just the registered keys, but to see what was
|
83
|
+
# registered would be very helpful. This is a step toward doing that.
|
84
|
+
def each(container, &block)
|
85
|
+
container.map { |key, value| [key, value.call] }.each(&block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
class Container
|
6
|
+
module Stub
|
7
|
+
# Overrides resolve to look into stubbed keys first
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def resolve(key)
|
11
|
+
_stubs.fetch(key.to_s) { super }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add a stub to the container
|
15
|
+
def stub(key, value, &block)
|
16
|
+
unless key?(key)
|
17
|
+
raise ArgumentError, "cannot stub #{key.to_s.inspect} - no such key in container"
|
18
|
+
end
|
19
|
+
|
20
|
+
_stubs[key.to_s] = value
|
21
|
+
|
22
|
+
if block
|
23
|
+
yield
|
24
|
+
unstub(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove stubbed keys from the container
|
31
|
+
def unstub(*keys)
|
32
|
+
keys = _stubs.keys if keys.empty?
|
33
|
+
keys.each { |key| _stubs.delete(key.to_s) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Stubs have already been enabled turning this into a noop
|
37
|
+
def enable_stubs!
|
38
|
+
# DO NOTHING
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Stubs container
|
44
|
+
def _stubs
|
45
|
+
@_stubs ||= {}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Mixin
|
50
|
+
# Enable stubbing functionality into the current container
|
51
|
+
def enable_stubs!
|
52
|
+
extend ::Dry::Core::Container::Stub
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Core
|
5
|
+
# Thread-safe object registry
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# container = Dry::Core::Container.new
|
10
|
+
# container.register(:item, 'item')
|
11
|
+
# container.resolve(:item)
|
12
|
+
# => 'item'
|
13
|
+
#
|
14
|
+
# container.register(:item1, -> { 'item' })
|
15
|
+
# container.resolve(:item1)
|
16
|
+
# => 'item'
|
17
|
+
#
|
18
|
+
# container.register(:item2, -> { 'item' }, call: false)
|
19
|
+
# container.resolve(:item2)
|
20
|
+
# => #<Proc:0x007f33b169e998@(irb):10 (lambda)>
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
class Container
|
24
|
+
include Container::Mixin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/dry/core/equalizer.rb
CHANGED
@@ -1,17 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dry
|
4
|
-
# Build an equalizer module for the inclusion in other class
|
5
|
-
#
|
6
|
-
# ## Credits
|
7
|
-
#
|
8
|
-
# Equalizer has been originally imported from the equalizer gem created by Dan Kubb
|
9
|
-
#
|
10
|
-
# @api public
|
11
|
-
def self.Equalizer(*keys, **options)
|
12
|
-
Dry::Core::Equalizer.new(*keys, **options)
|
13
|
-
end
|
14
|
-
|
15
4
|
module Core
|
16
5
|
# Define equality, equivalence and inspection methods
|
17
6
|
class Equalizer < ::Module
|
@@ -149,4 +138,19 @@ module Dry
|
|
149
138
|
end
|
150
139
|
end
|
151
140
|
end
|
141
|
+
|
142
|
+
# Old modules that depend on dry/core/equalizer may miss
|
143
|
+
# this method if dry/core is not required explicitly
|
144
|
+
unless singleton_class.method_defined?(:Equalizer)
|
145
|
+
# Build an equalizer module for the inclusion in other class
|
146
|
+
#
|
147
|
+
# ## Credits
|
148
|
+
#
|
149
|
+
# Equalizer has been originally imported from the equalizer gem created by Dan Kubb
|
150
|
+
#
|
151
|
+
# @api public
|
152
|
+
def self.Equalizer(*keys, **options)
|
153
|
+
Dry::Core::Equalizer.new(*keys, **options)
|
154
|
+
end
|
155
|
+
end
|
152
156
|
end
|
data/lib/dry/core/errors.rb
CHANGED
data/lib/dry/core/memoizable.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/core/deprecations"
|
4
|
-
|
5
3
|
module Dry
|
6
4
|
module Core
|
7
5
|
module Memoizable
|
@@ -18,7 +16,7 @@ module Dry
|
|
18
16
|
super
|
19
17
|
|
20
18
|
memoizer = base.ancestors.find { _1.is_a?(Memoizer) }
|
21
|
-
base.prepend(memoizer.dup)
|
19
|
+
base.prepend(memoizer.dup) if memoizer
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -91,7 +89,7 @@ module Dry
|
|
91
89
|
define_method(method.name) do
|
92
90
|
value = super()
|
93
91
|
|
94
|
-
if kernel[:frozen].
|
92
|
+
if kernel[:frozen].bind_call(self)
|
95
93
|
# It's not possible to modify singleton classes
|
96
94
|
# of frozen objects
|
97
95
|
mod.remove_method(method.name)
|
@@ -113,8 +111,8 @@ module Dry
|
|
113
111
|
# expect :)
|
114
112
|
attr_name = :"__memozed_#{key}__"
|
115
113
|
ivar_name = :"@#{attr_name}"
|
116
|
-
kernel[:ivar_set].
|
117
|
-
eigenclass = kernel[:singleton].
|
114
|
+
kernel[:ivar_set].bind_call(self, ivar_name, value)
|
115
|
+
eigenclass = kernel[:singleton].bind_call(self)
|
118
116
|
eigenclass.attr_reader(attr_name)
|
119
117
|
eigenclass.alias_method(method.name, attr_name)
|
120
118
|
eigenclass.remove_method(attr_name)
|
data/lib/dry/core/version.rb
CHANGED
data/lib/dry/core.rb
CHANGED
@@ -1,10 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "zeitwerk"
|
4
|
+
|
5
|
+
require "dry/core/constants"
|
6
|
+
require "dry/core/errors"
|
3
7
|
require "dry/core/version"
|
4
8
|
|
5
9
|
# :nodoc:
|
6
10
|
module Dry
|
7
11
|
# :nodoc:
|
8
12
|
module Core
|
13
|
+
include Constants
|
14
|
+
|
15
|
+
def self.loader
|
16
|
+
@loader ||= Zeitwerk::Loader.new.tap do |loader|
|
17
|
+
root = File.expand_path("..", __dir__)
|
18
|
+
loader.tag = "dry-core"
|
19
|
+
loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-core.rb")
|
20
|
+
loader.push_dir(root)
|
21
|
+
loader.ignore(
|
22
|
+
"#{root}/dry-core.rb",
|
23
|
+
"#{root}/dry/core/{constants,errors,version}.rb"
|
24
|
+
)
|
25
|
+
loader.inflector.inflect("namespace_dsl" => "NamespaceDSL")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
loader.setup
|
30
|
+
end
|
31
|
+
|
32
|
+
# See dry/core/equalizer.rb
|
33
|
+
unless singleton_class.method_defined?(:Equalizer)
|
34
|
+
# Build an equalizer module for the inclusion in other class
|
35
|
+
#
|
36
|
+
# ## Credits
|
37
|
+
#
|
38
|
+
# Equalizer has been originally imported from the equalizer gem created by Dan Kubb
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def self.Equalizer(*keys, **options)
|
42
|
+
Dry::Core::Equalizer.new(*keys, **options)
|
43
|
+
end
|
9
44
|
end
|
10
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Shilnikov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: zeitwerk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.6'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,6 +98,19 @@ files:
|
|
84
98
|
- lib/dry/core/class_attributes.rb
|
85
99
|
- lib/dry/core/class_builder.rb
|
86
100
|
- lib/dry/core/constants.rb
|
101
|
+
- lib/dry/core/container.rb
|
102
|
+
- lib/dry/core/container/config.rb
|
103
|
+
- lib/dry/core/container/configuration.rb
|
104
|
+
- lib/dry/core/container/item.rb
|
105
|
+
- lib/dry/core/container/item/callable.rb
|
106
|
+
- lib/dry/core/container/item/factory.rb
|
107
|
+
- lib/dry/core/container/item/memoizable.rb
|
108
|
+
- lib/dry/core/container/mixin.rb
|
109
|
+
- lib/dry/core/container/namespace.rb
|
110
|
+
- lib/dry/core/container/namespace_dsl.rb
|
111
|
+
- lib/dry/core/container/registry.rb
|
112
|
+
- lib/dry/core/container/resolver.rb
|
113
|
+
- lib/dry/core/container/stub.rb
|
87
114
|
- lib/dry/core/deprecations.rb
|
88
115
|
- lib/dry/core/descendants_tracker.rb
|
89
116
|
- lib/dry/core/equalizer.rb
|