dry-system 0.19.2 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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