dry-system 0.18.1 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +678 -0
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-system.gemspec +18 -21
- data/lib/dry/system/auto_registrar.rb +9 -64
- data/lib/dry/system/component.rb +124 -104
- data/lib/dry/system/component_dir.rb +171 -0
- data/lib/dry/system/config/component_dir.rb +228 -0
- data/lib/dry/system/config/component_dirs.rb +289 -0
- data/lib/dry/system/config/namespace.rb +75 -0
- data/lib/dry/system/config/namespaces.rb +196 -0
- data/lib/dry/system/constants.rb +2 -4
- data/lib/dry/system/container.rb +305 -345
- data/lib/dry/system/errors.rb +73 -56
- data/lib/dry/system/identifier.rb +176 -0
- data/lib/dry/system/importer.rb +89 -12
- data/lib/dry/system/indirect_component.rb +63 -0
- data/lib/dry/system/loader/autoloading.rb +24 -0
- data/lib/dry/system/loader.rb +49 -41
- data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +13 -14
- data/lib/dry/system/plugins/bootsnap.rb +3 -2
- data/lib/dry/system/plugins/dependency_graph/strategies.rb +38 -2
- data/lib/dry/system/plugins/dependency_graph.rb +25 -21
- data/lib/dry/system/plugins/env.rb +3 -2
- data/lib/dry/system/plugins/logging.rb +9 -8
- data/lib/dry/system/plugins/monitoring.rb +1 -2
- data/lib/dry/system/plugins/notifications.rb +1 -1
- data/lib/dry/system/plugins/plugin.rb +61 -0
- data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
- data/lib/dry/system/plugins/zeitwerk.rb +109 -0
- data/lib/dry/system/plugins.rb +5 -73
- data/lib/dry/system/provider/source.rb +276 -0
- data/lib/dry/system/provider/source_dsl.rb +55 -0
- data/lib/dry/system/provider.rb +261 -23
- data/lib/dry/system/provider_registrar.rb +251 -0
- data/lib/dry/system/provider_source_registry.rb +56 -0
- data/lib/dry/system/provider_sources/settings/config.rb +73 -0
- data/lib/dry/system/provider_sources/settings/loader.rb +44 -0
- data/lib/dry/system/provider_sources/settings.rb +40 -0
- data/lib/dry/system/provider_sources.rb +5 -0
- data/lib/dry/system/stubs.rb +6 -2
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +35 -13
- metadata +48 -97
- data/lib/dry/system/auto_registrar/configuration.rb +0 -43
- data/lib/dry/system/booter/component_registry.rb +0 -35
- data/lib/dry/system/booter.rb +0 -181
- data/lib/dry/system/components/bootable.rb +0 -289
- data/lib/dry/system/components/config.rb +0 -35
- data/lib/dry/system/components.rb +0 -8
- data/lib/dry/system/lifecycle.rb +0 -135
- data/lib/dry/system/provider_registry.rb +0 -27
- data/lib/dry/system/settings/file_loader.rb +0 -30
- data/lib/dry/system/settings/file_parser.rb +0 -51
- data/lib/dry/system/settings.rb +0 -67
- data/lib/dry/system/system_components/settings.rb +0 -11
data/lib/dry/system/container.rb
CHANGED
@@ -2,24 +2,10 @@
|
|
2
2
|
|
3
3
|
require "pathname"
|
4
4
|
|
5
|
-
require "dry
|
6
|
-
require "dry
|
7
|
-
require "dry-container"
|
5
|
+
require "dry/configurable"
|
6
|
+
require "dry/auto_inject"
|
8
7
|
require "dry/inflector"
|
9
8
|
|
10
|
-
require "dry/core/deprecations"
|
11
|
-
|
12
|
-
require "dry/system"
|
13
|
-
require "dry/system/errors"
|
14
|
-
require "dry/system/loader"
|
15
|
-
require "dry/system/booter"
|
16
|
-
require "dry/system/auto_registrar"
|
17
|
-
require "dry/system/manual_registrar"
|
18
|
-
require "dry/system/importer"
|
19
|
-
require "dry/system/component"
|
20
|
-
require "dry/system/constants"
|
21
|
-
require "dry/system/plugins"
|
22
|
-
|
23
9
|
module Dry
|
24
10
|
module System
|
25
11
|
# Abstract container class to inherit from
|
@@ -46,7 +32,7 @@ module Dry
|
|
46
32
|
#
|
47
33
|
# Every container needs to be configured with following settings:
|
48
34
|
#
|
49
|
-
# * `:name` - a unique container
|
35
|
+
# * `:name` - a unique container name
|
50
36
|
# * `:root` - a system root directory (defaults to `pwd`)
|
51
37
|
#
|
52
38
|
# @example
|
@@ -61,219 +47,234 @@ module Dry
|
|
61
47
|
# end
|
62
48
|
#
|
63
49
|
# # this will configure $LOAD_PATH to include your `lib` dir
|
64
|
-
#
|
50
|
+
# add_dirs_to_load_paths!('lib')
|
65
51
|
# end
|
66
52
|
#
|
67
53
|
# @api public
|
68
54
|
class Container
|
69
|
-
extend Dry::
|
70
|
-
extend Dry::Container::Mixin
|
55
|
+
extend Dry::Core::Container::Mixin
|
71
56
|
extend Dry::System::Plugins
|
72
57
|
|
73
58
|
setting :name
|
74
|
-
setting :
|
75
|
-
setting
|
76
|
-
setting :
|
77
|
-
setting :
|
78
|
-
setting :
|
79
|
-
setting :
|
80
|
-
setting :
|
81
|
-
setting :
|
82
|
-
setting :
|
83
|
-
setting :
|
84
|
-
|
85
|
-
|
86
|
-
|
59
|
+
setting :root, default: Pathname.pwd.freeze, constructor: ->(path) { Pathname(path) }
|
60
|
+
setting :provider_dirs, default: ["system/providers"]
|
61
|
+
setting :registrations_dir, default: "system/registrations"
|
62
|
+
setting :component_dirs, default: Config::ComponentDirs.new, cloneable: true
|
63
|
+
setting :exports, reader: true
|
64
|
+
setting :inflector, default: Dry::Inflector.new
|
65
|
+
setting :auto_registrar, default: Dry::System::AutoRegistrar
|
66
|
+
setting :manifest_registrar, default: Dry::System::ManifestRegistrar
|
67
|
+
setting :provider_registrar, default: Dry::System::ProviderRegistrar
|
68
|
+
setting :importer, default: Dry::System::Importer
|
69
|
+
|
70
|
+
# Expect "." as key namespace separator. This is not intended to be user-configurable.
|
71
|
+
config.namespace_separator = KEY_SEPARATOR
|
87
72
|
|
88
73
|
class << self
|
89
|
-
|
90
|
-
|
91
|
-
@strategies = value
|
92
|
-
else
|
93
|
-
@strategies ||= Dry::AutoInject::Strategies
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
extend Dry::Core::Deprecations["Dry::System::Container"]
|
98
|
-
|
99
|
-
# Define a new configuration setting
|
74
|
+
# @!method config
|
75
|
+
# Returns the configuration for the container
|
100
76
|
#
|
101
|
-
#
|
77
|
+
# @example
|
78
|
+
# container.config.root = "/path/to/app"
|
79
|
+
# container.config.root # => #<Pathname:/path/to/app>
|
102
80
|
#
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
# TODO: dry-configurable needs a public API for this
|
107
|
-
config._settings << _settings[name]
|
108
|
-
self
|
109
|
-
end
|
81
|
+
# @return [Dry::Configurable::Config]
|
82
|
+
#
|
83
|
+
# @api public
|
110
84
|
|
111
|
-
#
|
85
|
+
# Yields a configuration object for the container, which you can use to modify the
|
86
|
+
# configuration, then runs the after-`configured` hooks and finalizes (freezes)
|
87
|
+
# the {config}.
|
88
|
+
#
|
89
|
+
# Does not finalize the config when given `finalize_config: false`
|
112
90
|
#
|
113
91
|
# @example
|
114
92
|
# class MyApp < Dry::System::Container
|
115
93
|
# configure do |config|
|
116
94
|
# config.root = Pathname("/path/to/app")
|
117
95
|
# config.name = :my_app
|
118
|
-
# config.auto_register = %w(lib/apis lib/core)
|
119
96
|
# end
|
120
97
|
# end
|
121
98
|
#
|
99
|
+
# @param finalize_config [Boolean]
|
100
|
+
#
|
122
101
|
# @return [self]
|
123
102
|
#
|
103
|
+
# @see after
|
104
|
+
#
|
124
105
|
# @api public
|
125
|
-
def configure(&block)
|
126
|
-
hooks[:before_configure].each { |hook| instance_eval(&hook) }
|
106
|
+
def configure(finalize_config: true, &block)
|
127
107
|
super(&block)
|
128
|
-
|
108
|
+
configured!(finalize_config: finalize_config)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Marks the container as configured, runs the after-`configured` hooks, then
|
112
|
+
# finalizes (freezes) the {config}.
|
113
|
+
#
|
114
|
+
# This method is useful to call if you're modifying the container's {config}
|
115
|
+
# directly, rather than via the config object yielded when calling {configure}.
|
116
|
+
#
|
117
|
+
# Does not finalize the config if given `finalize_config: false`.
|
118
|
+
#
|
119
|
+
# @param finalize_config [Boolean]
|
120
|
+
#
|
121
|
+
# @return [self]
|
122
|
+
#
|
123
|
+
# @see after
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def configured!(finalize_config: true)
|
127
|
+
return self if configured?
|
128
|
+
|
129
129
|
hooks[:after_configure].each { |hook| instance_eval(&hook) }
|
130
|
+
|
131
|
+
_configurable_finalize! if finalize_config
|
132
|
+
|
133
|
+
@__configured__ = true
|
134
|
+
|
130
135
|
self
|
131
136
|
end
|
132
137
|
|
138
|
+
# Finalizes the config for this container
|
139
|
+
#
|
140
|
+
# @api private
|
141
|
+
alias_method :_configurable_finalize!, :finalize!
|
142
|
+
|
143
|
+
def configured?
|
144
|
+
@__configured__.equal?(true)
|
145
|
+
end
|
146
|
+
|
133
147
|
# Registers another container for import
|
134
148
|
#
|
135
149
|
# @example
|
136
150
|
# # system/container.rb
|
151
|
+
# require "dry/system/container"
|
152
|
+
# require "logger"
|
153
|
+
#
|
137
154
|
# class Core < Dry::System::Container
|
138
|
-
#
|
139
|
-
# config.root = Pathname("/path/to/app")
|
140
|
-
# config.auto_register = %w(lib/apis lib/core)
|
141
|
-
# end
|
155
|
+
# register("logger", Logger.new($stdout))
|
142
156
|
# end
|
143
157
|
#
|
144
158
|
# # apps/my_app/system/container.rb
|
145
159
|
# require 'system/container'
|
146
160
|
#
|
147
161
|
# class MyApp < Dry::System::Container
|
148
|
-
#
|
149
|
-
# config.root = Pathname("/path/to/app")
|
150
|
-
# config.auto_register = %w(lib/apis lib/core)
|
151
|
-
# end
|
152
|
-
#
|
153
|
-
# import core: Core
|
162
|
+
# import(from: Core, as: :core)
|
154
163
|
# end
|
155
164
|
#
|
156
|
-
#
|
165
|
+
# MyApp.import(keys: ["logger"], from: Core, as: :core2)
|
166
|
+
#
|
167
|
+
# MyApp["core.logger"].info("Test")
|
168
|
+
# MyApp["core2.logger"].info("Test2")
|
169
|
+
#
|
170
|
+
# @param keys [Array<String>] Keys for the components to import
|
171
|
+
# @param from [Class] The container to import from
|
172
|
+
# @param as [Symbol] Namespace to use for the components of the imported container
|
173
|
+
#
|
174
|
+
# @raise [Dry::System::ContainerAlreadyFinalizedError] if the container has already
|
175
|
+
# been finalized
|
157
176
|
#
|
158
177
|
# @api public
|
159
|
-
def import(
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
+other+ must be a hash of names and systems, or a Dry::Container namespace
|
166
|
-
STR
|
167
|
-
end
|
178
|
+
def import(from:, as:, keys: nil)
|
179
|
+
raise Dry::System::ContainerAlreadyFinalizedError if finalized?
|
180
|
+
|
181
|
+
importer.register(container: from, namespace: as, keys: keys)
|
182
|
+
|
183
|
+
self
|
168
184
|
end
|
169
185
|
|
170
|
-
#
|
186
|
+
# rubocop:disable Layout/LineLength
|
187
|
+
|
188
|
+
# @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
|
189
|
+
# Registers a provider and its lifecycle hooks
|
171
190
|
#
|
172
|
-
#
|
173
|
-
# `
|
174
|
-
#
|
175
|
-
# process.
|
191
|
+
# By convention, you should place a file for each provider in one of the
|
192
|
+
# configured `provider_dirs`, and they will be loaded on demand when components
|
193
|
+
# are loaded in isolation, or during container finalization.
|
176
194
|
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
# config.auto_register = %w(lib/apis lib/core)
|
195
|
+
# @example
|
196
|
+
# # system/container.rb
|
197
|
+
# class MyApp < Dry::System::Container
|
198
|
+
# configure do |config|
|
199
|
+
# config.root = Pathname("/path/to/app")
|
200
|
+
# end
|
184
201
|
# end
|
185
202
|
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
203
|
+
# # system/providers/db.rb
|
204
|
+
# #
|
205
|
+
# # Simple provider registration
|
206
|
+
# MyApp.register_provider(:db) do
|
207
|
+
# start do
|
208
|
+
# require "db"
|
209
|
+
# register("db", DB.new)
|
210
|
+
# end
|
211
|
+
# end
|
191
212
|
#
|
192
|
-
#
|
193
|
-
#
|
213
|
+
# # system/providers/db.rb
|
214
|
+
# #
|
215
|
+
# # Provider registration with lifecycle triggers
|
216
|
+
# MyApp.register_provider(:db) do |container|
|
217
|
+
# init do
|
218
|
+
# require "db"
|
219
|
+
# DB.configure(ENV["DB_URL"])
|
220
|
+
# container.register("db", DB.new)
|
221
|
+
# end
|
194
222
|
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
# MyApp.boot(:db) do |container|
|
199
|
-
# init do
|
200
|
-
# require 'db'
|
201
|
-
# DB.configure(ENV['DB_URL'])
|
202
|
-
# container.register(:db, DB.new)
|
203
|
-
# end
|
223
|
+
# start do
|
224
|
+
# container["db"].establish_connection
|
225
|
+
# end
|
204
226
|
#
|
205
|
-
#
|
206
|
-
#
|
227
|
+
# stop do
|
228
|
+
# container["db"].close_connection
|
229
|
+
# end
|
207
230
|
# end
|
208
231
|
#
|
209
|
-
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
232
|
+
# # system/providers/db.rb
|
233
|
+
# #
|
234
|
+
# # Provider registration which uses another provider
|
235
|
+
# MyApp.register_provider(:db) do |container|
|
236
|
+
# start do
|
237
|
+
# use :logger
|
213
238
|
#
|
214
|
-
#
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
# use :logger
|
219
|
-
#
|
220
|
-
# start do
|
221
|
-
# require 'db'
|
222
|
-
# DB.configure(ENV['DB_URL'], logger: logger)
|
223
|
-
# container.register(:db, DB.new)
|
239
|
+
# require "db"
|
240
|
+
# DB.configure(ENV['DB_URL'], logger: logger)
|
241
|
+
# container.register("db", DB.new)
|
242
|
+
# end
|
224
243
|
# end
|
225
|
-
# end
|
226
244
|
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
245
|
+
# # system/providers/db.rb
|
246
|
+
# #
|
247
|
+
# # Provider registration under a namespace. This will register the
|
248
|
+
# # db object with the "persistence.db" key
|
249
|
+
# MyApp.register_provider(:persistence, namespace: "db") do
|
250
|
+
# start do
|
251
|
+
# require "db"
|
252
|
+
# DB.configure(ENV["DB_URL"])
|
253
|
+
# register("db", DB.new)
|
254
|
+
# end
|
255
|
+
# end
|
236
256
|
#
|
237
|
-
#
|
257
|
+
# @param name [Symbol] a unique name for the provider
|
258
|
+
# @param namespace [String, nil] the key namespace to use for any registrations
|
259
|
+
# made during the provider's lifecycle
|
260
|
+
# @param from [Symbol, nil] the group for the external provider source (with the
|
261
|
+
# provider source name inferred from `name` or passsed explicitly as
|
262
|
+
# `source:`)
|
263
|
+
# @param source [Symbol, nil] the name of the external provider source to use
|
264
|
+
# (if different from the value provided as `name`)
|
265
|
+
# @param if [Boolean] a boolean to determine whether to register the provider
|
238
266
|
#
|
239
|
-
#
|
267
|
+
# @see Provider
|
268
|
+
# @see Provider::Source
|
240
269
|
#
|
241
|
-
#
|
270
|
+
# @return [self]
|
242
271
|
#
|
243
|
-
#
|
244
|
-
def
|
245
|
-
|
246
|
-
raise DuplicatedComponentKeyError, <<-STR
|
247
|
-
Bootable component #{name.inspect} was already registered
|
248
|
-
STR
|
249
|
-
end
|
250
|
-
|
251
|
-
component =
|
252
|
-
if opts[:from]
|
253
|
-
boot_external(name, **opts, &block)
|
254
|
-
else
|
255
|
-
boot_local(name, **opts, &block)
|
256
|
-
end
|
257
|
-
|
258
|
-
booter.register_component component
|
259
|
-
|
260
|
-
components[name] = component
|
261
|
-
end
|
262
|
-
deprecate :finalize, :boot
|
263
|
-
|
264
|
-
# @api private
|
265
|
-
def boot_external(identifier, from:, key: nil, namespace: nil, &block)
|
266
|
-
System.providers[from].component(
|
267
|
-
identifier, key: key, namespace: namespace, finalize: block, container: self
|
268
|
-
)
|
272
|
+
# @api public
|
273
|
+
def register_provider(...)
|
274
|
+
providers.register_provider(...)
|
269
275
|
end
|
270
276
|
|
271
|
-
#
|
272
|
-
def boot_local(identifier, namespace: nil, &block)
|
273
|
-
Components::Bootable.new(
|
274
|
-
identifier, container: self, namespace: namespace, &block
|
275
|
-
)
|
276
|
-
end
|
277
|
+
# rubocop:enable Layout/LineLength
|
277
278
|
|
278
279
|
# Return if a container was finalized
|
279
280
|
#
|
@@ -316,51 +317,55 @@ module Dry
|
|
316
317
|
def finalize!(freeze: true, &block)
|
317
318
|
return self if finalized?
|
318
319
|
|
320
|
+
configured!
|
321
|
+
|
322
|
+
hooks[:before_finalize].each { |hook| instance_eval(&hook) }
|
319
323
|
yield(self) if block
|
320
324
|
|
321
|
-
|
322
|
-
booter.finalize!
|
323
|
-
manual_registrar.finalize!
|
325
|
+
providers.finalize!
|
324
326
|
auto_registrar.finalize!
|
327
|
+
manifest_registrar.finalize!
|
328
|
+
importer.finalize!
|
325
329
|
|
326
330
|
@__finalized__ = true
|
327
331
|
|
328
332
|
self.freeze if freeze
|
333
|
+
hooks[:after_finalize].each { |hook| instance_eval(&hook) }
|
329
334
|
self
|
330
335
|
end
|
331
336
|
|
332
|
-
#
|
337
|
+
# Starts a provider
|
333
338
|
#
|
334
|
-
# As a result, `
|
339
|
+
# As a result, the provider's `prepare` and `start` lifecycle triggers are called
|
335
340
|
#
|
336
341
|
# @example
|
337
342
|
# MyApp.start(:persistence)
|
338
343
|
#
|
339
|
-
# @param name [Symbol] the name of a registered
|
344
|
+
# @param name [Symbol] the name of a registered provider to start
|
340
345
|
#
|
341
346
|
# @return [self]
|
342
347
|
#
|
343
348
|
# @api public
|
344
349
|
def start(name)
|
345
|
-
|
350
|
+
providers.start(name)
|
346
351
|
self
|
347
352
|
end
|
348
353
|
|
349
|
-
#
|
354
|
+
# Prepares a provider using its `prepare` lifecycle trigger
|
350
355
|
#
|
351
|
-
#
|
352
|
-
# needed but its started environment
|
356
|
+
# Preparing (as opposed to starting) a provider is useful in places where some
|
357
|
+
# aspects of a heavier dependency are needed, but its fully started environment
|
353
358
|
#
|
354
359
|
# @example
|
355
|
-
# MyApp.
|
360
|
+
# MyApp.prepare(:persistence)
|
356
361
|
#
|
357
|
-
# @param [Symbol]
|
362
|
+
# @param name [Symbol] The name of the registered provider to prepare
|
358
363
|
#
|
359
364
|
# @return [self]
|
360
365
|
#
|
361
366
|
# @api public
|
362
|
-
def
|
363
|
-
|
367
|
+
def prepare(name)
|
368
|
+
providers.prepare(name)
|
364
369
|
self
|
365
370
|
end
|
366
371
|
|
@@ -369,22 +374,23 @@ module Dry
|
|
369
374
|
# @example
|
370
375
|
# MyApp.stop(:persistence)
|
371
376
|
#
|
372
|
-
# @param [Symbol]
|
377
|
+
# @param name [Symbol] The name of a registered bootable component
|
373
378
|
#
|
374
379
|
# @return [self]
|
375
380
|
#
|
376
381
|
# @api public
|
377
382
|
def stop(name)
|
378
|
-
|
383
|
+
providers.stop(name)
|
379
384
|
self
|
380
385
|
end
|
381
386
|
|
387
|
+
# @api public
|
382
388
|
def shutdown!
|
383
|
-
|
389
|
+
providers.shutdown
|
384
390
|
self
|
385
391
|
end
|
386
392
|
|
387
|
-
#
|
393
|
+
# Adds the directories (relative to the container's root) to the Ruby load path
|
388
394
|
#
|
389
395
|
# @example
|
390
396
|
# class MyApp < Dry::System::Container
|
@@ -392,68 +398,24 @@ module Dry
|
|
392
398
|
# # ...
|
393
399
|
# end
|
394
400
|
#
|
395
|
-
#
|
401
|
+
# add_to_load_path!('lib')
|
396
402
|
# end
|
397
403
|
#
|
398
|
-
# @param [Array<String>]
|
404
|
+
# @param dirs [Array<String>]
|
399
405
|
#
|
400
406
|
# @return [self]
|
401
407
|
#
|
402
408
|
# @api public
|
403
|
-
def
|
404
|
-
dirs.map(&root.method(:join)).each do |path|
|
405
|
-
|
406
|
-
|
407
|
-
load_paths << path
|
408
|
-
$LOAD_PATH.unshift(path.to_s)
|
409
|
+
def add_to_load_path!(*dirs)
|
410
|
+
dirs.reverse.map(&root.method(:join)).each do |path|
|
411
|
+
$LOAD_PATH.prepend(path.to_s) unless $LOAD_PATH.include?(path.to_s)
|
409
412
|
end
|
410
413
|
self
|
411
414
|
end
|
412
415
|
|
413
416
|
# @api public
|
414
417
|
def load_registrations!(name)
|
415
|
-
|
416
|
-
self
|
417
|
-
end
|
418
|
-
|
419
|
-
# Auto-registers components from the provided directory
|
420
|
-
#
|
421
|
-
# Typically you want to configure auto_register directories, and it will
|
422
|
-
# work automatically. Use this method in cases where you want to have an
|
423
|
-
# explicit way where some components are auto-registered, or if you want
|
424
|
-
# to exclude some components from being auto-registered
|
425
|
-
#
|
426
|
-
# @example
|
427
|
-
# class MyApp < Dry::System::Container
|
428
|
-
# configure do |config|
|
429
|
-
# # ...
|
430
|
-
# end
|
431
|
-
#
|
432
|
-
# # with a dir
|
433
|
-
# auto_register!('lib/core')
|
434
|
-
#
|
435
|
-
# # with a dir and a custom registration block
|
436
|
-
# auto_register!('lib/core') do |config|
|
437
|
-
# config.instance do |component|
|
438
|
-
# # custom way of initializing a component
|
439
|
-
# end
|
440
|
-
#
|
441
|
-
# config.exclude do |component|
|
442
|
-
# # return true to exclude component from auto-registration
|
443
|
-
# end
|
444
|
-
# end
|
445
|
-
# end
|
446
|
-
#
|
447
|
-
# @param [String] dir The dir name relative to the root dir
|
448
|
-
#
|
449
|
-
# @yield AutoRegistrar::Configuration
|
450
|
-
# @see AutoRegistrar::Configuration
|
451
|
-
#
|
452
|
-
# @return [self]
|
453
|
-
#
|
454
|
-
# @api public
|
455
|
-
def auto_register!(dir, &block)
|
456
|
-
auto_registrar.(dir, &block)
|
418
|
+
manifest_registrar.(name)
|
457
419
|
self
|
458
420
|
end
|
459
421
|
|
@@ -482,8 +444,8 @@ module Dry
|
|
482
444
|
# @param options [Hash] injector options
|
483
445
|
#
|
484
446
|
# @api public
|
485
|
-
def injector(options
|
486
|
-
Dry::AutoInject(self, options)
|
447
|
+
def injector(**options)
|
448
|
+
Dry::AutoInject(self, **options)
|
487
449
|
end
|
488
450
|
|
489
451
|
# Requires one or more files relative to the container's root
|
@@ -525,8 +487,8 @@ module Dry
|
|
525
487
|
end
|
526
488
|
|
527
489
|
# @api public
|
528
|
-
def resolve(key
|
529
|
-
load_component(key
|
490
|
+
def resolve(key)
|
491
|
+
load_component(key) unless finalized?
|
530
492
|
|
531
493
|
super
|
532
494
|
end
|
@@ -535,7 +497,7 @@ module Dry
|
|
535
497
|
#
|
536
498
|
# @!method registered?(key)
|
537
499
|
# Whether a +key+ is registered (doesn't trigger loading)
|
538
|
-
# @param [String,Symbol] key
|
500
|
+
# @param key [String,Symbol] The key
|
539
501
|
# @return [Boolean]
|
540
502
|
# @api public
|
541
503
|
#
|
@@ -543,7 +505,7 @@ module Dry
|
|
543
505
|
# Check if identifier is registered.
|
544
506
|
# If not, try to load the component
|
545
507
|
#
|
546
|
-
# @param [String,Symbol]
|
508
|
+
# @param key [String,Symbol] Identifier
|
547
509
|
# @return [Boolean]
|
548
510
|
#
|
549
511
|
# @api public
|
@@ -557,26 +519,13 @@ module Dry
|
|
557
519
|
end
|
558
520
|
|
559
521
|
# @api private
|
560
|
-
def
|
561
|
-
|
522
|
+
def component_dirs
|
523
|
+
config.component_dirs.to_a.map { |dir| ComponentDir.new(config: dir, container: self) }
|
562
524
|
end
|
563
525
|
|
564
526
|
# @api private
|
565
|
-
def
|
566
|
-
@
|
567
|
-
end
|
568
|
-
|
569
|
-
# @api private
|
570
|
-
def boot_paths
|
571
|
-
config.bootable_dirs.map { |dir|
|
572
|
-
dir = Pathname(dir)
|
573
|
-
|
574
|
-
if dir.relative?
|
575
|
-
root.join(dir)
|
576
|
-
else
|
577
|
-
dir
|
578
|
-
end
|
579
|
-
}
|
527
|
+
def providers
|
528
|
+
@providers ||= config.provider_registrar.new(self)
|
580
529
|
end
|
581
530
|
|
582
531
|
# @api private
|
@@ -585,8 +534,8 @@ module Dry
|
|
585
534
|
end
|
586
535
|
|
587
536
|
# @api private
|
588
|
-
def
|
589
|
-
@
|
537
|
+
def manifest_registrar
|
538
|
+
@manifest_registrar ||= config.manifest_registrar.new(self)
|
590
539
|
end
|
591
540
|
|
592
541
|
# @api private
|
@@ -594,74 +543,45 @@ module Dry
|
|
594
543
|
@importer ||= config.importer.new(self)
|
595
544
|
end
|
596
545
|
|
597
|
-
#
|
598
|
-
def component(identifier, **options)
|
599
|
-
if (component = booter.components.detect { |c| c.identifier == identifier })
|
600
|
-
component
|
601
|
-
else
|
602
|
-
Component.new(
|
603
|
-
identifier,
|
604
|
-
loader: config.loader,
|
605
|
-
namespace: config.default_namespace,
|
606
|
-
separator: config.namespace_separator,
|
607
|
-
inflector: config.inflector,
|
608
|
-
**options
|
609
|
-
)
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
# @api private
|
614
|
-
def require_component(component)
|
615
|
-
return if registered?(component.identifier)
|
616
|
-
|
617
|
-
raise FileNotFoundError, component unless component.file_exists?(load_paths)
|
618
|
-
|
619
|
-
require_path(component.path)
|
620
|
-
|
621
|
-
yield
|
622
|
-
end
|
623
|
-
|
624
|
-
# Allows subclasses to use a different strategy for required files.
|
546
|
+
# Registers a callback hook to run before container lifecycle events.
|
625
547
|
#
|
626
|
-
#
|
627
|
-
#
|
628
|
-
# for non-finalized containers.
|
548
|
+
# Currently, the only supported event is `:finalized`. This hook is called when
|
549
|
+
# you run `{finalize!}`.
|
629
550
|
#
|
630
|
-
#
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
#
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
booter.start(component)
|
642
|
-
else
|
643
|
-
root_key = component.root_key
|
644
|
-
|
645
|
-
if (root_bootable = component(root_key)).bootable?
|
646
|
-
booter.start(root_bootable)
|
647
|
-
elsif importer.key?(root_key)
|
648
|
-
load_imported_component(component.namespaced(root_key))
|
649
|
-
end
|
650
|
-
|
651
|
-
load_local_component(component, &block) unless registered?(key)
|
652
|
-
end
|
653
|
-
end
|
654
|
-
|
551
|
+
# When the given block is called, `self` is the container class, and no block
|
552
|
+
# arguments are given.
|
553
|
+
#
|
554
|
+
# @param event [Symbol] the event name
|
555
|
+
# @param block [Proc] the callback hook to run
|
556
|
+
#
|
557
|
+
# @return [self]
|
558
|
+
#
|
559
|
+
# @api public
|
560
|
+
def before(event, &block)
|
561
|
+
hooks[:"before_#{event}"] << block
|
655
562
|
self
|
656
563
|
end
|
657
564
|
|
658
|
-
#
|
565
|
+
# Registers a callback hook to run after container lifecycle events.
|
566
|
+
#
|
567
|
+
# The supported events are:
|
568
|
+
#
|
569
|
+
# - `:configured`, called when you run {configure} or {configured!}, or when
|
570
|
+
# running {finalize!} and neither of the prior two methods have been called.
|
571
|
+
# - `:finalized`, called when you run {finalize!}.
|
572
|
+
#
|
573
|
+
# When the given block is called, `self` is the container class, and no block
|
574
|
+
# arguments are given.
|
575
|
+
#
|
576
|
+
# @param event [Symbol] the event name
|
577
|
+
# @param block [Proc] the callback hook to run
|
578
|
+
#
|
579
|
+
# @return [self]
|
580
|
+
#
|
581
|
+
# @api public
|
659
582
|
def after(event, &block)
|
660
583
|
hooks[:"after_#{event}"] << block
|
661
|
-
|
662
|
-
|
663
|
-
def before(event, &block)
|
664
|
-
hooks[:"before_#{event}"] << block
|
584
|
+
self
|
665
585
|
end
|
666
586
|
|
667
587
|
# @api private
|
@@ -671,45 +591,85 @@ module Dry
|
|
671
591
|
|
672
592
|
# @api private
|
673
593
|
def inherited(klass)
|
674
|
-
new_hooks = Container.hooks.dup
|
675
|
-
|
676
594
|
hooks.each do |event, blocks|
|
677
|
-
|
678
|
-
new_hooks[event].concat(klass.hooks[event])
|
595
|
+
klass.hooks[event].concat blocks.dup
|
679
596
|
end
|
680
597
|
|
681
|
-
klass.instance_variable_set(:@
|
598
|
+
klass.instance_variable_set(:@__configured__, false)
|
682
599
|
klass.instance_variable_set(:@__finalized__, false)
|
600
|
+
|
683
601
|
super
|
684
602
|
end
|
685
603
|
|
686
|
-
|
604
|
+
protected
|
687
605
|
|
606
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
688
607
|
# @api private
|
689
|
-
def
|
690
|
-
|
691
|
-
booter.boot_dependency(component) unless finalized?
|
608
|
+
def load_component(key)
|
609
|
+
return self if registered?(key)
|
692
610
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
611
|
+
if (provider = providers.find_and_load_provider(key))
|
612
|
+
provider.start
|
613
|
+
return self
|
614
|
+
end
|
615
|
+
|
616
|
+
component = find_component(key)
|
617
|
+
|
618
|
+
providers.start_provider_dependency(component)
|
619
|
+
return self if registered?(key)
|
620
|
+
|
621
|
+
if component.loadable?
|
622
|
+
load_local_component(component)
|
623
|
+
elsif manifest_registrar.file_exists?(component)
|
624
|
+
manifest_registrar.(component)
|
625
|
+
elsif importer.namespace?(component.identifier.root_key)
|
626
|
+
load_imported_component(component.identifier, namespace: component.identifier.root_key)
|
627
|
+
elsif importer.namespace?(nil)
|
628
|
+
load_imported_component(component.identifier, namespace: nil)
|
704
629
|
end
|
630
|
+
|
631
|
+
self
|
705
632
|
end
|
633
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
706
634
|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
635
|
+
private
|
636
|
+
|
637
|
+
def load_local_component(component)
|
638
|
+
if component.auto_register?
|
639
|
+
register(component.identifier, memoize: component.memoize?) { component.instance }
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
def load_imported_component(identifier, namespace:)
|
644
|
+
return unless importer.namespace?(namespace)
|
645
|
+
|
646
|
+
import_key = identifier.namespaced(from: namespace, to: nil).key
|
647
|
+
|
648
|
+
importer.import(namespace, keys: [import_key])
|
712
649
|
end
|
650
|
+
|
651
|
+
def find_component(key)
|
652
|
+
# Find the first matching component from within the configured component dirs.
|
653
|
+
# If no matching component is found, return a null component; this fallback is
|
654
|
+
# important because the component may still be loadable via the manifest
|
655
|
+
# registrar or an imported container.
|
656
|
+
component_dirs.detect { |dir|
|
657
|
+
if (component = dir.component_for_key(key))
|
658
|
+
break component
|
659
|
+
end
|
660
|
+
} || IndirectComponent.new(Identifier.new(key))
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
# Default hooks
|
665
|
+
after :configure do
|
666
|
+
# Add appropriately configured component dirs to the load path
|
667
|
+
#
|
668
|
+
# Do this in a single pass to preserve ordering (i.e. earliest dirs win)
|
669
|
+
paths = config.component_dirs.to_a.each_with_object([]) { |dir, arr|
|
670
|
+
arr << dir.path if dir.add_to_load_path
|
671
|
+
}
|
672
|
+
add_to_load_path!(*paths)
|
713
673
|
end
|
714
674
|
end
|
715
675
|
end
|