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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +678 -0
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-system.gemspec +18 -21
- data/lib/dry/system/auto_registrar.rb +9 -64
- data/lib/dry/system/component.rb +124 -104
- data/lib/dry/system/component_dir.rb +171 -0
- data/lib/dry/system/config/component_dir.rb +228 -0
- data/lib/dry/system/config/component_dirs.rb +289 -0
- data/lib/dry/system/config/namespace.rb +75 -0
- data/lib/dry/system/config/namespaces.rb +196 -0
- data/lib/dry/system/constants.rb +2 -4
- data/lib/dry/system/container.rb +305 -345
- data/lib/dry/system/errors.rb +73 -56
- data/lib/dry/system/identifier.rb +176 -0
- data/lib/dry/system/importer.rb +89 -12
- data/lib/dry/system/indirect_component.rb +63 -0
- data/lib/dry/system/loader/autoloading.rb +24 -0
- data/lib/dry/system/loader.rb +49 -41
- data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +13 -14
- data/lib/dry/system/plugins/bootsnap.rb +3 -2
- data/lib/dry/system/plugins/dependency_graph/strategies.rb +38 -2
- data/lib/dry/system/plugins/dependency_graph.rb +25 -21
- data/lib/dry/system/plugins/env.rb +3 -2
- data/lib/dry/system/plugins/logging.rb +9 -8
- data/lib/dry/system/plugins/monitoring.rb +1 -2
- data/lib/dry/system/plugins/notifications.rb +1 -1
- data/lib/dry/system/plugins/plugin.rb +61 -0
- data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
- data/lib/dry/system/plugins/zeitwerk.rb +109 -0
- data/lib/dry/system/plugins.rb +5 -73
- data/lib/dry/system/provider/source.rb +276 -0
- data/lib/dry/system/provider/source_dsl.rb +55 -0
- data/lib/dry/system/provider.rb +261 -23
- data/lib/dry/system/provider_registrar.rb +251 -0
- data/lib/dry/system/provider_source_registry.rb +56 -0
- data/lib/dry/system/provider_sources/settings/config.rb +73 -0
- data/lib/dry/system/provider_sources/settings/loader.rb +44 -0
- data/lib/dry/system/provider_sources/settings.rb +40 -0
- data/lib/dry/system/provider_sources.rb +5 -0
- data/lib/dry/system/stubs.rb +6 -2
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +35 -13
- metadata +48 -97
- data/lib/dry/system/auto_registrar/configuration.rb +0 -43
- data/lib/dry/system/booter/component_registry.rb +0 -35
- data/lib/dry/system/booter.rb +0 -181
- data/lib/dry/system/components/bootable.rb +0 -289
- data/lib/dry/system/components/config.rb +0 -35
- data/lib/dry/system/components.rb +0 -8
- data/lib/dry/system/lifecycle.rb +0 -135
- data/lib/dry/system/provider_registry.rb +0 -27
- data/lib/dry/system/settings/file_loader.rb +0 -30
- data/lib/dry/system/settings/file_parser.rb +0 -51
- data/lib/dry/system/settings.rb +0 -67
- data/lib/dry/system/system_components/settings.rb +0 -11
@@ -0,0 +1,251 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
require "dry/system/errors"
|
6
|
+
require "dry/system/constants"
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module System
|
10
|
+
# Default provider registrar implementation
|
11
|
+
#
|
12
|
+
# This is currently configured by default for every Dry::System::Container. The
|
13
|
+
# provider registrar is responsible for loading provider files and exposing an API for
|
14
|
+
# running the provider lifecycle steps.
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
class ProviderRegistrar
|
18
|
+
# @api private
|
19
|
+
attr_reader :providers
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
attr_reader :container
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def initialize(container)
|
26
|
+
@providers = {}
|
27
|
+
@container = container
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def freeze
|
32
|
+
providers.freeze
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
37
|
+
|
38
|
+
# @see Container.register_provider
|
39
|
+
# @api private
|
40
|
+
def register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
|
41
|
+
raise ProviderAlreadyRegisteredError, name if providers.key?(name)
|
42
|
+
|
43
|
+
if from && source.is_a?(Class)
|
44
|
+
raise ArgumentError, "You must supply a block when using a provider source"
|
45
|
+
end
|
46
|
+
|
47
|
+
if block && source.is_a?(Class)
|
48
|
+
raise ArgumentError, "You must supply only a `source:` option or a block, not both"
|
49
|
+
end
|
50
|
+
|
51
|
+
return self unless binding.local_variable_get(:if)
|
52
|
+
|
53
|
+
provider =
|
54
|
+
if from
|
55
|
+
build_provider_from_source(
|
56
|
+
name,
|
57
|
+
namespace: namespace,
|
58
|
+
source: source || name,
|
59
|
+
group: from,
|
60
|
+
&block
|
61
|
+
)
|
62
|
+
else
|
63
|
+
build_provider(name, namespace: namespace, source: source, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
providers[provider.name] = provider
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
72
|
+
|
73
|
+
# Returns a provider for the given name, if it has already been loaded
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def [](provider_name)
|
77
|
+
providers[provider_name]
|
78
|
+
end
|
79
|
+
alias_method :provider, :[]
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
def key?(provider_name)
|
83
|
+
providers.key?(provider_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns a provider if it can be found or loaded, otherwise nil
|
87
|
+
#
|
88
|
+
# @return [Dry::System::Provider, nil]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def find_and_load_provider(name)
|
92
|
+
name = name.to_sym
|
93
|
+
|
94
|
+
if (provider = providers[name])
|
95
|
+
return provider
|
96
|
+
end
|
97
|
+
|
98
|
+
return if finalized?
|
99
|
+
|
100
|
+
require_provider_file(name)
|
101
|
+
|
102
|
+
providers[name]
|
103
|
+
end
|
104
|
+
|
105
|
+
# @api private
|
106
|
+
def start_provider_dependency(component)
|
107
|
+
if (provider = find_and_load_provider(component.root_key))
|
108
|
+
provider.start
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns all provider files within the configured provider_paths.
|
113
|
+
#
|
114
|
+
# Searches for files in the order of the configured provider_paths. In the case of multiple
|
115
|
+
# identically-named boot files within different provider_paths, the file found first will be
|
116
|
+
# returned, and other matching files will be discarded.
|
117
|
+
#
|
118
|
+
# This method is public to allow other tools extending dry-system (like dry-rails)
|
119
|
+
# to access a canonical list of real, in-use provider files.
|
120
|
+
#
|
121
|
+
# @see Container.provider_paths
|
122
|
+
#
|
123
|
+
# @return [Array<Pathname>]
|
124
|
+
# @api public
|
125
|
+
def provider_files
|
126
|
+
@provider_files ||= provider_paths.each_with_object([[], []]) { |path, (provider_files, loaded)| # rubocop:disable Layout/LineLength
|
127
|
+
files = Dir["#{path}/#{RB_GLOB}"].sort
|
128
|
+
|
129
|
+
files.each do |file|
|
130
|
+
basename = File.basename(file)
|
131
|
+
|
132
|
+
unless loaded.include?(basename)
|
133
|
+
provider_files << Pathname(file)
|
134
|
+
loaded << basename
|
135
|
+
end
|
136
|
+
end
|
137
|
+
}.first
|
138
|
+
end
|
139
|
+
|
140
|
+
# @api private
|
141
|
+
def finalize!
|
142
|
+
provider_files.each do |path|
|
143
|
+
load_provider(path)
|
144
|
+
end
|
145
|
+
|
146
|
+
providers.each_value(&:start)
|
147
|
+
|
148
|
+
freeze
|
149
|
+
end
|
150
|
+
|
151
|
+
# @!method finalized?
|
152
|
+
# Returns true if the booter has been finalized
|
153
|
+
#
|
154
|
+
# @return [Boolean]
|
155
|
+
# @api private
|
156
|
+
alias_method :finalized?, :frozen?
|
157
|
+
|
158
|
+
# @api private
|
159
|
+
def shutdown
|
160
|
+
providers.each_value(&:stop)
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# @api private
|
165
|
+
def prepare(provider_name)
|
166
|
+
with_provider(provider_name, &:prepare)
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
# @api private
|
171
|
+
def start(provider_name)
|
172
|
+
with_provider(provider_name, &:start)
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# @api private
|
177
|
+
def stop(provider_name)
|
178
|
+
with_provider(provider_name, &:stop)
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# @api private
|
185
|
+
def provider_paths
|
186
|
+
provider_dirs = container.config.provider_dirs
|
187
|
+
|
188
|
+
provider_dirs.map { |dir|
|
189
|
+
dir = Pathname(dir)
|
190
|
+
|
191
|
+
if dir.relative?
|
192
|
+
container.root.join(dir)
|
193
|
+
else
|
194
|
+
dir
|
195
|
+
end
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
def build_provider(name, namespace:, source: nil, &block)
|
200
|
+
source_class = source || Provider::Source.for(name: name, &block)
|
201
|
+
|
202
|
+
Provider.new(
|
203
|
+
name: name,
|
204
|
+
namespace: namespace,
|
205
|
+
target_container: container,
|
206
|
+
source_class: source_class
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
def build_provider_from_source(name, source:, group:, namespace:, &block)
|
211
|
+
source_class = System.provider_sources.resolve(name: source, group: group)
|
212
|
+
|
213
|
+
Provider.new(
|
214
|
+
name: name,
|
215
|
+
namespace: namespace,
|
216
|
+
target_container: container,
|
217
|
+
source_class: source_class,
|
218
|
+
&block
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
def with_provider(provider_name)
|
223
|
+
require_provider_file(provider_name) unless providers.key?(provider_name)
|
224
|
+
|
225
|
+
provider = providers[provider_name]
|
226
|
+
|
227
|
+
raise ProviderNotFoundError, provider_name unless provider
|
228
|
+
|
229
|
+
yield(provider)
|
230
|
+
end
|
231
|
+
|
232
|
+
def load_provider(path)
|
233
|
+
name = Pathname(path).basename(RB_EXT).to_s.to_sym
|
234
|
+
|
235
|
+
Kernel.require path unless providers.key?(name)
|
236
|
+
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def require_provider_file(name)
|
241
|
+
provider_file = find_provider_file(name)
|
242
|
+
|
243
|
+
Kernel.require provider_file if provider_file
|
244
|
+
end
|
245
|
+
|
246
|
+
def find_provider_file(name)
|
247
|
+
provider_files.detect { |file| File.basename(file, RB_EXT) == name.to_s }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/system/constants"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module System
|
7
|
+
# @api private
|
8
|
+
class ProviderSourceRegistry
|
9
|
+
attr_reader :sources
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@sources = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_sources(path)
|
16
|
+
Dir[File.join(path, "**/#{RB_GLOB}")].sort.each do |file|
|
17
|
+
require file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(name:, group:, source:)
|
22
|
+
sources[key(name, group)] = source
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_from_block(name:, group:, &block)
|
26
|
+
register(
|
27
|
+
name: name,
|
28
|
+
group: group,
|
29
|
+
source: Provider::Source.for(
|
30
|
+
name: name,
|
31
|
+
group: group,
|
32
|
+
&block
|
33
|
+
)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve(name:, group:)
|
38
|
+
sources[key(name, group)].tap { |source|
|
39
|
+
unless source
|
40
|
+
raise ProviderSourceNotFoundError.new(
|
41
|
+
name: name,
|
42
|
+
group: group,
|
43
|
+
keys: sources.keys
|
44
|
+
)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def key(name, group)
|
52
|
+
{group: group, name: name}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module System
|
5
|
+
module ProviderSources
|
6
|
+
# @api private
|
7
|
+
module Settings
|
8
|
+
InvalidSettingsError = Class.new(ArgumentError) do
|
9
|
+
# @api private
|
10
|
+
def initialize(errors)
|
11
|
+
message = <<~STR
|
12
|
+
Could not load settings. The following settings were invalid:
|
13
|
+
|
14
|
+
#{setting_errors(errors).join("\n")}
|
15
|
+
STR
|
16
|
+
|
17
|
+
super(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def setting_errors(errors)
|
23
|
+
errors.sort_by { |k, _| k }.map { |key, error| "#{key}: #{error}" }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
class Config
|
29
|
+
# @api private
|
30
|
+
def self.load(root:, env:, loader: Loader)
|
31
|
+
loader = loader.new(root: root, env: env)
|
32
|
+
|
33
|
+
new.tap do |settings_obj|
|
34
|
+
errors = {}
|
35
|
+
|
36
|
+
settings.to_a.each do |setting|
|
37
|
+
value = loader[setting.name.to_s.upcase]
|
38
|
+
|
39
|
+
begin
|
40
|
+
if value
|
41
|
+
settings_obj.config.public_send(:"#{setting.name}=", value)
|
42
|
+
else
|
43
|
+
settings_obj.config[setting.name]
|
44
|
+
end
|
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
|
+
include Dry::Configurable
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def method_missing(name, *args, &block)
|
59
|
+
if config.respond_to?(name)
|
60
|
+
config.public_send(name, *args, &block)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to_missing?(name, include_all = false)
|
67
|
+
config.respond_to?(name, include_all) || super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module System
|
5
|
+
module ProviderSources
|
6
|
+
module Settings
|
7
|
+
# @api private
|
8
|
+
class Loader
|
9
|
+
# @api private
|
10
|
+
attr_reader :store
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def initialize(root:, env:, store: ENV)
|
14
|
+
@store = store
|
15
|
+
load_dotenv(root, env.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def [](key)
|
20
|
+
store[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def load_dotenv(root, env)
|
26
|
+
require "dotenv"
|
27
|
+
Dotenv.load(*dotenv_files(root, env)) if defined?(Dotenv)
|
28
|
+
rescue LoadError
|
29
|
+
# Do nothing if dotenv is unavailable
|
30
|
+
end
|
31
|
+
|
32
|
+
def dotenv_files(root, env)
|
33
|
+
[
|
34
|
+
File.join(root, ".env.#{env}.local"),
|
35
|
+
(File.join(root, ".env.local") unless env == :test),
|
36
|
+
File.join(root, ".env.#{env}"),
|
37
|
+
File.join(root, ".env")
|
38
|
+
].compact
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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 defined? @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
|
+
)
|
data/lib/dry/system/stubs.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/container/stub"
|
3
|
+
require "dry/core/container/stub"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module System
|
7
7
|
class Container
|
8
8
|
# @api private
|
9
9
|
module Stubs
|
10
|
-
|
10
|
+
# This overrides default finalize! just to disable automatic freezing
|
11
|
+
# of the container
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def finalize!(**, &block)
|
11
15
|
super(freeze: false, &block)
|
12
16
|
end
|
13
17
|
end
|
data/lib/dry/system/version.rb
CHANGED
data/lib/dry/system.rb
CHANGED
@@ -1,30 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "dry/
|
3
|
+
require "zeitwerk"
|
4
|
+
require "dry/core"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
module System
|
8
|
-
#
|
8
|
+
# @api private
|
9
|
+
def self.loader
|
10
|
+
@loader ||= Zeitwerk::Loader.new.tap do |loader|
|
11
|
+
root = File.expand_path("..", __dir__)
|
12
|
+
loader.tag = "dry-system"
|
13
|
+
loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-system.rb")
|
14
|
+
loader.push_dir(root)
|
15
|
+
loader.ignore(
|
16
|
+
"#{root}/dry-system.rb",
|
17
|
+
"#{root}/dry/system/{components,constants,errors,stubs,version}.rb"
|
18
|
+
)
|
19
|
+
loader.inflector.inflect("source_dsl" => "SourceDSL")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Registers the provider sources in the files under the given path
|
9
24
|
#
|
10
25
|
# @api public
|
11
|
-
def self.
|
12
|
-
|
13
|
-
providers[identifier].load_components
|
14
|
-
self
|
26
|
+
def self.register_provider_sources(path)
|
27
|
+
provider_sources.load_sources(path)
|
15
28
|
end
|
16
29
|
|
17
|
-
#
|
30
|
+
# Registers a provider source, which can be used as the basis for other providers
|
18
31
|
#
|
19
32
|
# @api public
|
20
|
-
def self.
|
21
|
-
|
22
|
-
|
33
|
+
def self.register_provider_source(name, group:, source: nil, &block)
|
34
|
+
if source && block
|
35
|
+
raise ArgumentError, "You must supply only a `source:` option or a block, not both"
|
36
|
+
end
|
37
|
+
|
38
|
+
if source
|
39
|
+
provider_sources.register(name: name, group: group, source: source)
|
40
|
+
else
|
41
|
+
provider_sources.register_from_block(name: name, group: group, &block)
|
42
|
+
end
|
23
43
|
end
|
24
44
|
|
25
45
|
# @api private
|
26
|
-
def self.
|
27
|
-
@
|
46
|
+
def self.provider_sources
|
47
|
+
@provider_sources ||= ProviderSourceRegistry.new
|
28
48
|
end
|
49
|
+
|
50
|
+
loader.setup
|
29
51
|
end
|
30
52
|
end
|