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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +678 -0
  3. data/LICENSE +1 -1
  4. data/README.md +5 -4
  5. data/dry-system.gemspec +18 -21
  6. data/lib/dry/system/auto_registrar.rb +9 -64
  7. data/lib/dry/system/component.rb +124 -104
  8. data/lib/dry/system/component_dir.rb +171 -0
  9. data/lib/dry/system/config/component_dir.rb +228 -0
  10. data/lib/dry/system/config/component_dirs.rb +289 -0
  11. data/lib/dry/system/config/namespace.rb +75 -0
  12. data/lib/dry/system/config/namespaces.rb +196 -0
  13. data/lib/dry/system/constants.rb +2 -4
  14. data/lib/dry/system/container.rb +305 -345
  15. data/lib/dry/system/errors.rb +73 -56
  16. data/lib/dry/system/identifier.rb +176 -0
  17. data/lib/dry/system/importer.rb +89 -12
  18. data/lib/dry/system/indirect_component.rb +63 -0
  19. data/lib/dry/system/loader/autoloading.rb +24 -0
  20. data/lib/dry/system/loader.rb +49 -41
  21. data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +13 -14
  22. data/lib/dry/system/plugins/bootsnap.rb +3 -2
  23. data/lib/dry/system/plugins/dependency_graph/strategies.rb +38 -2
  24. data/lib/dry/system/plugins/dependency_graph.rb +25 -21
  25. data/lib/dry/system/plugins/env.rb +3 -2
  26. data/lib/dry/system/plugins/logging.rb +9 -8
  27. data/lib/dry/system/plugins/monitoring.rb +1 -2
  28. data/lib/dry/system/plugins/notifications.rb +1 -1
  29. data/lib/dry/system/plugins/plugin.rb +61 -0
  30. data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  31. data/lib/dry/system/plugins/zeitwerk.rb +109 -0
  32. data/lib/dry/system/plugins.rb +5 -73
  33. data/lib/dry/system/provider/source.rb +276 -0
  34. data/lib/dry/system/provider/source_dsl.rb +55 -0
  35. data/lib/dry/system/provider.rb +261 -23
  36. data/lib/dry/system/provider_registrar.rb +251 -0
  37. data/lib/dry/system/provider_source_registry.rb +56 -0
  38. data/lib/dry/system/provider_sources/settings/config.rb +73 -0
  39. data/lib/dry/system/provider_sources/settings/loader.rb +44 -0
  40. data/lib/dry/system/provider_sources/settings.rb +40 -0
  41. data/lib/dry/system/provider_sources.rb +5 -0
  42. data/lib/dry/system/stubs.rb +6 -2
  43. data/lib/dry/system/version.rb +1 -1
  44. data/lib/dry/system.rb +35 -13
  45. metadata +48 -97
  46. data/lib/dry/system/auto_registrar/configuration.rb +0 -43
  47. data/lib/dry/system/booter/component_registry.rb +0 -35
  48. data/lib/dry/system/booter.rb +0 -181
  49. data/lib/dry/system/components/bootable.rb +0 -289
  50. data/lib/dry/system/components/config.rb +0 -35
  51. data/lib/dry/system/components.rb +0 -8
  52. data/lib/dry/system/lifecycle.rb +0 -135
  53. data/lib/dry/system/provider_registry.rb +0 -27
  54. data/lib/dry/system/settings/file_loader.rb +0 -30
  55. data/lib/dry/system/settings/file_parser.rb +0 -51
  56. data/lib/dry/system/settings.rb +0 -67
  57. 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
@@ -25,21 +27,18 @@ module Dry
25
27
  # @api private
26
28
  def finalize!
27
29
  ::Dir[registrations_dir.join(RB_GLOB)].sort.each do |file|
28
- call(File.basename(file, RB_EXT))
30
+ call(Identifier.new(File.basename(file, RB_EXT)))
29
31
  end
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
@@ -6,7 +6,7 @@ module Dry
6
6
  module DependencyGraph
7
7
  # @api private
8
8
  class Strategies
9
- extend Dry::Container::Mixin
9
+ extend Core::Container::Mixin
10
10
 
11
11
  # @api private
12
12
  class Kwargs < Dry::AutoInject::Strategies::Kwargs
@@ -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,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/constants"
4
- require "dry/system/plugins/dependency_graph/strategies"
5
-
6
3
  module Dry
7
4
  module System
8
5
  module Plugins
