dry-system 0.19.2 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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