dry-system 0.22.0 → 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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +400 -0
  3. data/LICENSE +1 -1
  4. data/README.md +2 -2
  5. data/dry-system.gemspec +2 -2
  6. data/lib/dry/system/component.rb +2 -3
  7. data/lib/dry/system/component_dir.rb +8 -34
  8. data/lib/dry/system/components.rb +8 -4
  9. data/lib/dry/system/config/component_dir.rb +60 -16
  10. data/lib/dry/system/config/component_dirs.rb +23 -10
  11. data/lib/dry/system/config/namespace.rb +4 -6
  12. data/lib/dry/system/constants.rb +1 -1
  13. data/lib/dry/system/container.rb +264 -182
  14. data/lib/dry/system/errors.rb +73 -53
  15. data/lib/dry/system/identifier.rb +62 -20
  16. data/lib/dry/system/importer.rb +83 -12
  17. data/lib/dry/system/indirect_component.rb +1 -1
  18. data/lib/dry/system/loader.rb +6 -1
  19. data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +8 -5
  20. data/lib/dry/system/plugins/bootsnap.rb +2 -1
  21. data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
  22. data/lib/dry/system/plugins/dependency_graph.rb +26 -20
  23. data/lib/dry/system/plugins/env.rb +2 -1
  24. data/lib/dry/system/plugins/logging.rb +2 -2
  25. data/lib/dry/system/plugins/monitoring.rb +1 -1
  26. data/lib/dry/system/plugins/notifications.rb +1 -1
  27. data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  28. data/lib/dry/system/plugins/zeitwerk.rb +109 -0
  29. data/lib/dry/system/plugins.rb +7 -4
  30. data/lib/dry/system/provider/source.rb +324 -0
  31. data/lib/dry/system/provider/source_dsl.rb +94 -0
  32. data/lib/dry/system/provider.rb +262 -22
  33. data/lib/dry/system/provider_registrar.rb +276 -0
  34. data/lib/dry/system/provider_source_registry.rb +70 -0
  35. data/lib/dry/system/provider_sources/settings/config.rb +86 -0
  36. data/lib/dry/system/provider_sources/settings/loader.rb +53 -0
  37. data/lib/dry/system/provider_sources/settings.rb +40 -0
  38. data/lib/dry/system/provider_sources.rb +5 -0
  39. data/lib/dry/system/version.rb +1 -1
  40. data/lib/dry/system.rb +44 -12
  41. metadata +18 -18
  42. data/lib/dry/system/booter/component_registry.rb +0 -35
  43. data/lib/dry/system/booter.rb +0 -200
  44. data/lib/dry/system/components/bootable.rb +0 -280
  45. data/lib/dry/system/components/config.rb +0 -35
  46. data/lib/dry/system/lifecycle.rb +0 -135
  47. data/lib/dry/system/provider_registry.rb +0 -27
  48. data/lib/dry/system/settings/file_loader.rb +0 -30
  49. data/lib/dry/system/settings/file_parser.rb +0 -51
  50. data/lib/dry/system/settings.rb +0 -64
  51. data/lib/dry/system/system_components/settings.rb +0 -11
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/deprecations"
5
+ require_relative "loader"
6
+
7
+ module Dry
8
+ module System
9
+ module ProviderSources
10
+ # @api private
11
+ module Settings
12
+ InvalidSettingsError = Class.new(ArgumentError) do
13
+ # @api private
14
+ def initialize(errors)
15
+ message = <<~STR
16
+ Could not load settings. The following settings were invalid:
17
+
18
+ #{setting_errors(errors).join("\n")}
19
+ STR
20
+
21
+ super(message)
22
+ end
23
+
24
+ private
25
+
26
+ def setting_errors(errors)
27
+ errors.sort_by { |k, _| k }.map { |key, error| "#{key}: #{error}" }
28
+ end
29
+ end
30
+
31
+ # @api private
32
+ class Config
33
+ # @api private
34
+ def self.load(root:, env:, loader: Loader)
35
+ loader = loader.new(root: root, env: env)
36
+
37
+ new.tap do |settings_obj|
38
+ errors = {}
39
+
40
+ settings.to_a.each do |setting_name|
41
+ value = loader[setting_name.to_s.upcase]
42
+
43
+ begin
44
+ settings_obj.config.public_send(:"#{setting_name}=", value) if value
45
+ rescue => e # rubocop:disable Style/RescueStandardError
46
+ errors[setting_name] = e
47
+ end
48
+ end
49
+
50
+ raise InvalidSettingsError, errors unless errors.empty?
51
+ end
52
+ end
53
+
54
+ # rubocop:disable Layout/LineLength
55
+ def self.key(name, type)
56
+ Dry::Core::Deprecations.announce(
57
+ "Dry::System :settings provider source setting definition using `key`",
58
+ "Use `setting` instead, with dry-configurable `setting` options, e.g. `setting :my_setting, default: \"hello\", constructor: Types::String.constrained(min_length: 3)`",
59
+ tag: "dry-system",
60
+ uplevel: 1
61
+ )
62
+
63
+ setting(name, constructor: type)
64
+ end
65
+ # rubocop:enable Layout/LineLength
66
+
67
+ include Dry::Configurable
68
+
69
+ private
70
+
71
+ def method_missing(name, *args, &block)
72
+ if config.respond_to?(name)
73
+ config.public_send(name, *args, &block)
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ def respond_to_missing?(name, include_all = false)
80
+ config.respond_to?(name, include_all) || super
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/deprecations"
5
+
6
+ module Dry
7
+ module System
8
+ module ProviderSources
9
+ module Settings
10
+ # @api private
11
+ class Loader
12
+ # @api private
13
+ attr_reader :store
14
+
15
+ # @api private
16
+ def initialize(root:, env:, store: ENV)
17
+ @store = store
18
+ load_dotenv(root, env.to_sym)
19
+ end
20
+
21
+ # @api private
22
+ def [](key)
23
+ store[key]
24
+ end
25
+
26
+ private
27
+
28
+ def load_dotenv(root, env)
29
+ require "dotenv"
30
+ Dotenv.load(*dotenv_files(root, env)) if defined?(Dotenv)
31
+ rescue LoadError
32
+ Dry::Core::Deprecations.announce(
33
+ "Dry::System :settings provider now requires dotenv to to load settings from .env files`", # rubocop:disable Layout/LineLength
34
+ "Add `gem \"dotenv\"` to your application's `Gemfile`",
35
+ tag: "dry-system",
36
+ uplevel: 3
37
+ )
38
+ # Do nothing if dotenv is unavailable
39
+ end
40
+
41
+ def dotenv_files(root, env)
42
+ [
43
+ File.join(root, ".env.#{env}.local"),
44
+ (File.join(root, ".env.local") unless env == :test),
45
+ File.join(root, ".env.#{env}"),
46
+ File.join(root, ".env")
47
+ ].compact
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ module ProviderSources
6
+ module Settings
7
+ class Source < Dry::System::Provider::Source
8
+ setting :store
9
+
10
+ def prepare
11
+ require "dry/system/provider_sources/settings/config"
12
+ end
13
+
14
+ def start
15
+ register(:settings, settings.load(root: target.root, env: target.config.env))
16
+ end
17
+
18
+ def settings(&block)
19
+ # Save the block and evaluate it lazily to allow a provider with this source
20
+ # to `require` any necessary files for the block to evaluate correctly (e.g.
21
+ # requiring an app-specific types module for setting constructors)
22
+ if block
23
+ @settings_block = block
24
+ elsif @settings_class
25
+ @settings_class
26
+ elsif @settings_block
27
+ @settings_class = Class.new(Settings::Config, &@settings_block)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ Dry::System.register_provider_source(
37
+ :settings,
38
+ group: :dry_system,
39
+ source: Dry::System::ProviderSources::Settings::Source
40
+ )
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system"
4
+
5
+ Dry::System.register_provider_sources Pathname(__dir__).join("provider_sources").realpath
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module System
5
- VERSION = "0.22.0"
5
+ VERSION = "0.23.0"
6
6
  end
