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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/dry-system-1.2.5/CHANGELOG.md +1311 -0
  3. data/dry-system-1.2.5/LICENSE +21 -0
  4. data/dry-system-1.2.5/README.md +17 -0
  5. data/dry-system-1.2.5/dry-system.gemspec +42 -0
  6. data/dry-system-1.2.5/lib/dry/system/auto_registrar.rb +45 -0
  7. data/dry-system-1.2.5/lib/dry/system/component.rb +190 -0
  8. data/dry-system-1.2.5/lib/dry/system/component_dir.rb +171 -0
  9. data/dry-system-1.2.5/lib/dry/system/config/component_dir.rb +228 -0
  10. data/dry-system-1.2.5/lib/dry/system/config/component_dirs.rb +285 -0
  11. data/dry-system-1.2.5/lib/dry/system/config/namespace.rb +75 -0
  12. data/dry-system-1.2.5/lib/dry/system/config/namespaces.rb +192 -0
  13. data/dry-system-1.2.5/lib/dry/system/constants.rb +13 -0
  14. data/dry-system-1.2.5/lib/dry/system/container.rb +685 -0
  15. data/dry-system-1.2.5/lib/dry/system/errors.rb +132 -0
  16. data/dry-system-1.2.5/lib/dry/system/identifier.rb +176 -0
  17. data/dry-system-1.2.5/lib/dry/system/importer.rb +144 -0
  18. data/dry-system-1.2.5/lib/dry/system/indirect_component.rb +63 -0
  19. data/dry-system-1.2.5/lib/dry/system/loader/autoloading.rb +24 -0
  20. data/dry-system-1.2.5/lib/dry/system/loader.rb +84 -0
  21. data/dry-system-1.2.5/lib/dry/system/magic_comments_parser.rb +31 -0
  22. data/dry-system-1.2.5/lib/dry/system/manifest_registrar.rb +57 -0
  23. data/dry-system-1.2.5/lib/dry/system/plugins/bootsnap.rb +47 -0
  24. data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph/strategies.rb +66 -0
  25. data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph.rb +53 -0
  26. data/dry-system-1.2.5/lib/dry/system/plugins/env.rb +30 -0
  27. data/dry-system-1.2.5/lib/dry/system/plugins/logging.rb +73 -0
  28. data/dry-system-1.2.5/lib/dry/system/plugins/monitoring/proxy.rb +51 -0
  29. data/dry-system-1.2.5/lib/dry/system/plugins/monitoring.rb +45 -0
  30. data/dry-system-1.2.5/lib/dry/system/plugins/notifications.rb +27 -0
  31. data/dry-system-1.2.5/lib/dry/system/plugins/plugin.rb +61 -0
  32. data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  33. data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk.rb +109 -0
  34. data/dry-system-1.2.5/lib/dry/system/plugins.rb +70 -0
  35. data/dry-system-1.2.5/lib/dry/system/provider/source.rb +281 -0
  36. data/dry-system-1.2.5/lib/dry/system/provider/source_dsl.rb +55 -0
  37. data/dry-system-1.2.5/lib/dry/system/provider.rb +291 -0
  38. data/dry-system-1.2.5/lib/dry/system/provider_registrar.rb +289 -0
  39. data/dry-system-1.2.5/lib/dry/system/provider_source_registry.rb +67 -0
  40. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/config.rb +73 -0
  41. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/loader.rb +44 -0
  42. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings.rb +40 -0
  43. data/dry-system-1.2.5/lib/dry/system/provider_sources.rb +6 -0
  44. data/dry-system-1.2.5/lib/dry/system/stubs.rb +39 -0
  45. data/dry-system-1.2.5/lib/dry/system/version.rb +7 -0
  46. data/dry-system-1.2.5/lib/dry/system.rb +62 -0
  47. data/dry-system-1.2.5/lib/dry-system.rb +3 -0
  48. data/piko-quick-lib.gemspec +11 -0
  49. 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