@@ -12,36 +9,43 @@ module Dry
12
9
  def self.extended(system)
13
10
  super
14
11
 
15
- system.use(:notifications)
16
-
17
- system.before(:configure) do
18
- setting :ignored_dependencies, []
19
- end
12
+ system.instance_eval do
13
+ use(:notifications)
20
14
 
21
- system.after(:configure) do
22
- self[:notifications].register_event(:resolved_dependency)
23
- self[:notifications].register_event(:registered_dependency)
15
+ setting :dependency_graph do
16
+ setting :ignored_dependencies, default: []
17
+ end
24
18
 
25
- strategies(Strategies)
19
+ after(:configure) do
20
+ self[:notifications].register_event(:resolved_dependency)
21
+ self[:notifications].register_event(:registered_dependency)
22
+ end
26
23
  end
27
24
  end
28
25
 
29
26
  # @api private
30
27
  def self.dependencies
31
- {'dry-events': "dry/events/publisher"}
28
+ {"dry-events" => "dry/events/publisher"}
32
29
  end
33
30
 
34
31
  # @api private
35
- def register(key, contents = nil, options = {}, &block)
36
- super
32
+ def injector(**options)
33
+ super(**options, strategies: DependencyGraph::Strategies)
34
+ end
37
35
 
38
- unless config.ignored_dependencies.include?(key.to_sym)
39
- self[:notifications].instrument(
40
- :registered_dependency, key: key, class: self[key].class
41
- )
36
+ # @api private
37
+ def register(key, contents = nil, options = {}, &block)
38
+ super.tap do
39
+ key = key.to_s
40
+
41
+ unless config.dependency_graph.ignored_dependencies.include?(key)
42
+ self[:notifications].instrument(
43
+ :registered_dependency,
44
+ key: key,
45
+ class: self[key].class
46
+ )
47
+ end
42
48
  end
43
-
44
- self
45
49
  end
46
50
  end
47
51
  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,17 +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,
17
- development: Logger::DEBUG,
18
- test: Logger::DEBUG,
19
- production: Logger::ERROR
16
+ setting :log_levels, default: {
17
+ development: Logger::DEBUG,
18
+ test: Logger::DEBUG,
19
+ production: Logger::ERROR
20
+ }
20
21
 
21
- setting :logger_class, ::Logger, reader: true
22
+ setting :logger_class, default: ::Logger, reader: true
22
23
  end
23
24
 
24
25
  system.after(:configure, &:register_logger)
@@ -39,7 +40,7 @@ module Dry
39
40
  elsif config.logger
40
41
  register(:logger, config.logger)
41
42
  else
42
- config.logger = logger = config.logger_class.new(log_file_path)
43
+ config.logger = config.logger_class.new(log_file_path)
43
44
  config.logger.level = log_level
44
45
 
45
46
  register(:logger, config.logger)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/constants"
4
- require "dry/system/plugins/monitoring/proxy"
5
4
 
6
5
  module Dry
7
6
  module System
@@ -21,7 +20,7 @@ module Dry
21
20
 
22
21
  # @api private
23
22
  def self.dependencies
24
- {'dry-events': "dry/events/publisher"}
23
+ {"dry-events": "dry/events/publisher"}
25
24
  end
26
25
 
27
26
  # @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"}
16
16
  end
17
17
 
18
18
  # @api private
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ module Plugins
6
+ # @api private
7
+ class Plugin
8
+ attr_reader :name
9
+
10
+ attr_reader :mod
11
+
12
+ attr_reader :block
13
+
14
+ # @api private
15
+ def initialize(name, mod, &block)
16
+ @name = name
17
+ @mod = mod
18
+ @block = block
19
+ end
20
+
21
+ # @api private
22
+ def apply_to(system, **options)
23
+ system.extend(stateful? ? mod.new(**options) : mod)
24
+ system.instance_eval(&block) if block
25
+ system
26
+ end
27
+
28
+ # @api private
29
+ def load_dependencies(dependencies = mod_dependencies, gem = nil)
30
+ Array(dependencies).each do |dependency|
31
+ if dependency.is_a?(Array) || dependency.is_a?(Hash)
32
+ dependency.each { |value| load_dependencies(*Array(value).reverse) }
33
+ elsif !Plugins.loaded_dependencies.include?(dependency.to_s)
34
+ load_dependency(dependency, gem)
35
+ end
36
+ end
37
+ end
38
+
39
+ # @api private
40
+ def load_dependency(dependency, gem)
41
+ Kernel.require dependency
42
+ Plugins.loaded_dependencies << dependency.to_s
43
+ rescue LoadError => e
44
+ raise PluginDependencyMissing.new(name, e.message, gem)
45
+ end
46
+
47
+ # @api private
48
+ def stateful?
49
+ mod < Module
50
+ end
51
+
52
+ # @api private
53
+ def mod_dependencies
54
+ return EMPTY_ARRAY unless mod.respond_to?(:dependencies)
55
+
56
+ mod.dependencies.is_a?(Array) ? mod.dependencies : [mod.dependencies]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -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
@@ -1,64 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/constants"
4
-
5
3
  module Dry