7
7
  end
data/lib/dry/system.rb CHANGED
@@ -1,30 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/provider"
4
- require "dry/system/provider_registry"
3
+ require "dry/core/deprecations"
4
+ require_relative "system/provider_source_registry"
5
5
 
6
6
  module Dry
7
7
  module System
8
- # Register external component provider
8
+ # Registers the provider sources in the files under the given path
9
9
  #
10
10
  # @api public
11
- def self.register_provider(name, options)
12
- providers.register(name, options)
13
- providers[name].load_components
14
- self
11
+ def self.register_provider_sources(path)
12
+ provider_sources.load_sources(path)
15
13
  end
16
14
 
17
- # Register an external component that can be booted within other systems
15
+ def self.register_provider(_name, options)
16
+ Dry::Core::Deprecations.announce(
17
+ "Dry::System.register_provider",
18
+ "Use `Dry::System.register_provider_sources` instead",
19
+ tag: "dry-system",
20
+ uplevel: 1
21
+ )
22
+
23
+ register_provider_sources(options.fetch(:path))
24
+ end
25
+
26
+ # Registers a provider source, which can be used as the basis for other providers
18
27
  #
19
28
  # @api public
29
+ def self.register_provider_source(name, group:, source: nil, &block)
30
+ if source && block
31
+ raise ArgumentError, "You must supply only a `source:` option or a block, not both"
32
+ end
33
+
34
+ if source
35
+ provider_sources.register(name: name, group: group, source: source)
36
+ else
37
+ provider_sources.register_from_block(
38
+ name: name,
39
+ group: group,
40
+ target_container: self,
41
+ &block
42
+ )
43
+ end
44
+ end
45
+
20
46
  def self.register_component(name, provider:, &block)
