piko-quick-lib 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.
- checksums.yaml +7 -0
- data/dry-system-1.2.5/CHANGELOG.md +1311 -0
- data/dry-system-1.2.5/LICENSE +21 -0
- data/dry-system-1.2.5/README.md +17 -0
- data/dry-system-1.2.5/dry-system.gemspec +42 -0
- data/dry-system-1.2.5/lib/dry/system/auto_registrar.rb +45 -0
- data/dry-system-1.2.5/lib/dry/system/component.rb +190 -0
- data/dry-system-1.2.5/lib/dry/system/component_dir.rb +171 -0
- data/dry-system-1.2.5/lib/dry/system/config/component_dir.rb +228 -0
- data/dry-system-1.2.5/lib/dry/system/config/component_dirs.rb +285 -0
- data/dry-system-1.2.5/lib/dry/system/config/namespace.rb +75 -0
- data/dry-system-1.2.5/lib/dry/system/config/namespaces.rb +192 -0
- data/dry-system-1.2.5/lib/dry/system/constants.rb +13 -0
- data/dry-system-1.2.5/lib/dry/system/container.rb +685 -0
- data/dry-system-1.2.5/lib/dry/system/errors.rb +132 -0
- data/dry-system-1.2.5/lib/dry/system/identifier.rb +176 -0
- data/dry-system-1.2.5/lib/dry/system/importer.rb +144 -0
- data/dry-system-1.2.5/lib/dry/system/indirect_component.rb +63 -0
- data/dry-system-1.2.5/lib/dry/system/loader/autoloading.rb +24 -0
- data/dry-system-1.2.5/lib/dry/system/loader.rb +84 -0
- data/dry-system-1.2.5/lib/dry/system/magic_comments_parser.rb +31 -0
- data/dry-system-1.2.5/lib/dry/system/manifest_registrar.rb +57 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/bootsnap.rb +47 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph/strategies.rb +66 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph.rb +53 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/env.rb +30 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/logging.rb +73 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/monitoring/proxy.rb +51 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/monitoring.rb +45 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/notifications.rb +27 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/plugin.rb +61 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
- data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk.rb +109 -0
- data/dry-system-1.2.5/lib/dry/system/plugins.rb +70 -0
- data/dry-system-1.2.5/lib/dry/system/provider/source.rb +281 -0
- data/dry-system-1.2.5/lib/dry/system/provider/source_dsl.rb +55 -0
- data/dry-system-1.2.5/lib/dry/system/provider.rb +291 -0
- data/dry-system-1.2.5/lib/dry/system/provider_registrar.rb +289 -0
- data/dry-system-1.2.5/lib/dry/system/provider_source_registry.rb +67 -0
- data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/config.rb +73 -0
- data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/loader.rb +44 -0
- data/dry-system-1.2.5/lib/dry/system/provider_sources/settings.rb +40 -0
- data/dry-system-1.2.5/lib/dry/system/provider_sources.rb +6 -0
- data/dry-system-1.2.5/lib/dry/system/stubs.rb +39 -0
- data/dry-system-1.2.5/lib/dry/system/version.rb +7 -0
- data/dry-system-1.2.5/lib/dry/system.rb +62 -0
- data/dry-system-1.2.5/lib/dry-system.rb +3 -0
- data/piko-quick-lib.gemspec +11 -0
- metadata +87 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "dry/configurable"
|
|
6
|
+
require "dry/auto_inject"
|
|
7
|
+
require "dry/inflector"
|
|
8
|
+
|
|
9
|
+
module Dry
|
|
10
|
+
module System
|
|
11
|
+
# Abstract container class to inherit from
|
|
12
|
+
#
|
|
13
|
+
# Container class is treated as a global registry with all system components.
|
|
14
|
+
# Container can also import dependencies from other containers, which is
|
|
15
|
+
# useful in complex systems that are split into sub-systems.
|
|
16
|
+
#
|
|
17
|
+
# Container can be finalized, which triggers loading of all the defined
|
|
18
|
+
# components within a system, after finalization it becomes frozen. This
|
|
19
|
+
# typically happens in cases like booting a web application.
|
|
20
|
+
#
|
|
21
|
+
# Before finalization, Container can lazy-load components on demand. A
|
|
22
|
+
# component can be a simple class defined in a single file, or a complex
|
|
23
|
+
# component which has init/start/stop lifecycle, and it's defined in a boot
|
|
24
|
+
# file. Components which specify their dependencies using Import module can
|
|
25
|
+
# be safely required in complete isolation, and Container will resolve and
|
|
26
|
+
# load these dependencies automatically.
|
|
27
|
+
#
|
|
28
|
+
# Furthermore, Container supports auto-registering components based on
|
|
29
|
+
# dir/file naming conventions. This reduces a lot of boilerplate code as all
|
|
30
|
+
# you have to do is to put your classes under configured directories and
|
|
31
|
+
# their instances will be automatically registered within a container.
|
|
32
|
+
#
|
|
33
|
+
# Every container needs to be configured with following settings:
|
|
34
|
+
#
|
|
35
|
+
# * `:name` - a unique container name
|
|
36
|
+
# * `:root` - a system root directory (defaults to `pwd`)
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# class MyApp < Dry::System::Container
|
|
40
|
+
# configure do |config|
|
|
41
|
+
# config.name = :my_app
|
|
42
|
+
#
|
|
43
|
+
# # this will auto-register classes from 'lib/components'. ie if you add
|
|
44
|
+
# # `lib/components/repo.rb` which defines `Repo` class, then it's
|
|
45
|
+
# # instance will be automatically available as `MyApp['repo']`
|
|
46
|
+
# config.auto_register = %w(lib/components)
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# # this will configure $LOAD_PATH to include your `lib` dir
|
|
50
|
+
# add_dirs_to_load_paths!('lib')
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
class Container
|
|
55
|
+
extend Dry::Core::Container::Mixin
|
|
56
|
+
extend Dry::System::Plugins
|
|
57
|
+
|
|
58
|
+
setting :name
|
|
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
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
# @!method config
|
|
75
|
+
# Returns the configuration for the container
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# container.config.root = "/path/to/app"
|
|
79
|
+
# container.config.root # => #<Pathname:/path/to/app>
|
|
80
|
+
#
|
|
81
|
+
# @return [Dry::Configurable::Config]
|
|
82
|
+
#
|
|
83
|
+
# @api public
|
|
84
|
+
|
|
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`
|
|
90
|
+
#
|
|
91
|
+
# @example
|
|
92
|
+
# class MyApp < Dry::System::Container
|
|
93
|
+
# configure do |config|
|
|
94
|
+
# config.root = Pathname("/path/to/app")
|
|
95
|
+
# config.name = :my_app
|
|
96
|
+
# end
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
# @param finalize_config [Boolean]
|
|
100
|
+
#
|
|
101
|
+
# @return [self]
|
|
102
|
+
#
|
|
103
|
+
# @see after
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
def configure(finalize_config: true, &)
|
|
107
|
+
super(&)
|
|
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
|
+
hooks[:after_configure].each { |hook| instance_eval(&hook) }
|
|
130
|
+
|
|
131
|
+
_configurable_finalize! if finalize_config
|
|
132
|
+
|
|
133
|
+
@__configured__ = true
|
|
134
|
+
|
|
135
|
+
self
|
|
136
|
+
end
|
|
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
|
+
|
|
147
|
+
# Registers another container for import
|
|
148
|
+
#
|
|
149
|
+
# @example
|
|
150
|
+
# # system/container.rb
|
|
151
|
+
# require "dry/system"
|
|
152
|
+
# require "logger"
|
|
153
|
+
#
|
|
154
|
+
# class Core < Dry::System::Container
|
|
155
|
+
# register("logger", Logger.new($stdout))
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
# # apps/my_app/system/container.rb
|
|
159
|
+
# require 'system/container'
|
|
160
|
+
#
|
|
161
|
+
# class MyApp < Dry::System::Container
|
|
162
|
+
# import(from: Core, as: :core)
|
|
163
|
+
# end
|
|
164
|
+
#
|
|
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
|
|
176
|
+
#
|
|
177
|
+
# @api public
|
|
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
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
|
|
187
|
+
# Registers a provider and its lifecycle hooks
|
|
188
|
+
#
|
|
189
|
+
# By convention, you should place a file for each provider in one of the
|
|
190
|
+
# configured `provider_dirs`, and they will be loaded on demand when components
|
|
191
|
+
# are loaded in isolation, or during container finalization.
|
|
192
|
+
#
|
|
193
|
+
# @example
|
|
194
|
+
# # system/container.rb
|
|
195
|
+
# class MyApp < Dry::System::Container
|
|
196
|
+
# configure do |config|
|
|
197
|
+
# config.root = Pathname("/path/to/app")
|
|
198
|
+
# end
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
# # system/providers/db.rb
|
|
202
|
+
# #
|
|
203
|
+
# # Simple provider registration
|
|
204
|
+
# MyApp.register_provider(:db) do
|
|
205
|
+
# start do
|
|
206
|
+
# require "db"
|
|
207
|
+
# register("db", DB.new)
|
|
208
|
+
# end
|
|
209
|
+
# end
|
|
210
|
+
#
|
|
211
|
+
# # system/providers/db.rb
|
|
212
|
+
# #
|
|
213
|
+
# # Provider registration with lifecycle triggers
|
|
214
|
+
# MyApp.register_provider(:db) do |container|
|
|
215
|
+
# init do
|
|
216
|
+
# require "db"
|
|
217
|
+
# DB.configure(ENV["DB_URL"])
|
|
218
|
+
# container.register("db", DB.new)
|
|
219
|
+
# end
|
|
220
|
+
#
|
|
221
|
+
# start do
|
|
222
|
+
# container["db"].establish_connection
|
|
223
|
+
# end
|
|
224
|
+
#
|
|
225
|
+
# stop do
|
|
226
|
+
# container["db"].close_connection
|
|
227
|
+
# end
|
|
228
|
+
# end
|
|
229
|
+
#
|
|
230
|
+
# # system/providers/db.rb
|
|
231
|
+
# #
|
|
232
|
+
# # Provider registration which uses another provider
|
|
233
|
+
# MyApp.register_provider(:db) do |container|
|
|
234
|
+
# start do
|
|
235
|
+
# use :logger
|
|
236
|
+
#
|
|
237
|
+
# require "db"
|
|
238
|
+
# DB.configure(ENV['DB_URL'], logger: logger)
|
|
239
|
+
# container.register("db", DB.new)
|
|
240
|
+
# end
|
|
241
|
+
# end
|
|
242
|
+
#
|
|
243
|
+
# # system/providers/db.rb
|
|
244
|
+
# #
|
|
245
|
+
# # Provider registration under a namespace. This will register the
|
|
246
|
+
# # db object with the "persistence.db" key
|
|
247
|
+
# MyApp.register_provider(:persistence, namespace: "db") do
|
|
248
|
+
# start do
|
|
249
|
+
# require "db"
|
|
250
|
+
# DB.configure(ENV["DB_URL"])
|
|
251
|
+
# register("db", DB.new)
|
|
252
|
+
# end
|
|
253
|
+
# end
|
|
254
|
+
#
|
|
255
|
+
# @param name [Symbol] a unique name for the provider
|
|
256
|
+
# @param namespace [String, nil] the key namespace to use for any registrations
|
|
257
|
+
# made during the provider's lifecycle
|
|
258
|
+
# @param from [Symbol, nil] the group for the external provider source (with the
|
|
259
|
+
# provider source name inferred from `name` or passsed explicitly as
|
|
260
|
+
# `source:`)
|
|
261
|
+
# @param source [Symbol, nil] the name of the external provider source to use
|
|
262
|
+
# (if different from the value provided as `name`)
|
|
263
|
+
# @param if [Boolean] a boolean to determine whether to register the provider
|
|
264
|
+
#
|
|
265
|
+
# @see Provider
|
|
266
|
+
# @see Provider::Source
|
|
267
|
+
#
|
|
268
|
+
# @return [self]
|
|
269
|
+
#
|
|
270
|
+
# @api public
|
|
271
|
+
def register_provider(...)
|
|
272
|
+
providers.register_provider(...)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Return if a container was finalized
|
|
276
|
+
#
|
|
277
|
+
# @return [TrueClass, FalseClass]
|
|
278
|
+
#
|
|
279
|
+
# @api public
|
|
280
|
+
def finalized?
|
|
281
|
+
@__finalized__.equal?(true)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Finalizes the container
|
|
285
|
+
#
|
|
286
|
+
# This triggers importing components from other containers, booting
|
|
287
|
+
# registered components and auto-registering components. It should be
|
|
288
|
+
# called only in places where you want to finalize your system as a
|
|
289
|
+
# whole, ie when booting a web application
|
|
290
|
+
#
|
|
291
|
+
# @example
|
|
292
|
+
# # system/container.rb
|
|
293
|
+
# class MyApp < Dry::System::Container
|
|
294
|
+
# configure do |config|
|
|
295
|
+
# config.root = Pathname("/path/to/app")
|
|
296
|
+
# config.name = :my_app
|
|
297
|
+
# config.auto_register = %w(lib/apis lib/core)
|
|
298
|
+
# end
|
|
299
|
+
# end
|
|
300
|
+
#
|
|
301
|
+
# # You can put finalization file anywhere you want, ie system/boot.rb
|
|
302
|
+
# MyApp.finalize!
|
|
303
|
+
#
|
|
304
|
+
# # If you need last-moment adjustments just before the finalization
|
|
305
|
+
# # you can pass a block and do it there
|
|
306
|
+
# MyApp.finalize! do |container|
|
|
307
|
+
# # stuff that only needs to happen for finalization
|
|
308
|
+
# end
|
|
309
|
+
#
|
|
310
|
+
# @return [self] frozen container
|
|
311
|
+
#
|
|
312
|
+
# @api public
|
|
313
|
+
def finalize!(freeze: true, &)
|
|
314
|
+
return self if finalized?
|
|
315
|
+
|
|
316
|
+
configured!
|
|
317
|
+
|
|
318
|
+
run_hooks(:finalize) do
|
|
319
|
+
yield(self) if block_given?
|
|
320
|
+
|
|
321
|
+
[providers, auto_registrar, manifest_registrar, importer].each(&:finalize!)
|
|
322
|
+
|
|
323
|
+
@__finalized__ = true
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
self.freeze if freeze
|
|
327
|
+
|
|
328
|
+
self
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Starts a provider
|
|
332
|
+
#
|
|
333
|
+
# As a result, the provider's `prepare` and `start` lifecycle triggers are called
|
|
334
|
+
#
|
|
335
|
+
# @example
|
|
336
|
+
# MyApp.start(:persistence)
|
|
337
|
+
#
|
|
338
|
+
# @param name [Symbol] the name of a registered provider to start
|
|
339
|
+
#
|
|
340
|
+
# @return [self]
|
|
341
|
+
#
|
|
342
|
+
# @api public
|
|
343
|
+
def start(name)
|
|
344
|
+
providers.start(name)
|
|
345
|
+
self
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Prepares a provider using its `prepare` lifecycle trigger
|
|
349
|
+
#
|
|
350
|
+
# Preparing (as opposed to starting) a provider is useful in places where some
|
|
351
|
+
# aspects of a heavier dependency are needed, but its fully started environment
|
|
352
|
+
#
|
|
353
|
+
# @example
|
|
354
|
+
# MyApp.prepare(:persistence)
|
|
355
|
+
#
|
|
356
|
+
# @param name [Symbol] The name of the registered provider to prepare
|
|
357
|
+
#
|
|
358
|
+
# @return [self]
|
|
359
|
+
#
|
|
360
|
+
# @api public
|
|
361
|
+
def prepare(name)
|
|
362
|
+
providers.prepare(name)
|
|
363
|
+
self
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Stop a specific component but calls only `stop` lifecycle trigger
|
|
367
|
+
#
|
|
368
|
+
# @example
|
|
369
|
+
# MyApp.stop(:persistence)
|
|
370
|
+
#
|
|
371
|
+
# @param name [Symbol] The name of a registered bootable component
|
|
372
|
+
#
|
|
373
|
+
# @return [self]
|
|
374
|
+
#
|
|
375
|
+
# @api public
|
|
376
|
+
def stop(name)
|
|
377
|
+
providers.stop(name)
|
|
378
|
+
self
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# @api public
|
|
382
|
+
def shutdown!
|
|
383
|
+
providers.shutdown
|
|
384
|
+
self
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Adds the directories (relative to the container's root) to the Ruby load path
|
|
388
|
+
#
|
|
389
|
+
# @example
|
|
390
|
+
# class MyApp < Dry::System::Container
|
|
391
|
+
# configure do |config|
|
|
392
|
+
# # ...
|
|
393
|
+
# end
|
|
394
|
+
#
|
|
395
|
+
# add_to_load_path!('lib')
|
|
396
|
+
# end
|
|
397
|
+
#
|
|
398
|
+
# @param dirs [Array<String>]
|
|
399
|
+
#
|
|
400
|
+
# @return [self]
|
|
401
|
+
#
|
|
402
|
+
# @api public
|
|
403
|
+
def add_to_load_path!(*dirs)
|
|
404
|
+
dirs.reverse.map(&root.method(:join)).each do |path|
|
|
405
|
+
$LOAD_PATH.prepend(path.to_s) unless $LOAD_PATH.include?(path.to_s)
|
|
406
|
+
end
|
|
407
|
+
self
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# @api public
|
|
411
|
+
def load_registrations!(name)
|
|
412
|
+
manifest_registrar.(name)
|
|
413
|
+
self
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Builds injector for this container
|
|
417
|
+
#
|
|
418
|
+
# An injector is a useful mixin which injects dependencies into
|
|
419
|
+
# automatically defined constructor.
|
|
420
|
+
#
|
|
421
|
+
# @example
|
|
422
|
+
# # Define an injection mixin
|
|
423
|
+
# #
|
|
424
|
+
# # system/import.rb
|
|
425
|
+
# Import = MyApp.injector
|
|
426
|
+
#
|
|
427
|
+
# # Use it in your auto-registered classes
|
|
428
|
+
# #
|
|
429
|
+
# # lib/user_repo.rb
|
|
430
|
+
# require 'import'
|
|
431
|
+
#
|
|
432
|
+
# class UserRepo
|
|
433
|
+
# include Import['persistence.db']
|
|
434
|
+
# end
|
|
435
|
+
#
|
|
436
|
+
# MyApp['user_repo].db # instance under 'persistence.db' key
|
|
437
|
+
#
|
|
438
|
+
# @param options [Hash] injector options
|
|
439
|
+
#
|
|
440
|
+
# @api public
|
|
441
|
+
def injector(**options)
|
|
442
|
+
Dry::AutoInject(self, **options)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Requires one or more files relative to the container's root
|
|
446
|
+
#
|
|
447
|
+
# @example
|
|
448
|
+
# # single file
|
|
449
|
+
# MyApp.require_from_root('lib/core')
|
|
450
|
+
#
|
|
451
|
+
# # glob
|
|
452
|
+
# MyApp.require_from_root('lib/**/*')
|
|
453
|
+
#
|
|
454
|
+
# @param paths [Array<String>] one or more paths, supports globs too
|
|
455
|
+
#
|
|
456
|
+
# @api public
|
|
457
|
+
def require_from_root(*paths)
|
|
458
|
+
paths.flat_map { |path|
|
|
459
|
+
path.to_s.include?("*") ? ::Dir[root.join(path)] : root.join(path)
|
|
460
|
+
}.each { |path|
|
|
461
|
+
Kernel.require path.to_s
|
|
462
|
+
}
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Returns container's root path
|
|
466
|
+
#
|
|
467
|
+
# @example
|
|
468
|
+
# class MyApp < Dry::System::Container
|
|
469
|
+
# configure do |config|
|
|
470
|
+
# config.root = Pathname('/my/app')
|
|
471
|
+
# end
|
|
472
|
+
# end
|
|
473
|
+
#
|
|
474
|
+
# MyApp.root # returns '/my/app' pathname
|
|
475
|
+
#
|
|
476
|
+
# @return [Pathname]
|
|
477
|
+
#
|
|
478
|
+
# @api public
|
|
479
|
+
def root
|
|
480
|
+
config.root
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# @api public
|
|
484
|
+
def register(key, *)
|
|
485
|
+
super
|
|
486
|
+
|
|
487
|
+
hooks[:after_register].each { |hook| instance_exec(key, &hook) }
|
|
488
|
+
|
|
489
|
+
self
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# @api public
|
|
493
|
+
def resolve(key)
|
|
494
|
+
load_component(key) unless finalized?
|
|
495
|
+
|
|
496
|
+
super
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
alias_method :registered?, :key?
|
|
500
|
+
#
|
|
501
|
+
# @!method registered?(key)
|
|
502
|
+
# Whether a +key+ is registered (doesn't trigger loading)
|
|
503
|
+
# @param key [String,Symbol] The key
|
|
504
|
+
# @return [Boolean]
|
|
505
|
+
# @api public
|
|
506
|
+
#
|
|
507
|
+
|
|
508
|
+
# Check if identifier is registered.
|
|
509
|
+
# If not, try to load the component
|
|
510
|
+
#
|
|
511
|
+
# @param key [String,Symbol] Identifier
|
|
512
|
+
# @return [Boolean]
|
|
513
|
+
#
|
|
514
|
+
# @api public
|
|
515
|
+
def key?(key)
|
|
516
|
+
if finalized?
|
|
517
|
+
registered?(key)
|
|
518
|
+
else
|
|
519
|
+
registered?(key) || resolve(key) { return false }
|
|
520
|
+
true
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# @api private
|
|
525
|
+
def component_dirs
|
|
526
|
+
config.component_dirs.to_a.map { |dir| ComponentDir.new(config: dir, container: self) }
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# @api private
|
|
530
|
+
def providers
|
|
531
|
+
@providers ||= config.provider_registrar.new(self)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# @api private
|
|
535
|
+
def auto_registrar
|
|
536
|
+
@auto_registrar ||= config.auto_registrar.new(self)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# @api private
|
|
540
|
+
def manifest_registrar
|
|
541
|
+
@manifest_registrar ||= config.manifest_registrar.new(self)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# @api private
|
|
545
|
+
def importer
|
|
546
|
+
@importer ||= config.importer.new(self)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Registers a callback hook to run before container lifecycle events.
|
|
550
|
+
#
|
|
551
|
+
# Currently, the only supported event is `:finalized`. This hook is called when
|
|
552
|
+
# you run `{finalize!}`.
|
|
553
|
+
#
|
|
554
|
+
# When the given block is called, `self` is the container class, and no block
|
|
555
|
+
# arguments are given.
|
|
556
|
+
#
|
|
557
|
+
# @param event [Symbol] the event name
|
|
558
|
+
# @param block [Proc] the callback hook to run
|
|
559
|
+
#
|
|
560
|
+
# @return [self]
|
|
561
|
+
#
|
|
562
|
+
# @api public
|
|
563
|
+
def before(event, &block)
|
|
564
|
+
hooks[:"before_#{event}"] << block
|
|
565
|
+
self
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Registers a callback hook to run after container lifecycle events.
|
|
569
|
+
#
|
|
570
|
+
# The supported events are:
|
|
571
|
+
#
|
|
572
|
+
# - `:configured`, called when you run {configure} or {configured!}, or when
|
|
573
|
+
# running {finalize!} and neither of the prior two methods have been called.
|
|
574
|
+
# - `:finalized`, called when you run {finalize!}.
|
|
575
|
+
#
|
|
576
|
+
# When the given block is called, `self` is the container class, and no block
|
|
577
|
+
# arguments are given.
|
|
578
|
+
#
|
|
579
|
+
# @param event [Symbol] the event name
|
|
580
|
+
# @param block [Proc] the callback hook to run
|
|
581
|
+
#
|
|
582
|
+
# @return [self]
|
|
583
|
+
#
|
|
584
|
+
# @api public
|
|
585
|
+
def after(event, &block)
|
|
586
|
+
hooks[:"after_#{event}"] << block
|
|
587
|
+
self
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# @api private
|
|
591
|
+
def hooks
|
|
592
|
+
@hooks ||= Hash.new { |h, k| h[k] = [] }
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# @api private
|
|
596
|
+
def inherited(klass)
|
|
597
|
+
hooks.each do |event, blocks|
|
|
598
|
+
klass.hooks[event].concat blocks.dup
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
klass.instance_variable_set(:@__configured__, false)
|
|
602
|
+
klass.instance_variable_set(:@__finalized__, false)
|
|
603
|
+
|
|
604
|
+
super
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
protected
|
|
608
|
+
|
|
609
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
610
|
+
# @api private
|
|
611
|
+
def load_component(key)
|
|
612
|
+
return self if registered?(key)
|
|
613
|
+
|
|
614
|
+
if (provider = providers[key])
|
|
615
|
+
provider.start
|
|
616
|
+
return self
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
component = find_component(key)
|
|
620
|
+
|
|
621
|
+
providers[component.root_key]&.start
|
|
622
|
+
return self if registered?(key)
|
|
623
|
+
|
|
624
|
+
if component.loadable?
|
|
625
|
+
load_local_component(component)
|
|
626
|
+
elsif manifest_registrar.file_exists?(component)
|
|
627
|
+
manifest_registrar.(component)
|
|
628
|
+
elsif importer.namespace?(component.root_key)
|
|
629
|
+
load_imported_component(component.identifier, namespace: component.root_key)
|
|
630
|
+
elsif importer.namespace?(nil)
|
|
631
|
+
load_imported_component(component.identifier, namespace: nil)
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
self
|
|
635
|
+
end
|
|
636
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
637
|
+
|
|
638
|
+
private
|
|
639
|
+
|
|
640
|
+
def load_local_component(component)
|
|
641
|
+
if component.auto_register?
|
|
642
|
+
register(component.identifier, memoize: component.memoize?) { component.instance }
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def load_imported_component(identifier, namespace:)
|
|
647
|
+
return unless importer.namespace?(namespace)
|
|
648
|
+
|
|
649
|
+
import_key = identifier.namespaced(from: namespace, to: nil).key
|
|
650
|
+
|
|
651
|
+
importer.import(namespace, keys: [import_key])
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def find_component(key)
|
|
655
|
+
# Find the first matching component from within the configured component dirs.
|
|
656
|
+
# If no matching component is found, return a null component; this fallback is
|
|
657
|
+
# important because the component may still be loadable via the manifest
|
|
658
|
+
# registrar or an imported container.
|
|
659
|
+
component_dirs.detect { |dir|
|
|
660
|
+
if (component = dir.component_for_key(key))
|
|
661
|
+
break component
|
|
662
|
+
end
|
|
663
|
+
} || IndirectComponent.new(Identifier.new(key))
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def run_hooks(event)
|
|
667
|
+
hooks[:"before_#{event}"].each { instance_eval(&_1) }
|
|
668
|
+
yield
|
|
669
|
+
hooks[:"after_#{event}"].each { instance_eval(&_1) }
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Default hooks
|
|
674
|
+
after :configure do
|
|
675
|
+
# Add appropriately configured component dirs to the load path
|
|
676
|
+
#
|
|
677
|
+
# Do this in a single pass to preserve ordering (i.e. earliest dirs win)
|
|
678
|
+
paths = config.component_dirs.to_a.each_with_object([]) { |dir, arr|
|
|
679
|
+
arr << dir.path if dir.add_to_load_path
|
|
680
|
+
}
|
|
681
|
+
add_to_load_path!(*paths)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
end
|