dry-system 0.19.2 → 0.23.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +472 -1
  3. data/LICENSE +1 -1
  4. data/README.md +4 -3
  5. data/dry-system.gemspec +16 -15
  6. data/lib/dry/system/auto_registrar.rb +1 -13
  7. data/lib/dry/system/component.rb +104 -47
  8. data/lib/dry/system/component_dir.rb +88 -47
  9. data/lib/dry/system/components.rb +8 -4
  10. data/lib/dry/system/config/component_dir.rb +141 -53
  11. data/lib/dry/system/config/component_dirs.rb +176 -70
  12. data/lib/dry/system/config/namespace.rb +76 -0
  13. data/lib/dry/system/config/namespaces.rb +208 -0
  14. data/lib/dry/system/constants.rb +2 -2
  15. data/lib/dry/system/container.rb +279 -201
  16. data/lib/dry/system/errors.rb +72 -61
  17. data/lib/dry/system/identifier.rb +99 -79
  18. data/lib/dry/system/importer.rb +83 -12
  19. data/lib/dry/system/indirect_component.rb +65 -0
  20. data/lib/dry/system/loader.rb +8 -4
  21. data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +12 -13
  22. data/lib/dry/system/plugins/bootsnap.rb +3 -2
  23. data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
  24. data/lib/dry/system/plugins/dependency_graph.rb +26 -20
  25. data/lib/dry/system/plugins/env.rb +3 -2
  26. data/lib/dry/system/plugins/logging.rb +9 -5
  27. data/lib/dry/system/plugins/monitoring.rb +1 -1
  28. data/lib/dry/system/plugins/notifications.rb +1 -1
  29. data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  30. data/lib/dry/system/plugins/zeitwerk.rb +109 -0
  31. data/lib/dry/system/plugins.rb +8 -7
  32. data/lib/dry/system/provider/source.rb +324 -0
  33. data/lib/dry/system/provider/source_dsl.rb +94 -0
  34. data/lib/dry/system/provider.rb +264 -24
  35. data/lib/dry/system/provider_registrar.rb +276 -0
  36. data/lib/dry/system/provider_source_registry.rb +70 -0
  37. data/lib/dry/system/provider_sources/settings/config.rb +86 -0
  38. data/lib/dry/system/provider_sources/settings/loader.rb +53 -0
  39. data/lib/dry/system/provider_sources/settings.rb +40 -0
  40. data/lib/dry/system/provider_sources.rb +5 -0
  41. data/lib/dry/system/stubs.rb +1 -1
  42. data/lib/dry/system/version.rb +1 -1
  43. data/lib/dry/system.rb +45 -13
  44. metadata +25 -22
  45. data/lib/dry/system/booter/component_registry.rb +0 -35
  46. data/lib/dry/system/booter.rb +0 -200
  47. data/lib/dry/system/components/bootable.rb +0 -289
  48. data/lib/dry/system/components/config.rb +0 -35
  49. data/lib/dry/system/lifecycle.rb +0 -135
  50. data/lib/dry/system/provider_registry.rb +0 -27
  51. data/lib/dry/system/settings/file_loader.rb +0 -30
  52. data/lib/dry/system/settings/file_parser.rb +0 -51
  53. data/lib/dry/system/settings.rb +0 -67
  54. data/lib/dry/system/system_components/settings.rb +0 -11
@@ -4,19 +4,21 @@ require "dry/system/constants"
4
4
 
5
5
  module Dry
6
6
  module System
7
- # Default manual registration implementation
7
+ # Default manifest registration implementation
8
8
  #
9
- # This is currently configured by default for every System::Container.
10
- # Manual registrar objects are responsible for loading files from configured
11
- # manual registration paths, which should hold code to explicitly register
9
+ # This is configured by default for every System::Container. The manifest registrar is
10
+ # responsible for loading manifest files that contain code to manually register
12
11
  # certain objects with the container.
13
12
  #
14
13
  # @api private
15
- class ManualRegistrar
14
+ class ManifestRegistrar
15
+ # @api private
16
16
  attr_reader :container
17
17
 
18
+ # @api private
18
19
  attr_reader :config
19
20
 
21
+ # @api private
20
22
  def initialize(container)
21
23
  @container = container
22
24
  @config = container.config
@@ -30,16 +32,13 @@ module Dry
30
32
  end