21
- providers[provider].register_component(name, block)
22
- self
47
+ Dry::Core::Deprecations.announce(
48
+ "Dry::System.register_component",
49
+ "Use `Dry::System.register_provider_source` instead",
50
+ tag: "dry-system",
51
+ uplevel: 1
52
+ )
53
+
54
+ register_provider_source(name, group: provider, &block)
23
55
  end
24
56
 
25
57
  # @api private
26
- def self.providers
27
- @providers ||= ProviderRegistry.new
58
+ def self.provider_sources
59
+ @provider_sources ||= ProviderSourceRegistry.new
28
60
  end
29
61
  end
30
62
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-system
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-06 00:00:00.000000000 Z
11
+ date: 2022-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -44,20 +44,20 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.13'
47
+ version: '0.14'
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: 0.13.0
50
+ version: 0.14.0
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - "~>"
56
56
  - !ruby/object:Gem::Version
57
- version: '0.13'
57
+ version: '0.14'
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 0.13.0
60
+ version: 0.14.0
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: dry-container
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -188,13 +188,9 @@ files:
188
188
  - lib/dry-system.rb
189
189
  - lib/dry/system.rb
190
190
  - lib/dry/system/auto_registrar.rb
191
- - lib/dry/system/booter.rb
192
- - lib/dry/system/booter/component_registry.rb
193
191
  - lib/dry/system/component.rb
194
192
  - lib/dry/system/component_dir.rb
195
193
  - lib/dry/system/components.rb
196
- - lib/dry/system/components/bootable.rb
197
- - lib/dry/system/components/config.rb
198
194
  - lib/dry/system/config/component_dir.rb
199
195
  - lib/dry/system/config/component_dirs.rb
200
196
  - lib/dry/system/config/namespace.rb
@@ -205,11 +201,10 @@ files:
205
201
  - lib/dry/system/identifier.rb
206
202
  - lib/dry/system/importer.rb