6
4
  module System
7
5
  module Plugins
8
- # @api private
9
- class Plugin
10
- attr_reader :name
11
-
12
- attr_reader :mod
13
-
14
- attr_reader :block
15
-
16
- # @api private
17
- def initialize(name, mod, &block)
18
- @name = name
19
- @mod = mod
20
- @block = block
21
- end
22
-
23
- # @api private
24
- def apply_to(system, options)
25
- system.extend(stateful? ? mod.new(options) : mod)
26
- system.instance_eval(&block) if block
27
- system
28
- end
29
-
30
- # @api private
31
- def load_dependencies(dependencies = mod_dependencies, gem = nil)
32
- Array(dependencies).each do |dependency|
33
- if dependency.is_a?(Array) || dependency.is_a?(Hash)
34
- dependency.each { |value| load_dependencies(*Array(value).reverse) }
35
- elsif !Plugins.loaded_dependencies.include?(dependency.to_s)
36
- load_dependency(dependency, gem)
37
- end
38
- end
39
- end
40
-
41
- # @api private
42
- def load_dependency(dependency, gem)
43
- Kernel.require dependency
44
- Plugins.loaded_dependencies << dependency.to_s
45
- rescue LoadError => e
46
- raise PluginDependencyMissing.new(name, e.message, gem)
47
- end
48
-
49
- # @api private
50
- def stateful?
51
- mod < Module
52
- end
53
-
54
- # @api private
55
- def mod_dependencies
56
- return EMPTY_ARRAY unless mod.respond_to?(:dependencies)
57
-
58
- mod.dependencies.is_a?(Array) ? mod.dependencies : [mod.dependencies]
59
- end
60
- end
61
-
62
6
  # Register a plugin
63
7
  #
64
8
  # @param [Symbol] name The name of a plugin
@@ -84,21 +28,19 @@ module Dry
84
28
  # Enables a plugin if not already enabled.
85
29
  # Raises error if plugin cannot be found in the plugin registry.
86
30
  #
87
- # Plugin identifier
88
- #
89
- # @param [Symbol] name The plugin identifier
31
+ # @param [Symbol] name The plugin name
90
32
  # @param [Hash] options Plugin options
91
33
  #
92
34
  # @return [self]
93
35
  #
94
36
  # @api public
95
- def use(name, options = {})
37
+ def use(name, **options)
96
38
  return self if enabled_plugins.include?(name)
97
39
 
98
- raise PluginNotFoundError, name unless (plugin = Plugins.registry[name])
40
+ raise PluginNotFoundError, name unless (plugin = Dry::System::Plugins.registry[name])
99
41
 
100
42
  plugin.load_dependencies
101
- plugin.apply_to(self, options)
43
+ plugin.apply_to(self, **options)
102
44
 
103
45
  enabled_plugins << name
104
46
 
@@ -116,23 +58,13 @@ module Dry
116
58
  @enabled_plugins ||= []
117
59
  end
118
60
 
119
- require "dry/system/plugins/bootsnap"
120
61
  register(:bootsnap, Plugins::Bootsnap)
121
-
122
- require "dry/system/plugins/logging"
123
62
  register(:logging, Plugins::Logging)
124
-
125
- require "dry/system/plugins/env"
126
63
  register(:env, Plugins::Env)
127
-
128
- require "dry/system/plugins/notifications"
129
64
  register(:notifications, Plugins::Notifications)
130
-
131
- require "dry/system/plugins/monitoring"
132
65
  register(:monitoring, Plugins::Monitoring)
133
-
134
- require "dry/system/plugins/dependency_graph"
135
66
  register(:dependency_graph, Plugins::DependencyGraph)
67
+ register(:zeitwerk, Plugins::Zeitwerk)
136
68
  end
137
69
  end
138
70
  end