31
33
 
32
34
  # @api private
33
- def call(name)
34
- name = name.respond_to?(:root_key) ? name.root_key.to_s : name
35
-
36
- require(root.join(config.registrations_dir, name))
35
+ def call(component)
36
+ require(root.join(config.registrations_dir, component.root_key.to_s))
37
37
  end
38
38
 
39
- def file_exists?(name)
40
- name = name.respond_to?(:root_key) ? name.root_key.to_s : name
41
-
42
- File.exist?(File.join(registrations_dir, "#{name}#{RB_EXT}"))
39
+ # @api private
40
+ def file_exists?(component)
41
+ File.exist?(File.join(registrations_dir, "#{component.root_key}#{RB_EXT}"))
43
42
  end
44
43
 
45
44
  private
@@ -15,8 +15,9 @@ module Dry
15
15
  # @api private
16
16
  def self.extended(system)
17
17
  super
18
+
18
19
  system.use(:env)
19
- system.before(:configure) { setting :bootsnap, DEFAULT_OPTIONS }
20
+ system.setting :bootsnap, default: DEFAULT_OPTIONS
20
21
  system.after(:configure, &:setup_bootsnap)
21
22
  end
22
23
 
@@ -36,7 +37,7 @@ module Dry
36
37
 
37
38
  # @api private
38
39
  def bootsnap_available?
39
- RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.3.0" && RUBY_VERSION < "2.5.0"
40
+ RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.3.0" && RUBY_VERSION < "3.1.0"
40
41
  end
41
42
  end
42
43
  end
@@ -15,13 +15,49 @@ module Dry
15
15
  # @api private
16
16
  def define_initialize(klass)
17
17
  @container["notifications"].instrument(
18
- :resolved_dependency, dependency_map: dependency_map.to_h, target_class: klass
18
+ :resolved_dependency,
19
+ dependency_map: dependency_map.to_h,
20
+ target_class: klass
19
21
  )
22
+
23
+ super(klass)
24
+ end
25
+ end
26
+
27
+ # @api private
28
+ class Args < Dry::AutoInject::Strategies::Args
29
+ private
30
+
31
+ # @api private
32
+ def define_initialize(klass)
33
+ @container["notifications"].instrument(
34
+ :resolved_dependency,
35
+ dependency_map: dependency_map.to_h,
36
+ target_class: klass
37
+ )
38
+
39
+ super(klass)
40
+ end
41
+ end
42
+
43
+ class Hash < Dry::AutoInject::Strategies::Hash
44
+ private
45
+
46
+ # @api private
47
+ def define_initialize(klass)
48
+ @container["notifications"].instrument(
49
+ :resolved_dependency,
50
+ dependency_map: dependency_map.to_h,
51
+ target_class: klass
52
+ )
53
+
20
54
  super(klass)
21
55
  end
22
56
  end
23
57
 
24
58
  register :kwargs, Kwargs
59
+ register :args, Args
60
+ register :hash, Hash
25
61
  register :default, Kwargs
26
62
  end
27
63
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/constants"
4
- require "dry/system/plugins/dependency_graph/strategies"
3
+ require_relative "dependency_graph/strategies"
5
4
 
6
5
  module Dry
7
6
  module System
@@ -12,36 +11,43 @@ module Dry
12
11
  def self.extended(system)
13
12
  super
14
13
 
15
- system.use(:notifications)
14
+ system.instance_eval do
15
+ use(:notifications)
16
16
 
17
- system.before(:configure) do
18
- setting :ignored_dependencies, []
19
- end
20
-
21
- system.after(:configure) do
22
- self[:notifications].register_event(:resolved_dependency)
23
- self[:notifications].register_event(:registered_dependency)
17
+ setting :dependency_graph do
18
+ setting :ignored_dependencies, default: []
19
+ end
24
20
 
25
- strategies(Strategies)
21
+ after(:configure) do
22
+ self[:notifications].register_event(:resolved_dependency)
23
+ self[:notifications].register_event(:registered_dependency)
24
+ end
26
25
  end
27
26
  end
28
27
 
29
28
  # @api private
30
29
  def self.dependencies
31
- {'dry-events': "dry/events/publisher"}
30
+ {"dry-events" => "dry/events/publisher"}
32
31
  end
33
32
 
34
33
  # @api private