207
203
  - lib/dry/system/indirect_component.rb
208
- - lib/dry/system/lifecycle.rb
209
204
  - lib/dry/system/loader.rb
210
205
  - lib/dry/system/loader/autoloading.rb
211
206
  - lib/dry/system/magic_comments_parser.rb
212
- - lib/dry/system/manual_registrar.rb
207
+ - lib/dry/system/manifest_registrar.rb
213
208
  - lib/dry/system/plugins.rb
214
209
  - lib/dry/system/plugins/bootsnap.rb
215
210
  - lib/dry/system/plugins/dependency_graph.rb
@@ -219,13 +214,18 @@ files:
219
214
  - lib/dry/system/plugins/monitoring.rb
220
215
  - lib/dry/system/plugins/monitoring/proxy.rb
221
216
  - lib/dry/system/plugins/notifications.rb
217
+ - lib/dry/system/plugins/zeitwerk.rb
218
+ - lib/dry/system/plugins/zeitwerk/compat_inflector.rb
222
219
  - lib/dry/system/provider.rb
223
- - lib/dry/system/provider_registry.rb
224
- - lib/dry/system/settings.rb
225
- - lib/dry/system/settings/file_loader.rb
226
- - lib/dry/system/settings/file_parser.rb
220
+ - lib/dry/system/provider/source.rb
221
+ - lib/dry/system/provider/source_dsl.rb
222
+ - lib/dry/system/provider_registrar.rb
223
+ - lib/dry/system/provider_source_registry.rb
224
+ - lib/dry/system/provider_sources.rb
225
+ - lib/dry/system/provider_sources/settings.rb
226
+ - lib/dry/system/provider_sources/settings/config.rb
227
+ - lib/dry/system/provider_sources/settings/loader.rb
227
228
  - lib/dry/system/stubs.rb
228
- - lib/dry/system/system_components/settings.rb
229
229
  - lib/dry/system/version.rb
230
230
  homepage: https://dry-rb.org/gems/dry-system
231
231
  licenses:
@@ -243,7 +243,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
243
243
  requirements:
244
244
  - - ">="
245
245
  - !ruby/object:Gem::Version
246
- version: 2.6.0
246
+ version: 2.7.0
247
247
  required_rubygems_version: !ruby/object:Gem::Requirement
248
248
  requirements:
249
249
  - - ">="
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dry
4
- module System
5
- class Booter
6
- class ComponentRegistry
7
- include Enumerable
8
-
9
- attr_reader :components
10
-
11
- def initialize
12
- @components = []
13
- end
14
-
15
- def each(&block)
16
- components.each(&block)
17
- end
18
-
19
- def register(component)
20
- @components << component
21
- end
22
-
23
- def exists?(name)
24
- components.any? { |component| component.name == name }
25
- end
26
-
27
- def [](name)
28
- component = components.detect { |c| c.name == name }
29
-
30
- component || raise(InvalidComponentNameError, name)
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,200 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/system/components/bootable"
4
- require "dry/system/errors"
5
- require "dry/system/constants"
6
- require "dry/system/lifecycle"
7
- require "dry/system/booter/component_registry"
8
- require "pathname"
9
-
10
- module Dry
11
- module System
12
- # Default booter implementation
13
- #
14
- # This is currently configured by default for every System::Container.
15
- # Booter objects are responsible for loading system/boot files and expose
16
- # an API for calling lifecycle triggers.
17
- #
18
- # @api private
19
- class Booter
20
- attr_reader :paths
21
-
22
- attr_reader :booted
23
-
24
- attr_reader :components
25
-
26
- # @api private
27
- def initialize(paths)
28
- @paths = paths
29
- @booted = []
30
- @components = ComponentRegistry.new
31
- end
32
-
33
- # @api private
34
- def register_component(component)
35
- components.register(component)
36
- self
37
- end
38
-
39
- # Returns a bootable component if it can be found or loaded, otherwise nil
40
- #
41
- # @return [Dry::System::Components::Bootable, nil]
42
- # @api private
43
- def find_component(name)
44
- name = name.to_sym
45
-
46
- return components[name] if components.exists?(name)
47
-
48
- return if finalized?
49
-
50
- require_boot_file(name)
51
-
52
- components[name] if components.exists?(name)
53
- end
54
-
55
- # @api private
56
- def finalize!
57
- boot_files.each do |path|
58
- load_component(path)
59
- end
60
-
61
- components.each do |component|
62
- start(component)
63
- end
64
-
65
- freeze
66
- end
67
-
68
- # @!method finalized?
69
- # Returns true if the booter has been finalized
70
- #
71
- # @return [Boolean]
72
- # @api private
73
- alias_method :finalized?, :frozen?
74
-
75
- # @api private
76
- def shutdown
77
- components.each do |component|
78
- next unless booted.include?(component)
79
-
80
- stop(component)
81
- end
82
- end
83
-
84
- # @api private
85
- def init(name_or_component)
86
- with_component(name_or_component) do |component|
87
- call(component) do
88
- component.init.finalize
89
- yield if block_given?
90
- end
91
-
92
- self
93
- end
94
- end
95
-
96
- # @api private
97
- def start(name_or_component)
98
- with_component(name_or_component) do |component|
99
- return self if booted.include?(component)
100
-
101
- init(name_or_component) do
102
- component.start
103
- end
104
-
105
- booted << component.finalize
106
-
107
- self
108
- end
109
- end
110
-
111
- # @api private
112
- def stop(name_or_component)
113
- call(name_or_component) do |component|
114
- raise ComponentNotStartedError, name_or_component unless booted.include?(component)
115
-
116
- component.stop
117
- booted.delete(component)
118
-
119
- yield if block_given?
120
- end
121
- end
122
-
123
- # @api private
124
- def call(name_or_component)
125
- with_component(name_or_component) do |component|
126
- raise ComponentFileMismatchError, name unless component
127
-
128
- yield(component) if block_given?
129
-
130
- component
131
- end
132
- end
133
-
134
- # @api private
135
- def boot_dependency(component)
136
- if (component = find_component(component.root_key))
137
- start(component)
138
- end
139
- end
140
-
141
- # Returns all boot files within the configured paths
142
- #
143
- # Searches for files in the order of the configured paths. In the case of multiple
144
- # identically-named boot files within different paths, the file found first will be
145
- # returned, and other matching files will be discarded.
146
- #
147
- # @return [Array<Pathname>]
148
- # @api public
149
- def boot_files
150
- @boot_files ||= paths.each_with_object([[], []]) { |path, (boot_files, loaded)|
151
- files = Dir["#{path}/#{RB_GLOB}"].sort
152
-
153
- files.each do |file|
154
- basename = File.basename(file)
155
-
156
- unless loaded.include?(basename)
157
- boot_files << Pathname(file)
158
- loaded << basename
159
- end
160
- end
161
- }.first
162
- end
163
-
164
- private
165
-
166
- def with_component(id_or_component)
167
- component =
168
- case id_or_component
169
- when Symbol
170
- require_boot_file(id_or_component) unless components.exists?(id_or_component)
171
- components[id_or_component]
172
- when Components::Bootable
173
- id_or_component
174
- end
175
-
176
- raise InvalidComponentError, id_or_component unless component
177
-
178
- yield(component)
179
- end
180
-
181
- def load_component(path)
182
- name = Pathname(path).basename(RB_EXT).to_s.to_sym
183
-
184
- Kernel.require path unless components.exists?(name)
185
-
186
- self
187
- end
188
-
189
- def require_boot_file(name)
190
- boot_file = find_boot_file(name)
191
-
192
- Kernel.require boot_file if boot_file
193
- end
194
-
195
- def find_boot_file(name)
196
- boot_files.detect { |file| File.basename(file, RB_EXT) == name.to_s }
197
- end
198
- end
199
- end
200
- end