dry-system 0.18.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+
5
+ module Dry
6
+ module System
7
+ module Config
8
+ # @api public
9
+ class ComponentDir
10
+ include Dry::Configurable
11
+
12
+ # @!group Settings
13
+
14
+ # @!method auto_register=(policy)
15
+ #
16
+ # Sets the auto-registration policy for the component dir.
17
+ #
18
+ # This may be a simple boolean to enable or disable auto-registration for all
19
+ # components, or a proc accepting a {Dry::System::Component} and returning a
20
+ # boolean to configure auto-registration on a per-component basis
21
+ #
22
+ # Defaults to `true`.
23
+ #
24
+ # @param policy [Boolean, Proc]
25
+ # @return [Boolean, Proc]
26
+ #
27
+ # @example
28
+ # dir.auto_register = false
29
+ #
30
+ # @example
31
+ # dir.auto_register = proc do |component|
32
+ # !component.identifier.start_with?("entities")
33
+ # end
34
+ #
35
+ # @see auto_register
36
+ # @see Component
37
+ # @api public
38
+ #
39
+ # @!method auto_register
40
+ #
41
+ # Returns the configured auto-registration policy.
42
+ #
43
+ # @return [Boolean, Proc] the configured policy
44
+ #
45
+ # @see auto_register=
46
+ # @api public
47
+ setting :auto_register, default: true
48
+
49
+ # @!method instance=(instance_proc)
50
+ #
51
+ # Sets a proc used to return the instance of any component within the component
52
+ # dir.
53
+ #
54
+ # This proc should accept a {Dry::System::Component} and return the object to
55
+ # serve as the component's instance.
56
+ #
57
+ # When you provide an instance proc, it will be used in preference to the
58
+ # {loader} (either the default loader or an explicitly configured one). Provide
59
+ # an instance proc when you want a simple way to customize the instance for
60
+ # certain components. For complete control, provide a replacement loader via
61
+ # {loader=}.
62
+ #
63
+ # Defaults to `nil`.
64
+ #
65
+ # @param instance_proc [Proc, nil]
66
+ # @return [Proc]
67
+ #
68
+ # @example
69
+ # dir.instance = proc do |component|
70
+ # if component.key.match?(/workers\./)
71
+ # # Register classes for jobs
72
+ # component.loader.constant(component)
73
+ # else
74
+ # # Otherwise register regular instances per default loader
75
+ # component.loader.call(component)
76
+ # end
77
+ # end
78
+ #
79
+ # @see Component, Loader
80
+ # @api public
81
+ #
82
+ # @!method instance
83
+ #
84
+ # Returns the configured instance proc.
85
+ #
86
+ # @return [Proc, nil]
87
+ #
88
+ # @see instance=
89
+ # @api public
90
+ setting :instance
91
+
92
+ # @!method loader=(loader)
93
+ #
94
+ # Sets the loader to use when registering components from the dir in the
95
+ # container.
96
+ #
97
+ # Defaults to `Dry::System::Loader`.
98
+ #
99
+ # When using an autoloader like Zeitwerk, consider using
100
+ # `Dry::System::Loader::Autoloading`
101
+ #
102
+ # @param loader [#call] the loader
103
+ # @return [#call] the configured loader
104
+ #
105
+ # @see loader
106
+ # @see Loader
107
+ # @see Loader::Autoloading
108
+ # @api public
109
+ #
110
+ # @!method loader
111
+ #
112
+ # Returns the configured loader.
113
+ #
114
+ # @return [#call]
115
+ #
116
+ # @see loader=
117
+ # @api public
118
+ setting :loader, default: Dry::System::Loader
119
+
120
+ # @!method memoize=(policy)
121
+ #
122
+ # Sets whether to memoize components from the dir when registered in the
123
+ # container.
124
+ #
125
+ # This may be a simple boolean to enable or disable memoization for all
126
+ # components, or a proc accepting a `Dry::Sytem::Component` and returning a
127
+ # boolean to configure memoization on a per-component basis
128
+ #
129
+ # Defaults to `false`.
130
+ #
131
+ # @param policy [Boolean, Proc]
132
+ # @return [Boolean, Proc] the configured memoization policy
133
+ #
134
+ # @example
135
+ # dir.memoize = true
136
+ #
137
+ # @example
138
+ # dir.memoize = proc do |component|
139
+ # !component.identifier.start_with?("providers")
140
+ # end
141
+ #
142
+ # @see memoize
143
+ # @see Component
144
+ # @api public
145
+ #
146
+ # @!method memoize
147
+ #
148
+ # Returns the configured memoization policy.
149
+ #
150
+ # @return [Boolean, Proc] the configured memoization policy
151
+ #
152
+ # @see memoize=
153
+ # @api public
154
+ setting :memoize, default: false
155
+
156
+ # @!method namespaces
157
+ #
158
+ # Returns the configured namespaces for the component dir.
159
+ #
160
+ # Allows namespaces to added on the returned object via {Namespaces#add}.
161
+ #
162
+ # @return [Namespaces] the namespaces
163
+ #
164
+ # @see Namespaces#add
165
+ # @api public
166
+ setting :namespaces, default: Namespaces.new, cloneable: true
167
+
168
+ # @!method add_to_load_path=(policy)
169
+ #
170
+ # Sets whether the dir should be added to the `$LOAD_PATH` after the container
171
+ # is configured.
172
+ #
173
+ # Defaults to `true`. This may need to be set to `false` when using a class
174
+ # autoloading system.
175
+ #
176
+ # @param policy [Boolean]
177
+ # @return [Boolean]
178
+ #
179
+ # @see add_to_load_path
180
+ # @see Container.configure
181
+ # @api public
182
+ #
183
+ # @!method add_to_load_path
184
+ #
185
+ # Returns the configured value.
186
+ #
187
+ # @return [Boolean]
188
+ #
189
+ # @see add_to_load_path=
190
+ # @api public
191
+ setting :add_to_load_path, default: true
192
+
193
+ # @!endgroup
194
+
195
+ # Returns the component dir path, relative to the configured container root
196
+ #
197
+ # @return [String] the path
198
+ attr_reader :path
199
+
200
+ # @api public
201
+ def initialize(path)
202
+ super()
203
+ @path = path
204
+ yield self if block_given?
205
+ end
206
+
207
+ # @api private
208
+ def auto_register?
209
+ !!config.auto_register
210
+ end
211
+
212
+ private
213
+
214
+ def method_missing(name, *args, &block)
215
+ if config.respond_to?(name)
216
+ config.public_send(name, *args, &block)
217
+ else
218
+ super
219
+ end
220
+ end
221
+
222
+ def respond_to_missing?(name, include_all = false)
223
+ config.respond_to?(name) || super
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+ require "dry/system/errors"
5
+
6
+ module Dry
7
+ module System
8
+ module Config
9
+ # The configured component dirs for a container
10
+ #
11
+ # @api public
12
+ class ComponentDirs
13
+ # @!group Settings
14
+
15
+ # @!method auto_register=(value)
16
+ #
17
+ # Sets a default `auto_register` for all added component dirs
18
+ #
19
+ # @see ComponentDir.auto_register=
20
+ # @see auto_register
21
+ #
22
+ # @!method auto_register
23
+ #
24
+ # Returns the configured default `auto_register`
25
+ #
26
+ # @see auto_register=
27
+
28
+ # @!method instance=(value)
29
+ #
30
+ # Sets a default `instance` for all added component dirs
31
+ #
32
+ # @see ComponentDir.instance=
33
+ # @see auto_register
34
+ #
35
+ # @!method auto_register
36
+ #
37
+ # Returns the configured default `instance`
38
+ #
39
+ # @see instance=
40
+
41
+ # @!method loader=(value)
42
+ #
43
+ # Sets a default `loader` value for all added component dirs
44
+ #
45
+ # @see ComponentDir.loader=
46
+ # @see loader
47
+ #
48
+ # @!method loader
49
+ #
50
+ # Returns the configured default `loader`
51
+ #
52
+ # @see loader=
53
+
54
+ # @!method memoize=(value)
55
+ #
56
+ # Sets a default `memoize` value for all added component dirs
57
+ #
58
+ # @see ComponentDir.memoize=
59
+ # @see memoize
60
+ #
61
+ # @!method memoize
62
+ #
63
+ # Returns the configured default `memoize`
64
+ #
65
+ # @see memoize=
66
+
67
+ # rubocop:disable Layout/LineLength
68
+
69
+ # @!method namespaces
70
+ #
71
+ # Returns the default configured namespaces for all added component dirs
72
+ #
73
+ # Allows namespaces to added on the returned object via {Dry::System::Config::Namespaces#add}.
74
+ #
75
+ # @see Dry::System::Config::Namespaces#add
76
+ #
77
+ # @return [Namespaces] the namespaces
78
+
79
+ # @!method add_to_load_path=(value)
80
+ #
81
+ # Sets a default `add_to_load_path` value for all added component dirs
82
+ #
83
+ # @see ComponentDir.add_to_load_path=
84
+ # @see add_to_load_path
85
+ #
86
+ # @!method add_to_load_path
87
+ #
88
+ # Returns the configured default `add_to_load_path`
89
+ #
90
+ # @see add_to_load_path=
91
+
92
+ # rubocop:enable Layout/LineLength
93
+
94
+ # @!endgroup
95
+
96
+ # A ComponentDir for configuring the default values to apply to all added
97
+ # component dirs
98
+ #
99
+ # @see #method_missing
100
+ # @api private
101
+ attr_reader :defaults
102
+
103
+ # Creates a new component dirs
104
+ #
105
+ # @api private
106
+ def initialize
107
+ @dirs = {}
108
+ @defaults = ComponentDir.new(nil)
109
+ end
110
+
111
+ # @api private
112
+ def initialize_copy(source)
113
+ @dirs = source.dirs.map { |path, dir| [path, dir.dup] }.to_h
114
+ @defaults = source.defaults.dup
115
+ end
116
+
117
+ # Returns and optionally yields a previously added component dir
118
+ #
119
+ # @param path [String] the path for the component dir
120
+ # @yieldparam dir [ComponentDir] the component dir
121
+ #
122
+ # @return [ComponentDir] the component dir
123
+ #
124
+ # @api public
125
+ def dir(path)
126
+ dirs[path].tap do |dir|
127
+ # Defaults can be (re-)applied first, since the dir has already been added
128
+ apply_defaults_to_dir(dir) if dir
129
+ yield dir if block_given?
130
+ end
131
+ end
132
+ alias_method :[], :dir
133
+
134
+ # @overload add(path)
135
+ # Adds and configures a component dir for the given path
136
+ #
137
+ # @param path [String] the path for the component dir, relative to the configured
138
+ # container root
139
+ # @yieldparam dir [ComponentDir] the component dir to configure
140
+ #
141
+ # @return [ComponentDir] the added component dir
142
+ #
143
+ # @example
144
+ # component_dirs.add "lib" do |dir|
145
+ # dir.default_namespace = "my_app"
146
+ # end
147
+ #
148
+ # @see ComponentDir
149
+ # @api public
150
+ #
151
+ # @overload add(dir)
152
+ # Adds a configured component dir
153
+ #
154
+ # @param dir [ComponentDir] the configured component dir
155
+ #
156
+ # @return [ComponentDir] the added component dir
157
+ #
158
+ # @example
159
+ # dir = Dry::System::ComponentDir.new("lib")
160
+ # component_dirs.add dir
161
+ #
162
+ # @see ComponentDir
163
+ # @api public
164
+ def add(path_or_dir)
165
+ path, dir_to_add = path_and_dir(path_or_dir)
166
+
167
+ raise ComponentDirAlreadyAddedError, path if dirs.key?(path)
168
+
169
+ dirs[path] = dir_to_add.tap do |dir|
170
+ # Defaults must be applied after yielding, since the dir is being newly added,
171
+ # and must have its configuration fully in place before we can know which
172
+ # defaults to apply
173
+ yield dir if path_or_dir == path && block_given?
174
+ apply_defaults_to_dir(dir)
175
+ end
176
+ end
177
+
178
+ # Deletes and returns a previously added component dir
179
+ #
180
+ # @param path [String] the path for the component dir
181
+ #
182
+ # @return [ComponentDir] the removed component dir
183
+ #
184
+ # @api public
185
+ def delete(path)
186
+ dirs.delete(path)
187
+ end
188
+
189
+ # Returns the paths of the component dirs
190
+ #
191
+ # @return [Array<String>] the component dir paths
192
+ #
193
+ # @api public
194
+ def paths
195
+ dirs.keys
196
+ end
197
+
198
+ # Returns the count of component dirs
199
+ #
200
+ # @return [Integer]
201
+ #
202
+ # @api public
203
+ def length
204
+ dirs.length
205
+ end
206
+ alias_method :size, :length
207
+
208
+ # Returns the added component dirs, with default settings applied
209
+ #
210
+ # @return [Array<ComponentDir>]
211
+ #
212
+ # @api public
213
+ def to_a
214
+ dirs.each { |_, dir| apply_defaults_to_dir(dir) }
215
+ dirs.values
216
+ end
217
+
218
+ # Calls the given block once for each added component dir, passing the dir as an
219
+ # argument.
220
+ #
221
+ # @yieldparam dir [ComponentDir] the yielded component dir
222
+ #
223
+ # @api public
224
+ def each(&block)
225
+ to_a.each(&block)
226
+ end
227
+
228
+ protected
229
+
230
+ # Returns the hash of component dirs, keyed by their paths
231
+ #
232
+ # Recently changed default configuration may not be applied to these dirs. Use
233
+ # #to_a or #each to access dirs with default configuration fully applied.
234
+ #
235
+ # This method exists to encapsulate the instance variable and to serve the needs
236
+ # of #initialize_copy
237
+ #
238
+ # @return [Hash{String => ComponentDir}]
239
+ #
240
+ # @api private
241
+ attr_reader :dirs
242
+
243
+ private
244
+
245
+ # Converts a path string or pre-built component dir into a path and dir tuple
246
+ #
247
+ # @param path_or_dir [String,ComponentDir]
248
+ #
249
+ # @return [Array<(String, ComponentDir)>]
250
+ #
251
+ # @see #add
252
+ def path_and_dir(path_or_dir)
253
+ if path_or_dir.is_a?(ComponentDir)
254
+ dir = path_or_dir
255
+ [dir.path, dir]
256
+ else
257
+ path = path_or_dir
258
+ [path, ComponentDir.new(path)]
259
+ end
260
+ end
261
+
262
+ # Applies default settings to a component dir. This is run every time the dirs are
263
+ # accessed to ensure defaults are applied regardless of when new component dirs
264
+ # are added. This method must be idempotent.
265
+ #
266
+ # @return [void]
267
+ def apply_defaults_to_dir(dir)
268
+ defaults.config.values.each do |key, _|
269
+ if defaults.configured?(key) && !dir.configured?(key)
270
+ dir.public_send(:"#{key}=", defaults.public_send(key).dup)
271
+ end
272
+ end
273
+ end
274
+
275
+ def method_missing(name, *args, &block)
276
+ if defaults.respond_to?(name)
277
+ defaults.public_send(name, *args, &block)
278
+ else
279
+ super
280
+ end
281
+ end
282
+
283
+ def respond_to_missing?(name, include_all = false)
284
+ defaults.respond_to?(name) || super
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+
5
+ module Dry
6
+ module System
7
+ module Config
8
+ # A configured namespace for a component dir
9
+ #
10
+ # Namespaces consist of three elements:
11
+ #
12
+ # - The `path` within the component dir to which its namespace rules should apply.
13
+ # - A `key`, which determines the leading part of the key used to register
14
+ # each component in the container.
15
+ # - A `const`, which is the Ruby namespace expected to contain the class constants
16
+ # defined within each component's source file. This value is expected to be an
17
+ # "underscored" string, intended to be run through the configured inflector to be
18
+ # converted into a real constant (e.g. `"foo_bar/baz"` will become `FooBar::Baz`)
19
+ #
20
+ # Namespaces are added and configured for a component dir via {Namespaces#add}.
21
+ #
22
+ # @see Namespaces#add
23
+ #
24
+ # @api public
25
+ class Namespace
26
+ ROOT_PATH = nil
27
+
28
+ include Dry::Equalizer(:path, :key, :const)
29
+
30
+ # @api public
31
+ attr_reader :path
32
+
33
+ # @api public
34
+ attr_reader :key
35
+
36
+ # @api public
37
+ attr_reader :const
38
+
39
+ # Returns a namespace configured to serve as the default root namespace for a
40
+ # component dir, ensuring that all code within the dir can be loaded, regardless
41
+ # of any other explictly configured namespaces
42
+ #
43
+ # @return [Namespace] the root namespace
44
+ #
45
+ # @api private
46
+ def self.default_root
47
+ new(
48
+ path: ROOT_PATH,
49
+ key: nil,
50
+ const: nil
51
+ )
52
+ end
53
+
54
+ # @api private
55
+ def initialize(path:, key:, const:)
56
+ @path = path
57
+ # Default keys (i.e. when the user does not explicitly provide one) for non-root
58
+ # paths will include path separators, which we must convert into key separators
59
+ @key = key && key == path ? key.gsub(PATH_SEPARATOR, KEY_SEPARATOR) : key
60
+ @const = const
61
+ end
62
+
63
+ # @api public
64
+ def root?
65
+ path == ROOT_PATH
66
+ end
67
+
68
+ # @api public
69
+ def path?
70
+ !root?
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end