35
- def register(key, contents = nil, options = {}, &block)
36
- super
34
+ def injector(**options)
35
+ super(**options, strategies: DependencyGraph::Strategies)
36
+ end
37
37
 
38
- unless config.ignored_dependencies.include?(key.to_sym)
39
- self[:notifications].instrument(
40
- :registered_dependency, key: key, class: self[key].class
41
- )
38
+ # @api private
39
+ def register(key, contents = nil, options = {}, &block)
40
+ super.tap do
41
+ key = key.to_s
42
+
43
+ unless config.dependency_graph.ignored_dependencies.include?(key)
44
+ self[:notifications].instrument(
45
+ :registered_dependency,
46
+ key: key,
47
+ class: self[key].class
48
+ )
49
+ end
42
50
  end
43
-
44
- self
45
51
  end
46
52
  end
47
53
  end
@@ -10,8 +10,9 @@ module Dry
10
10
  attr_reader :options
11
11
 
12
12
  # @api private
13
- def initialize(options)
13
+ def initialize(**options)
14
14
  @options = options
15
+ super()
15
16
  end
16
17
 
17
18
  def inferrer
@@ -20,7 +21,7 @@ module Dry
20
21
 
21
22
  # @api private
22
23
  def extended(system)
23
- system.setting :env, inferrer.(), reader: true
24
+ system.setting :env, default: inferrer.(), reader: true
24
25
  super
25
26
  end
26
27
  end
@@ -8,14 +8,18 @@ module Dry
8
8
  module Logging
9
9
  # @api private
10
10
  def self.extended(system)
11
- system.before(:configure) do
11
+ system.instance_eval do
12
12
  setting :logger, reader: true
13
13
 
14
- setting :log_dir, "log"
14
+ setting :log_dir, default: "log"
15
15
 
16
- setting :log_levels, {development: Logger::DEBUG, test: Logger::DEBUG, production: Logger::ERROR}
16
+ setting :log_levels, default: {
17
+ development: Logger::DEBUG,
18
+ test: Logger::DEBUG,
19
+ production: Logger::ERROR
20
+ }
17
21
 
18
- setting :logger_class, ::Logger, reader: true
22
+ setting :logger_class, default: ::Logger, reader: true
19
23
  end
20
24
 
21
25
  system.after(:configure, &:register_logger)
@@ -36,7 +40,7 @@ module Dry
36
40
  elsif config.logger
37
41
  register(:logger, config.logger)
38
42
  else
39
- config.logger = logger = config.logger_class.new(log_file_path)
43
+ config.logger = config.logger_class.new(log_file_path)
40
44
  config.logger.level = log_level
41
45
 
42
46
  register(:logger, config.logger)
@@ -21,7 +21,7 @@ module Dry
21
21
 
22
22
  # @api private
23
23
  def self.dependencies
24
- {'dry-events': "dry/events/publisher"}
24
+ {"dry-events": "dry/events/publisher"}
25
25
  end
26
26
 
27
27
  # @api private
@@ -12,7 +12,7 @@ module Dry
12
12
 
13
13
  # @api private
14
14
  def self.dependencies
15
- {'dry-monitor': "dry/monitor/notifications"}
15
+ {"dry-monitor": "dry/monitor/notifications"}
16
16
  end
17
17
 
