dry-system 0.19.2 → 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 (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
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/system/constants"
5
+
6
+ module Dry
7
+ module System
8
+ module Config
9
+ # A configured namespace for a component dir
10
+ #
11
+ # Namespaces consist of three elements:
12
+ #
13
+ # - The `path` within the component dir to which its namespace rules should apply.
14
+ # - A `key`, which determines the leading part of the key used to register
15
+ # each component in the container.
16
+ # - A `const`, which is the Ruby namespace expected to contain the class constants
17
+ # defined within each component's source file. This value is expected to be an
18
+ # "underscored" string, intended to be run through the configured inflector to be
19
+ # converted into a real constant (e.g. `"foo_bar/baz"` will become `FooBar::Baz`)
20
+ #
21
+ # Namespaces are added and configured for a component dir via {Namespaces#add}.
22
+ #
23
+ # @see Namespaces#add
24
+ #
25
+ # @api public
26
+ class Namespace
27
+ ROOT_PATH = nil
28
+
29
+ include Dry::Equalizer(:path, :key, :const)
30
+
31
+ # @api public
32
+ attr_reader :path
33
+
34
+ # @api public
35
+ attr_reader :key
36
+
37
+ # @api public
38
+ attr_reader :const
39
+
40
+ # Returns a namespace configured to serve as the default root namespace for a
41
+ # component dir, ensuring that all code within the dir can be loaded, regardless
42
+ # of any other explictly configured namespaces
43
+ #
44
+ # @return [Namespace] the root namespace
45
+ #
46
+ # @api private
47
+ def self.default_root
48
+ new(
49
+ path: ROOT_PATH,
50
+ key: nil,
51
+ const: nil
52
+ )
53
+ end
54
+
55
+ # @api private
56
+ def initialize(path:, key:, const:)
57
+ @path = path
58
+ # Default keys (i.e. when the user does not explicitly provide one) for non-root
59
+ # paths will include path separators, which we must convert into key separators
60
+ @key = key && key == path ? key.gsub(PATH_SEPARATOR, KEY_SEPARATOR) : key
61
+ @const = const
62
+ end
63
+
64
+ # @api public
65
+ def root?
66
+ path == ROOT_PATH
67
+ end
68
+
69
+ # @api public
70
+ def path?
71
+ !root?
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/deprecations"
4
+ require "dry/system/errors"
5
+ require_relative "namespace"
6
+
7
+ module Dry
8
+ module System
9
+ module Config
10
+ # The configured namespaces for a ComponentDir
11
+ #
12
+ # @see Config::ComponentDir#namespaces
13
+ #
14
+ # @api private
15
+ class Namespaces
16
+ # @api private
17
+ attr_reader :namespaces
18
+
19
+ # @api private
20
+ def initialize
21
+ @namespaces = {}
22
+ end
23
+
24
+ # @api private
25
+ def initialize_copy(source)
26
+ super
27
+ @namespaces = source.namespaces.dup
28
+ end
29
+
30
+ # Returns the namespace configured for the path, or nil if no such namespace has
31
+ # been configured
32
+ #
33
+ # @return [Namespace, nil] the namespace, if configured
34
+ #
35
+ # @api public
36
+ def namespace(path)
37
+ namespaces[path]
38
+ end
39
+ alias_method :[], :namespace
40
+
41
+ # Returns the namespace configured for the root path, or nil if the root namespace
42
+ # has not been configured
43
+ #
44
+ # @return [Namespace, nil] the root namespace, if configured
45
+ #
46
+ # @api public
47
+ def root(**options)
48
+ if options.any?
49
+ Dry::Core::Deprecations.announce(
50
+ "Dry::System::Config::Namespaces#root (with arguments)",
51
+ "Use `#add_root(key: nil, const: nil)` instead",
52
+ tag: "dry-system",
53
+ uplevel: 1
54
+ )
55
+
56
+ add_root(**options)
57
+ return
58
+ end
59
+
60
+ namespaces[Namespace::ROOT_PATH]
61
+ end
62
+
63
+ # rubocop:disable Layout/LineLength
64
+
65
+ # Adds a component dir namespace
66
+ #
67
+ # A namespace encompasses a given sub-directory of the component dir, and
68
+ # determines (1) the leading segments of its components' registered identifiers,
69
+ # and (2) the expected constant namespace of their class constants.
70
+ #
71
+ # A namespace for a path can only be added once.
72
+ #
73
+ # @example Adding a namespace with top-level identifiers
74
+ # # Components defined within admin/ (e.g. admin/my_component.rb) will be:
75
+ # #
76
+ # # - Registered with top-level identifiers ("my_component")
77
+ # # - Expected to have constants in `Admin`, matching the namespace's path (Admin::MyComponent)
78
+ #
79
+ # namespaces.add "admin", key: nil
80
+ #
81
+ # @example Adding a namespace with top-level class constants
82
+ # # Components defined within adapters/ (e.g. adapters/my_adapter.rb) will be:
83
+ # #
84
+ # # - Registered with leading identifiers matching the namespace's path ("adapters.my_adapter")
85
+ # # - Expected to have top-level constants (::MyAdapter)
86
+ #
87
+ # namespaces.add "adapters", const: nil
88
+ #
89
+ # @example Adding a namespace with distinct identifiers and class constants
90
+ # # Components defined within `bananas/` (e.g. bananas/banana_split.rb) will be:
91
+ # #
92
+ # # - Registered with the given leading identifier ("desserts.banana_split")
93
+ # # - Expected to have constants within the given namespace (EatMe::Now::BananaSplit)
94
+ #
95
+ # namespaces.add "bananas", key: "desserts", const: "eat_me/now"
96
+ #
97
+ # @param path [String] the path to the sub-directory of source files to which this
98
+ # namespace should apply, relative to the component dir
99
+ # @param key [String, nil] the leading namespace to apply to the container keys
100
+ # for the components. Set `nil` for the keys to be top-level.
101
+ # @param const [String, nil] the Ruby constant namespace to expect for constants
102
+ # defined within the components. This should be provided in underscored string
103
+ # form, e.g. "hello_there/world" for a Ruby constant of `HelloThere::World`. Set
104
+ # `nil` for the constants to be top-level.
105
+ #
106
+ # @return [Namespace] the added namespace
107
+ #
108
+ # @see Namespace
109
+ #
110
+ # @api public
111
+ def add(path, key: path, const: path)
112
+ raise NamespaceAlreadyAddedError, path if namespaces.key?(path)
113
+
114
+ namespaces[path] = Namespace.new(path: path, key: key, const: const)
115
+ end
116
+
117
+ # rubocop:enable Layout/LineLength
118
+
119
+ # Adds a root component dir namespace
120
+ #
121
+ # @see #add
122
+ #
123
+ # @api public
124
+ def add_root(key: nil, const: nil)
125
+ add(Namespace::ROOT_PATH, key: key, const: const)
126
+ end
127
+
128
+ # Deletes the configured namespace for the given path and returns the namespace
129
+ #
130
+ # If no namespace was previously configured for the given path, returns nil
131
+ #
132
+ # @param path [String] the path for the namespace
133
+ #
134
+ # @return [Namespace, nil]
135
+ #
136
+ # @api public
137
+ def delete(path)
138
+ namespaces.delete(path)
139
+ end
140
+
141
+ # Deletes the configured root namespace and returns the namespace
142
+ #
143
+ # If no root namespace was previously configured, returns nil
144
+ #
145
+ # @return [Namespace, nil]
146
+ #
147
+ # @api public
148
+ def delete_root
149
+ delete(Namespace::ROOT_PATH)
150
+ end
151
+
152
+ # Returns the paths of the configured namespaces
153
+ #
154
+ # @return [Array<String,nil>] the namespace paths, with nil representing the root
155
+ # namespace
156
+ #
157
+ # @api public
158
+ def paths
159
+ namespaces.keys
160
+ end
161
+
162
+ # Returns the count of configured namespaces
163
+ #
164
+ # @return [Integer]
165
+ #
166
+ # @api public
167
+ def length
168
+ namespaces.length
169
+ end
170
+ alias_method :size, :length
171
+
172
+ # Returns true if there are no configured namespaces
173
+ #
174
+ # @return [Boolean]
175
+ #
176
+ # @api public
177
+ def empty?
178
+ namespaces.empty?
179
+ end
180
+
181
+ # Returns the configured namespaces as an array
182
+ #
183
+ # Adds a default root namespace to the end of the array if one was not added
184
+ # explicitly. This fallback ensures that all components in the component dir can
185
+ # be loaded.
186
+ #
187
+ # @return [Array<Namespace>] the namespaces
188
+ #
189
+ # @api public
190
+ def to_a
191
+ namespaces.values.tap do |arr|
192
+ arr << Namespace.default_root unless arr.any?(&:root?)
193
+ end
194
+ end
195
+
196
+ # Calls the given block once for each configured namespace, passing the namespace
197
+ # as an argument.
198
+ #
199
+ # @yieldparam namespace [Namespace] the yielded namespace
200
+ #
201
+ # @api public
202
+ def each(&block)
203
+ to_a.each(&block)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -8,8 +8,8 @@ module Dry
8
8
 
9
9
  RB_EXT = ".rb"
10
10
  RB_GLOB = "*.rb"
11
- PATH_SEPARATOR = "/"
12
- DEFAULT_SEPARATOR = "."
11
+ PATH_SEPARATOR = File::SEPARATOR
12
+ KEY_SEPARATOR = "."
13
13
  WORD_REGEX = /\w+/.freeze
14
14
  end
15
15
  end