dry-core 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81d1266061bbae6eb6d4c08dba39d2787f29f033ec6b8ed8555830e170cae6fc
4
- data.tar.gz: bb44a8177749013af0a3245e5e7e9c168818700c6d93b5dbbcf4d46d05e9e4fd
3
+ metadata.gz: 518b0050fe58a57f837980c65799982ee76a803f684288b58bb47fb7a59e91f6
4
+ data.tar.gz: 77db1195074bfd0e6de6a33a0e3b14b25c7d82e0cab202a9738d1a1ffa7f28de
5
5
  SHA512:
6
- metadata.gz: 48c5e7b008c7f1b3ec9d23801f020ee67f34199aec9e9690066cd7a4316e7d79e48c77056df180679e22767b367b4988c0f617948c49d931e5d62f3be54a1c4f
7
- data.tar.gz: a2eb13da61361ef1d7c2ebb52390df5406f1598d1a9f64c2b11de9fc574d51f28920cc34342dfa3027bb9eb73b13802114b0d0aaafd51e0688cda7b66fd647bc
6
+ metadata.gz: 9dedf1b3b3b2f1b08a0d8b2fd382d9b96bc2dafdce30a53a3cef75869bc037090bd9b30a16f032d075dd95a55212f25f6ef055af1bf1473b5083609524c65fbb
7
+ data.tar.gz: 861672b79b3286c54d4886a02b861488e30d86e9bb8ab625cd1eac32cf45b571c51e61537c69a5d48d5cd45a9f87682dc8602b7f84bb9b908774e0e8db6906f3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
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
+
3
13
  ## 0.9.1 2022-10-18
4
14
 
5
15
 
@@ -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
@@ -16,7 +16,7 @@ module Dry
16
16
  super
17
17
 
18
18
  memoizer = base.ancestors.find { _1.is_a?(Memoizer) }
19
- base.prepend(memoizer.dup)
19
+ base.prepend(memoizer.dup) if memoizer
20
20
  end
21
21
  end
22
22
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Core
5
- VERSION = "0.9.1"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
data/lib/dry/core.rb CHANGED
@@ -22,6 +22,7 @@ module Dry
22
22
  "#{root}/dry-core.rb",
23
23
  "#{root}/dry/core/{constants,errors,version}.rb"
24
24
  )
25
+ loader.inflector.inflect("namespace_dsl" => "NamespaceDSL")
25
26
  end
26
27
  end
27
28
 
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.9.1
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-10-18 00:00:00.000000000 Z
11
+ date: 2022-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -98,6 +98,19 @@ files:
98
98
  - lib/dry/core/class_attributes.rb
99
99
  - lib/dry/core/class_builder.rb
100
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
101
114
  - lib/dry/core/deprecations.rb
102
115
  - lib/dry/core/descendants_tracker.rb
103
116
  - lib/dry/core/equalizer.rb