18
18
  # @api private
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ module Plugins
6
+ class Zeitwerk < Module
7
+ # @api private
8
+ class CompatInflector
9
+ attr_reader :config
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def camelize(string, _)
16
+ config.inflector.camelize(string)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+
5
+ module Dry
6
+ module System
7
+ module Plugins
8
+ # @api private
9
+ class Zeitwerk < Module
10
+ # @api private
11
+ def self.dependencies
12
+ [
13
+ "dry/system/loader/autoloading",
14
+ "dry/system/plugins/zeitwerk/compat_inflector",
15
+ {"zeitwerk" => "zeitwerk"}
16
+ ]
17
+ end
18
+
19
+ # @api private
20
+ attr_reader :loader, :run_setup, :eager_load, :debug
21
+
22
+ # @api private
23
+ def initialize(loader: nil, run_setup: true, eager_load: nil, debug: false)
24
+ @loader = loader || ::Zeitwerk::Loader.new
25
+ @run_setup = run_setup
26
+ @eager_load = eager_load
27
+ @debug = debug
28
+ super()
29
+ end
30
+
31
+ # @api private
32
+ def extended(system)
33
+ system.setting :autoloader, reader: true
34
+
35
+ system.config.autoloader = loader
36
+ system.config.component_dirs.loader = Dry::System::Loader::Autoloading
37
+ system.config.component_dirs.add_to_load_path = false
38
+
39
+ system.after(:configure, &method(:setup_autoloader))
40
+
41
+ super
42
+ end
43
+
44
+ private
45
+
46
+ def setup_autoloader(system)
47
+ configure_loader(system.autoloader, system)
48
+
49
+ push_component_dirs_to_loader(system, system.autoloader)
50
+
51
+ system.autoloader.setup if run_setup
52
+
53
+ system.after(:finalize) { system.autoloader.eager_load } if eager_load?(system)
54
+
55
+ system
56
+ end
57
+
58
+ # Build a zeitwerk loader with the configured component directories
59
+ #
60
+ # @return [Zeitwerk::Loader]
61
+ def configure_loader(loader, system)
62
+ loader.tag = system.config.name || system.name unless loader.tag
63
+ loader.inflector = CompatInflector.new(system.config)
64
+ loader.logger = method(:puts) if debug
65
+ end
66
+
67
+ # Add component dirs to the zeitwerk loader
68
+ #
69
+ # @return [Zeitwerk::Loader]
70
+ def push_component_dirs_to_loader(system, loader)
71
+ system.config.component_dirs.each do |dir|
72
+ dir.namespaces.each do |ns|
73
+ loader.push_dir(
74
+ system.root.join(dir.path, ns.path.to_s),
75
+ namespace: module_for_namespace(ns, system.config.inflector)
76
+ )
77
+ end
78
+ end
79
+
80
+ loader
81
+ end
82
+
83
+ def module_for_namespace(namespace, inflector)
84
+ return Object unless namespace.const
85
+
86
+ begin
87
+ inflector.constantize(inflector.camelize(namespace.const))
88
+ rescue NameError
89
+ namespace.const.split(PATH_SEPARATOR).reduce(Object) { |parent_mod, mod_path|
90
+ get_or_define_module(parent_mod, inflector.camelize(mod_path))
91
+ }
92
+ end
93
+ end
94
+
95
+ def get_or_define_module(parent_mod, name)
96
+ parent_mod.const_get(name)
97
+ rescue NameError
98
+ parent_mod.const_set(name, Module.new)
99
+ end
100
+
101
+ def eager_load?(system)
102
+ return eager_load unless eager_load.nil?
103
+
104
+ system.config.respond_to?(:env) && system.config.env == :production
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -21,8 +21,8 @@ module Dry
21
21
  end
22
22
 
23
23
  # @api private
24
- def apply_to(system, options)
25
- system.extend(stateful? ? mod.new(options) : mod)
24
+ def apply_to(system, **options)
25
+ system.extend(stateful? ? mod.new(**options) : mod)
26
26
  system.instance_eval(&block) if block
27
27
  system
28
28
  end
@@ -84,21 +84,19 @@ module Dry
84
84
  # Enables a plugin if not already enabled.
85
85
  # Raises error if plugin cannot be found in the plugin registry.
86
86
  #
87
- # Plugin identifier
88
- #
89
- # @param [Symbol] name The plugin identifier
87
+ # @param [Symbol] name The plugin name
90
88
  # @param [Hash] options Plugin options
91
89
  #
92
90
  # @return [self]
93
91
  #
94
92
  # @api public
95
- def use(name, options = {})
93
+ def use(name, **options)
96
94
  return self if enabled_plugins.include?(name)
97
95
 
98
96
  raise PluginNotFoundError, name unless (plugin = Plugins.registry[name])
99
97
 
100
98
  plugin.load_dependencies
101
- plugin.apply_to(self, options)
99
+ plugin.apply_to(self, **options)
102
100
 
103
101
  enabled_plugins << name
104
102
 
@@ -133,6 +131,9 @@ module Dry
133
131
 
134
132
  require "dry/system/plugins/dependency_graph"
135
133
  register(:dependency_graph, Plugins::DependencyGraph)
134
+
135
+ require "dry/system/plugins/zeitwerk"
136
+ register(:zeitwerk, Plugins::Zeitwerk)
136
137
  end
137
138
  end
138
139
  end