super-lite-hub 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of super-lite-hub might be problematic. Click here for more details.

@@ -0,0 +1,353 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/hash"
4
+
5
+ module Dry
6
+ class Container
7
+ # @api public
8
+ class Config
9
+ DEFAULT_NAMESPACE_SEPARATOR = "."
10
+ DEFAULT_RESOLVER = Resolver.new
11
+ DEFAULT_REGISTRY = Registry.new
12
+
13
+ # @api public
14
+ attr_accessor :namespace_separator
15
+
16
+ # @api public
17
+ attr_accessor :resolver
18
+
19
+ # @api public
20
+ attr_accessor :registry
21
+
22
+ # @api private
23
+ def initialize(
24
+ namespace_separator: DEFAULT_NAMESPACE_SEPARATOR,
25
+ resolver: DEFAULT_RESOLVER,
26
+ registry: DEFAULT_REGISTRY
27
+ )
28
+ @namespace_separator = namespace_separator
29
+ @resolver = resolver
30
+ @registry = registry
31
+ end
32
+ end
33
+
34
+ # @api public
35
+ module Configuration
36
+ # Use dry/configurable if it's available
37
+ begin
38
+ require "dry/configurable"
39
+
40
+ # @api private
41
+ def self.extended(klass)
42
+ super
43
+ klass.class_eval do
44
+ extend Dry::Configurable
45
+
46
+ setting :namespace_separator, default: Config::DEFAULT_NAMESPACE_SEPARATOR
47
+ setting :resolver, default: Config::DEFAULT_RESOLVER
48
+ setting :registry, default: Config::DEFAULT_REGISTRY
49
+ end
50
+ end
51
+ rescue LoadError
52
+ # @api private
53
+ def config
54
+ @config ||= Config.new
55
+ end
56
+ end
57
+
58
+ # @api private
59
+ def configure
60
+ yield config
61
+ end
62
+ end
63
+
64
+ PREFIX_NAMESPACE = lambda do |namespace, key, config|
65
+ [namespace, key].join(config.namespace_separator)
66
+ end
67
+
68
+ EMPTY_HASH = {}.freeze
69
+
70
+ # Mixin to expose Inversion of Control (IoC) container behaviour
71
+ #
72
+ # @example
73
+ #
74
+ # class MyClass
75
+ # extend Dry::Container::Mixin
76
+ # end
77
+ #
78
+ # MyClass.register(:item, 'item')
79
+ # MyClass.resolve(:item)
80
+ # => 'item'
81
+ #
82
+ # class MyObject
83
+ # include Dry::Container::Mixin
84
+ # end
85
+ #
86
+ # container = MyObject.new
87
+ # container.register(:item, 'item')
88
+ # container.resolve(:item)
89
+ # => 'item'
90
+ #
91
+ # @api public
92
+ #
93
+ # rubocop:disable Metrics/ModuleLength
94
+ module Mixin
95
+ # @private
96
+ def self.extended(base)
97
+ hooks_mod = ::Module.new do
98
+ def inherited(subclass)
99
+ subclass.instance_variable_set(:@_container, @_container.dup)
100
+ super
101
+ end
102
+ end
103
+
104
+ base.class_eval do
105
+ extend Configuration
106
+ extend hooks_mod
107
+
108
+ @_container = ::Concurrent::Hash.new
109
+ end
110
+ end
111
+
112
+ # @private
113
+ module Initializer
114
+ def initialize(*args, &block)
115
+ @_container = ::Concurrent::Hash.new
116
+ super
117
+ end
118
+ end
119
+
120
+ # @private
121
+ def self.included(base)
122
+ base.class_eval do
123
+ extend Configuration
124
+ prepend Initializer
125
+
126
+ def config
127
+ self.class.config
128
+ end
129
+ end
130
+ end
131
+
132
+ # Register an item with the container to be resolved later
133
+ #
134
+ # @param [Mixed] key
135
+ # The key to register the container item with (used to resolve)
136
+ # @param [Mixed] contents
137
+ # The item to register with the container (if no block given)
138
+ # @param [Hash] options
139
+ # Options to pass to the registry when registering the item
140
+ # @yield
141
+ # If a block is given, contents will be ignored and the block
142
+ # will be registered instead
143
+ #
144
+ # @return [Dry::Container::Mixin] self
145
+ #
146
+ # @api public
147
+ def register(key, contents = nil, options = EMPTY_HASH, &block)
148
+ if block_given?
149
+ item = block
150
+ options = contents if contents.is_a?(::Hash)
151
+ else
152
+ item = contents
153
+ end
154
+
155
+ config.registry.call(_container, key, item, options)
156
+
157
+ self
158
+ rescue FrozenError
159
+ raise FrozenError,
160
+ "can't modify frozen #{self.class} (when attempting to register '#{key}')"
161
+ end
162
+
163
+ # Resolve an item from the container
164
+ #
165
+ # @param [Mixed] key
166
+ # The key for the item you wish to resolve
167
+ # @yield
168
+ # Fallback block to call when a key is missing. Its result will be returned
169
+ # @yieldparam [Mixed] key Missing key
170
+ #
171
+ # @return [Mixed]
172
+ #
173
+ # @api public
174
+ def resolve(key, &block)
175
+ config.resolver.call(_container, key, &block)
176
+ end
177
+
178
+ # Resolve an item from the container
179
+ #
180
+ # @param [Mixed] key
181
+ # The key for the item you wish to resolve
182
+ #
183
+ # @return [Mixed]
184
+ #
185
+ # @api public
186
+ # @see Dry::Container::Mixin#resolve
187
+ def [](key)
188
+ resolve(key)
189
+ end
190
+
191
+ # Merge in the items of the other container
192
+ #
193
+ # @param [Dry::Container] other
194
+ # The other container to merge in
195
+ # @param [Symbol, nil] namespace
196
+ # Namespace to prefix other container items with, defaults to nil
197
+ #
198
+ # @return [Dry::Container::Mixin] self
199
+ #
200
+ # @api public
201
+ def merge(other, namespace: nil, &block)
202
+ if namespace
203
+ _container.merge!(
204
+ other._container.each_with_object(::Concurrent::Hash.new) { |(key, item), hsh|
205
+ hsh[PREFIX_NAMESPACE.call(namespace, key, config)] = item
206
+ },
207
+ &block
208
+ )
209
+ else
210
+ _container.merge!(other._container, &block)
211
+ end
212
+
213
+ self
214
+ end
215
+
216
+ # Check whether an item is registered under the given key
217
+ #
218
+ # @param [Mixed] key
219
+ # The key you wish to check for registration with
220
+ #
221
+ # @return [Bool]
222
+ #
223
+ # @api public
224
+ def key?(key)
225
+ config.resolver.key?(_container, key)
226
+ end
227
+
228
+ # An array of registered names for the container
229
+ #
230
+ # @return [Array<String>]
231
+ #
232
+ # @api public
233
+ def keys
234
+ config.resolver.keys(_container)
235
+ end
236
+
237
+ # Calls block once for each key in container, passing the key as a parameter.
238
+ #
239
+ # If no block is given, an enumerator is returned instead.
240
+ #
241
+ # @return [Dry::Container::Mixin] self
242
+ #
243
+ # @api public
244
+ def each_key(&block)
245
+ config.resolver.each_key(_container, &block)
246
+ self
247
+ end
248
+
249
+ # Calls block once for each key/value pair in the container, passing the key and
250
+ # the registered item parameters.
251
+ #
252
+ # If no block is given, an enumerator is returned instead.
253
+ #
254
+ # @return [Enumerator]
255
+ #
256
+ # @api public
257
+ #
258
+ # @note In discussions with other developers, it was felt that being able to iterate
259
+ # over not just the registered keys, but to see what was registered would be
260
+ # very helpful. This is a step toward doing that.
261
+ def each(&block)
262
+ config.resolver.each(_container, &block)
263
+ end
264
+
265
+ # Decorates an item from the container with specified decorator
266
+ #
267
+ # @return [Dry::Container::Mixin] self
268
+ #
269
+ # @api public
270
+ def decorate(key, with: nil, &block)
271
+ key = key.to_s
272
+ original = _container.delete(key) do
273
+ raise Error, "Nothing registered with the key #{key.inspect}"
274
+ end
275
+
276
+ if with.is_a?(Class)
277
+ decorator = with.method(:new)
278
+ elsif block.nil? && !with.respond_to?(:call)
279
+ raise Error, "Decorator needs to be a Class, block, or respond to the `call` method"
280
+ else
281
+ decorator = with || block
282
+ end
283
+
284
+ _container[key] = original.map(decorator)
285
+ self
286
+ end
287
+
288
+ # Evaluate block and register items in namespace
289
+ #
290
+ # @param [Mixed] namespace
291
+ # The namespace to register items in
292
+ #
293
+ # @return [Dry::Container::Mixin] self
294
+ #
295
+ # @api public
296
+ def namespace(namespace, &block)
297
+ ::Dry::Container::NamespaceDSL.new(
298
+ self,
299
+ namespace,
300
+ config.namespace_separator,
301
+ &block
302
+ )
303
+
304
+ self
305
+ end
306
+
307
+ # Import a namespace
308
+ #
309
+ # @param [Dry::Container::Namespace] namespace
310
+ # The namespace to import
311
+ #
312
+ # @return [Dry::Container::Mixin] self
313
+ #
314
+ # @api public
315
+ def import(namespace)
316
+ namespace(namespace.name, &namespace.block)
317
+
318
+ self
319
+ end
320
+
321
+ # Freeze the container. Nothing can be registered after freezing
322
+ #
323
+ # @api public
324
+ def freeze
325
+ super
326
+ _container.freeze
327
+ self
328
+ end
329
+
330
+ # @private no, really
331
+ def _container
332
+ @_container
333
+ end
334
+
335
+ # @api public
336
+ def dup
337
+ copy = super
338
+ copy.instance_variable_set(:@_container, _container.dup)
339
+ copy
340
+ end
341
+
342
+ # @api public
343
+ def clone
344
+ copy = super
345
+ unless copy.frozen?
346
+ copy.instance_variable_set(:@_container, _container.dup)
347
+ end
348
+ copy
349
+ end
350
+ end
351
+ # rubocop:enable Metrics/ModuleLength
352
+ end
353
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Container
5
+ # Create a namespace to be imported
6
+ #
7
+ # @example
8
+ #
9
+ # ns = Dry::Container::Namespace.new('name') do
10
+ # register('item', 'item')
11
+ # end
12
+ #
13
+ # container = Dry::Container.new
14
+ #
15
+ # container.import(ns)
16
+ #
17
+ # container.resolve('name.item')
18
+ # => 'item'
19
+ #
20
+ #
21
+ # @api public
22
+ class Namespace
23
+ # @return [Mixed] The namespace (name)
24
+ attr_reader :name
25
+ # @return [Proc] The block to be executed when the namespace is imported
26
+ attr_reader :block
27
+
28
+ # Create a new namespace
29
+ #
30
+ # @param [Mixed] name
31
+ # The name of the namespace
32
+ # @yield
33
+ # The block to evaluate when the namespace is imported
34
+ #
35
+ # @return [Dry::Container::Namespace]
36
+ #
37
+ # @api public
38
+ def initialize(name, &block)
39
+ @name = name
40
+ @block = block
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module Dry
6
+ class Container
7
+ # @api private
8
+ class NamespaceDSL < ::SimpleDelegator
9
+ # DSL for defining namespaces
10
+ #
11
+ # @param [Dry::Container::Mixin] container
12
+ # The container
13
+ # @param [String] namespace
14
+ # The namespace (name)
15
+ # @param [String] namespace_separator
16
+ # The namespace separator
17
+ # @yield
18
+ # The block to evaluate to define the namespace
19
+ #
20
+ # @return [Mixed]
21
+ #
22
+ # @api private
23
+ def initialize(container, namespace, namespace_separator, &block)
24
+ @namespace = namespace
25
+ @namespace_separator = namespace_separator
26
+
27
+ super(container)
28
+
29
+ if block.arity.zero?
30
+ instance_eval(&block)
31
+ else
32
+ yield self
33
+ end
34
+ end
35
+
36
+ def register(key, *args, &block)
37
+ super(namespaced(key), *args, &block)
38
+ end
39
+
40
+ def namespace(namespace, &block)
41
+ super(namespaced(namespace), &block)
42
+ end
43
+
44
+ def import(namespace)
45
+ namespace(namespace.name, &namespace.block)
46
+
47
+ self
48
+ end
49
+
50
+ def resolve(key)
51
+ super(namespaced(key))
52
+ end
53
+
54
+ private
55
+
56
+ def namespaced(key)
57
+ [@namespace, key].join(@namespace_separator)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/container/item/factory"
4
+
5
+ module Dry
6
+ class Container
7
+ # Default registry for registering items with the container
8
+ #
9
+ # @api public
10
+ class Registry
11
+ # @private
12
+ def initialize
13
+ @_mutex = ::Mutex.new
14
+ end
15
+
16
+ # Register an item with the container to be resolved later
17
+ #
18
+ # @param [Concurrent::Hash] container
19
+ # The container
20
+ # @param [Mixed] key
21
+ # The key to register the container item with (used to resolve)
22
+ # @param [Mixed] item
23
+ # The item to register with the container
24
+ # @param [Hash] options
25
+ # @option options [Symbol] :call
26
+ # Whether the item should be called when resolved
27
+ #
28
+ # @raise [Dry::Container::Error]
29
+ # If an item is already registered with the given key
30
+ #
31
+ # @return [Mixed]
32
+ #
33
+ # @api public
34
+ def call(container, key, item, options)
35
+ key = key.to_s.dup.freeze
36
+ @_mutex.synchronize do
37
+ if container.key?(key)
38
+ raise Error, "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
+ def factory
46
+ @factory ||= ::Dry::Container::Item::Factory.new
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Container
5
+ # Default resolver for resolving items from container
6
+ #
7
+ # @api public
8
+ class Resolver
9
+ # Resolve an item from the container
10
+ #
11
+ # @param [Concurrent::Hash] container
12
+ # The container
13
+ # @param [Mixed] key
14
+ # The key for the item you wish to resolve
15
+ # @yield
16
+ # Fallback block to call when a key is missing. Its result will be returned
17
+ # @yieldparam [Mixed] key Missing key
18
+ #
19
+ # @raise [KeyError]
20
+ # If the given key is not registered with the container (and no block provided)
21
+ #
22
+ #
23
+ # @return [Mixed]
24
+ #
25
+ # @api public
26
+ def call(container, key)
27
+ item = container.fetch(key.to_s) do
28
+ if block_given?
29
+ return yield(key)
30
+ else
31
+ raise KeyError.new(%(key not found: "#{key}"), key: key.to_s, receiver: container)
32
+ end
33
+ end
34
+
35
+ item.call
36
+ end
37
+
38
+ # Check whether an items is registered under the given key
39
+ #
40
+ # @param [Concurrent::Hash] container
41
+ # The container
42
+ # @param [Mixed] key
43
+ # The key you wish to check for registration with
44
+ #
45
+ # @return [Bool]
46
+ #
47
+ # @api public
48
+ def key?(container, key)
49
+ container.key?(key.to_s)
50
+ end
51
+
52
+ # An array of registered names for the container
53
+ #
54
+ # @return [Array]
55
+ #
56
+ # @api public
57
+ def keys(container)
58
+ container.keys
59
+ end
60
+
61
+ # Calls block once for each key in container, passing the key as a parameter.
62
+ #
63
+ # If no block is given, an enumerator is returned instead.
64
+ #
65
+ # @return Hash
66
+ #
67
+ # @api public
68
+ def each_key(container, &block)
69
+ container.each_key(&block)
70
+ end
71
+
72
+ # Calls block once for each key in container, passing the key and
73
+ # the registered item parameters.
74
+ #
75
+ # If no block is given, an enumerator is returned instead.
76
+ #
77
+ # @return Key, Value
78
+ #
79
+ # @api public
80
+ # @note In discussions with other developers, it was felt that being able
81
+ # to iterate over not just the registered keys, but to see what was
82
+ # registered would be very helpful. This is a step toward doing that.
83
+ def each(container, &block)
84
+ container.map { |key, value| [key, value.call] }.each(&block)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Container
5
+ module Stub
6
+ # Overrides resolve to look into stubbed keys first
7
+ #
8
+ # @api public
9
+ def resolve(key)
10
+ _stubs.fetch(key.to_s) { super }
11
+ end
12
+
13
+ # Add a stub to the container
14
+ def stub(key, value, &block)
15
+ unless key?(key)
16
+ raise ArgumentError, "cannot stub #{key.to_s.inspect} - no such key in container"
17
+ end
18
+
19
+ _stubs[key.to_s] = value
20
+
21
+ if block
22
+ yield
23
+ unstub(key)
24
+ end
25
+
26
+ self
27
+ end
28
+
29
+ # Remove stubbed keys from the container
30
+ def unstub(*keys)
31
+ keys = _stubs.keys if keys.empty?
32
+ keys.each { |key| _stubs.delete(key.to_s) }
33
+ end
34
+
35
+ # Stubs have already been enabled turning this into a noop
36
+ def enable_stubs!
37
+ # DO NOTHING
38
+ end
39
+
40
+ private
41
+
42
+ # Stubs container
43
+ def _stubs
44
+ @_stubs ||= {}
45
+ end
46
+ end
47
+
48
+ module Mixin
49
+ # Enable stubbing functionality into the current container
50
+ def enable_stubs!
51
+ extend ::Dry::Container::Stub
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Container
5
+ # @api public
6
+ VERSION = "0.11.0"
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/container/error"
4
+ require "dry/container/namespace"
5
+ require "dry/container/registry"
6
+ require "dry/container/resolver"
7
+ require "dry/container/namespace_dsl"
8
+ require "dry/container/mixin"
9
+ require "dry/container/version"
10
+
11
+ # A collection of micro-libraries, each intended to encapsulate
12
+ # a common task in Ruby
13
+ module Dry
14
+ # Inversion of Control (IoC) container
15
+ #
16
+ # @example
17
+ #
18
+ # container = Dry::Container.new
19
+ # container.register(:item, 'item')
20
+ # container.resolve(:item)
21
+ # => 'item'
22
+ #
23
+ # container.register(:item1, -> { 'item' })
24
+ # container.resolve(:item1)
25
+ # => 'item'
26
+ #
27
+ # container.register(:item2, -> { 'item' }, call: false)
28
+ # container.resolve(:item2)
29
+ # => #<Proc:0x007f33b169e998@(irb):10 (lambda)>
30
+ #
31
+ # @api public
32
+ class Container
33
+ include ::Dry::Container::Mixin
34
+ end
35
+ end