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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +472 -1
- data/LICENSE +1 -1
- data/README.md +4 -3
- data/dry-system.gemspec +16 -15
- data/lib/dry/system/auto_registrar.rb +1 -13
- data/lib/dry/system/component.rb +104 -47
- data/lib/dry/system/component_dir.rb +88 -47
- data/lib/dry/system/components.rb +8 -4
- data/lib/dry/system/config/component_dir.rb +141 -53
- data/lib/dry/system/config/component_dirs.rb +176 -70
- data/lib/dry/system/config/namespace.rb +76 -0
- data/lib/dry/system/config/namespaces.rb +208 -0
- data/lib/dry/system/constants.rb +2 -2
- data/lib/dry/system/container.rb +279 -201
- data/lib/dry/system/errors.rb +72 -61
- data/lib/dry/system/identifier.rb +99 -79
- data/lib/dry/system/importer.rb +83 -12
- data/lib/dry/system/indirect_component.rb +65 -0
- data/lib/dry/system/loader.rb +8 -4
- data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +12 -13
- data/lib/dry/system/plugins/bootsnap.rb +3 -2
- data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
- data/lib/dry/system/plugins/dependency_graph.rb +26 -20
- data/lib/dry/system/plugins/env.rb +3 -2
- data/lib/dry/system/plugins/logging.rb +9 -5
- data/lib/dry/system/plugins/monitoring.rb +1 -1
- data/lib/dry/system/plugins/notifications.rb +1 -1
- 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 +8 -7
- data/lib/dry/system/provider/source.rb +324 -0
- data/lib/dry/system/provider/source_dsl.rb +94 -0
- data/lib/dry/system/provider.rb +264 -24
- data/lib/dry/system/provider_registrar.rb +276 -0
- data/lib/dry/system/provider_source_registry.rb +70 -0
- data/lib/dry/system/provider_sources/settings/config.rb +86 -0
- data/lib/dry/system/provider_sources/settings/loader.rb +53 -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 +1 -1
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +45 -13
- metadata +25 -22
- data/lib/dry/system/booter/component_registry.rb +0 -35
- data/lib/dry/system/booter.rb +0 -200
- data/lib/dry/system/components/bootable.rb +0 -289
- data/lib/dry/system/components/config.rb +0 -35
- 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
data/lib/dry/system/errors.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/core/deprecations"
|
4
|
+
|
3
5
|
module Dry
|
4
6
|
module System
|
7
|
+
extend Dry::Core::Deprecations["dry-system"]
|
8
|
+
|
5
9
|
# Error raised when a component dir is added to configuration more than once
|
6
10
|
#
|
7
11
|
# @api public
|
@@ -11,64 +15,61 @@ module Dry
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
# Error raised when
|
15
|
-
# file
|
18
|
+
# Error raised when a configured component directory could not be found
|
16
19
|
#
|
17
20
|
# @api public
|
18
|
-
|
19
|
-
def initialize(
|
20
|
-
super("
|
21
|
+
ComponentDirNotFoundError = Class.new(StandardError) do
|
22
|
+
def initialize(dir)
|
23
|
+
super("Component dir '#{dir}' not found")
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
# Error raised when
|
27
|
+
# Error raised when a namespace for a component dir is added to configuration more
|
28
|
+
# than once
|
25
29
|
#
|
26
30
|
# @api public
|
27
|
-
|
28
|
-
def initialize(
|
29
|
-
|
30
|
-
Bootable component '#{component.identifier}' not found
|
31
|
-
STR
|
32
|
-
end
|
33
|
-
end
|
31
|
+
NamespaceAlreadyAddedError = Class.new(StandardError) do
|
32
|
+
def initialize(path)
|
33
|
+
path_label = path ? "path #{path.inspect}" : "root path"
|
34
34
|
|
35
|
-
|
36
|
-
#
|
37
|
-
# @api public
|
38
|
-
InvalidComponentError = Class.new(ArgumentError) do
|
39
|
-
def initialize(name, reason = nil)
|
40
|
-
super(
|
41
|
-
"Tried to create an invalid #{name.inspect} component - #{reason}"
|
42
|
-
)
|
35
|
+
super("Namespace for #{path_label} already added")
|
43
36
|
end
|
44
37
|
end
|
45
38
|
|
46
|
-
# Error raised when
|
39
|
+
# Error raised when attempting to register provider using a name that has already been
|
40
|
+
# registered
|
47
41
|
#
|
48
42
|
# @api public
|
49
|
-
|
50
|
-
def initialize(
|
51
|
-
super(
|
52
|
-
"component identifier +#{name}+ is invalid or boot file is missing"
|
53
|
-
)
|
43
|
+
ProviderAlreadyRegisteredError = Class.new(ArgumentError) do
|
44
|
+
def initialize(provider_name)
|
45
|
+
super("Provider #{provider_name.inspect} has already been registered")
|
54
46
|
end
|
55
47
|
end
|
48
|
+
DuplicatedComponentKeyError = ProviderAlreadyRegisteredError
|
49
|
+
deprecate_constant :DuplicatedComponentKeyError
|
56
50
|
|
57
|
-
# Error raised when
|
51
|
+
# Error raised when a named provider could not be found
|
58
52
|
#
|
59
53
|
# @api public
|
60
|
-
|
54
|
+
ProviderNotFoundError = Class.new(ArgumentError) do
|
61
55
|
def initialize(name)
|
62
|
-
super("
|
56
|
+
super("Provider #{name.inspect} not found")
|
63
57
|
end
|
64
58
|
end
|
59
|
+
InvalidComponentError = ProviderNotFoundError
|
60
|
+
deprecate_constant :InvalidComponentError
|
65
61
|
|
66
|
-
# Error raised when
|
62
|
+
# Error raised when a named provider source could not be found
|
67
63
|
#
|
68
64
|
# @api public
|
69
|
-
|
70
|
-
def initialize(
|
71
|
-
|
65
|
+
ProviderSourceNotFoundError = Class.new(StandardError) do
|
66
|
+
def initialize(name:, group:, keys:)
|
67
|
+
msg = "Provider source not found: #{name.inspect}, group: #{group.inspect}"
|
68
|
+
|
69
|
+
key_list = keys.map { |key| "- #{key[:name].inspect}, group: #{key[:group].inspect}" }
|
70
|
+
msg += "Available provider sources:\n\n#{key_list}"
|
71
|
+
|
72
|
+
super(msg)
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -81,43 +82,53 @@ module Dry
|
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
84
|
-
#
|
85
|
+
# Exception raise when a plugin dependency failed to load
|
85
86
|
#
|
86
87
|
# @api public
|
87
|
-
|
88
|
-
|
89
|
-
|
88
|
+
PluginDependencyMissing = Class.new(StandardError) do
|
89
|
+
# @api private
|
90
|
+
def initialize(plugin, message, gem = nil)
|
91
|
+
details = gem ? "#{message} - add #{gem} to your Gemfile" : message
|
92
|
+
super("dry-system plugin #{plugin.inspect} failed to load its dependencies: #{details}")
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
# Exception raised when auto-registerable component is not loadable
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
ComponentNotLoadableError = Class.new(NameError) do
|
96
100
|
# @api private
|
97
|
-
def initialize(
|
98
|
-
|
99
|
-
|
101
|
+
def initialize(component, error,
|
102
|
+
corrections: DidYouMean::ClassNameChecker.new(error).corrections)
|
103
|
+
full_class_name = [error.receiver, error.name].join("::")
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
+
message = [
|
106
|
+
"Component '#{component.key}' is not loadable.",
|
107
|
+
"Looking for #{full_class_name}."
|
108
|
+
]
|
105
109
|
|
106
|
-
|
110
|
+
if corrections.any?
|
111
|
+
case_correction = corrections.find { |correction| correction.casecmp?(full_class_name) }
|
112
|
+
if case_correction
|
113
|
+
acronyms_needed = case_correction.split("::").difference(full_class_name.split("::"))
|
114
|
+
stringified_acronyms_needed = acronyms_needed.map { |acronym|
|
115
|
+
"'#{acronym}'"
|
116
|
+
} .join(", ")
|
117
|
+
message <<
|
118
|
+
<<~ERROR_MESSAGE
|
107
119
|
|
108
|
-
|
109
|
-
attributes.map { |key, error| "#{key.name}: #{error}" }
|
110
|
-
end
|
111
|
-
end
|
120
|
+
You likely need to add:
|
112
121
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
122
|
+
acronym(#{stringified_acronyms_needed})
|
123
|
+
|
124
|
+
to your container's inflector, since we found a #{case_correction} class.
|
125
|
+
ERROR_MESSAGE
|
126
|
+
else
|
127
|
+
message << DidYouMean.formatter.message_for(corrections)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
super message.join("\n")
|
121
132
|
end
|
122
133
|
end
|
123
134
|
end
|
@@ -13,42 +13,24 @@ module Dry
|
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
class Identifier
|
16
|
-
include Dry::Equalizer(:
|
16
|
+
include Dry::Equalizer(:key)
|
17
17
|
|
18
|
-
# @return [String] the identifier string
|
18
|
+
# @return [String] the identifier's string key
|
19
19
|
# @api public
|
20
|
-
attr_reader :
|
21
|
-
|
22
|
-
# @return [String, nil] the namespace for the component
|
23
|
-
# @api public
|
24
|
-
attr_reader :namespace
|
25
|
-
|
26
|
-
# @return [String] the configured namespace separator
|
27
|
-
# @api public
|
28
|
-
attr_reader :separator
|
20
|
+
attr_reader :key
|
29
21
|
|
30
22
|
# @api private
|
31
|
-
def initialize(
|
32
|
-
@
|
33
|
-
@namespace = namespace
|
34
|
-
@separator = separator
|
23
|
+
def initialize(key)
|
24
|
+
@key = key.to_s
|
35
25
|
end
|
36
26
|
|
37
|
-
# @!method key
|
38
|
-
# Returns the identifier string
|
39
|
-
#
|
40
|
-
# @return [String]
|
41
|
-
# @see #identifier
|
42
|
-
# @api public
|
43
|
-
alias_method :key, :identifier
|
44
|
-
|
45
27
|
# @!method to_s
|
46
|
-
# Returns the identifier string
|
28
|
+
# Returns the identifier string key
|
47
29
|
#
|
48
30
|
# @return [String]
|
49
|
-
# @see #
|
31
|
+
# @see #key
|
50
32
|
# @api public
|
51
|
-
alias_method :to_s, :
|
33
|
+
alias_method :to_s, :key
|
52
34
|
|
53
35
|
# Returns the root namespace segment of the identifier string, as a symbol
|
54
36
|
#
|
@@ -62,95 +44,133 @@ module Dry
|
|
62
44
|
segments.first.to_sym
|
63
45
|
end
|
64
46
|
|
65
|
-
# Returns
|
66
|
-
#
|
67
|
-
#
|
47
|
+
# Returns true if the given leading segments string is a leading part of the {key}.
|
48
|
+
#
|
49
|
+
# Also returns true if nil or an empty string is given.
|
68
50
|
#
|
69
51
|
# @example
|
70
52
|
# identifier.key # => "articles.operations.create"
|
71
|
-
# identifier.namespace # => "admin"
|
72
53
|
#
|
73
|
-
# identifier.
|
54
|
+
# identifier.start_with?("articles.operations") # => true
|
55
|
+
# identifier.start_with?("articles") # => true
|
56
|
+
# identifier.start_with?("article") # => false
|
57
|
+
# identifier.start_with?(nil) # => true
|
74
58
|
#
|
75
|
-
# @
|
59
|
+
# @param leading_segments [String] the one or more leading segments to check
|
60
|
+
# @return [Boolean]
|
76
61
|
# @api public
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
"#{namespace_path}#{PATH_SEPARATOR}#{path}"
|
82
|
-
else
|
83
|
-
path
|
84
|
-
end
|
85
|
-
}
|
62
|
+
def start_with?(leading_segments)
|
63
|
+
leading_segments.to_s.empty? ||
|
64
|
+
key.start_with?("#{leading_segments}#{KEY_SEPARATOR}") ||
|
65
|
+
key.eql?(leading_segments)
|
86
66
|
end
|
87
67
|
|
88
|
-
# Returns true if the given
|
89
|
-
#
|
68
|
+
# Returns true if the given trailing segments string is the end part of the {key}.
|
69
|
+
#
|
70
|
+
# Also returns true if nil or an empty string is given.
|
90
71
|
#
|
91
72
|
# @example
|
92
73
|
# identifier.key # => "articles.operations.create"
|
93
74
|
#
|
94
|
-
# identifier.
|
95
|
-
# identifier.
|
96
|
-
# identifier.
|
75
|
+
# identifier.end_with?("create") # => true
|
76
|
+
# identifier.end_with?("operations.create") # => true
|
77
|
+
# identifier.end_with?("ate") # => false, not a whole segment
|
78
|
+
# identifier.end_with?("nup") # => false, not in key at all
|
97
79
|
#
|
98
|
-
# @param
|
80
|
+
# @param trailing_segments [String] the one or more trailing key segments to check
|
99
81
|
# @return [Boolean]
|
100
82
|
# @api public
|
101
|
-
def
|
102
|
-
|
83
|
+
def end_with?(trailing_segments)
|
84
|
+
trailing_segments.to_s.empty? ||
|
85
|
+
key.end_with?("#{KEY_SEPARATOR}#{trailing_segments}") ||
|
86
|
+
key.eql?(trailing_segments)
|
103
87
|
end
|
104
88
|
|
105
|
-
# Returns
|
106
|
-
# the identifier string.
|
89
|
+
# Returns true if the given segments string matches whole segments within the {key}.
|
107
90
|
#
|
108
|
-
#
|
109
|
-
#
|
91
|
+
# @example
|
92
|
+
# identifier.key # => "articles.operations.create"
|
110
93
|
#
|
111
|
-
#
|
112
|
-
#
|
94
|
+
# identifier.include?("operations") # => true
|
95
|
+
# identifier.include?("articles.operations") # => true
|
96
|
+
# identifier.include?("operations.create") # => true
|
113
97
|
#
|
114
|
-
#
|
98
|
+
# identifier.include?("article") # => false, not a whole segment
|
99
|
+
# identifier.include?("update") # => false, not in key at all
|
115
100
|
#
|
116
|
-
# @
|
117
|
-
# @
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
101
|
+
# @param segments [String] the one of more key segments to check
|
102
|
+
# @return [Boolean]
|
103
|
+
# @api public
|
104
|
+
def include?(segments)
|
105
|
+
return false if segments.to_s.empty?
|
106
|
+
|
107
|
+
sep_re = Regexp.escape(KEY_SEPARATOR)
|
108
|
+
key.match?(
|
109
|
+
/
|
110
|
+
(\A|#{sep_re})
|
111
|
+
#{Regexp.escape(segments)}
|
112
|
+
(\Z|#{sep_re})
|
113
|
+
/x
|
122
114
|
)
|
115
|
+
end
|
123
116
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
117
|
+
# Returns the key with its segments separated by the given separator
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# identifier.key # => "articles.operations.create"
|
121
|
+
# identifier.key_with_separator("/") # => "articles/operations/create"
|
122
|
+
#
|
123
|
+
# @return [String] the key using the separator
|
124
|
+
# @api private
|
125
|
+
def key_with_separator(separator)
|
126
|
+
segments.join(separator)
|
132
127
|
end
|
133
128
|
|
134
|
-
# Returns a copy of the identifier with the
|
129
|
+
# Returns a copy of the identifier with the key's leading namespace(s) replaced
|
135
130
|
#
|
136
|
-
# @
|
131
|
+
# @example Changing a namespace
|
132
|
+
# identifier.key # => "articles.operations.create"
|
133
|
+
# identifier.namespaced(from: "articles", to: "posts").key # => "posts.commands.create"
|
134
|
+
#
|
135
|
+
# @example Removing a namespace
|
136
|
+
# identifier.key # => "articles.operations.create"
|
137
|
+
# identifier.namespaced(from: "articles", to: nil).key # => "operations.create"
|
138
|
+
#
|
139
|
+
# @example Adding a namespace
|
140
|
+
# identifier.key # => "articles.operations.create"
|
141
|
+
# identifier.namespaced(from: nil, to: "admin").key # => "admin.articles.operations.create"
|
142
|
+
#
|
143
|
+
# @param from [String, nil] the leading namespace(s) to replace
|
144
|
+
# @param to [String, nil] the replacement for the leading namespace
|
137
145
|
#
|
138
146
|
# @return [Dry::System::Identifier] the copy of the identifier
|
139
147
|
#
|
140
148
|
# @see #initialize
|
141
149
|
# @api private
|
142
|
-
def
|
143
|
-
self
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
150
|
+
def namespaced(from:, to:)
|
151
|
+
return self if from == to
|
152
|
+
|
153
|
+
separated_to = "#{to}#{KEY_SEPARATOR}" if to
|
154
|
+
|
155
|
+
new_key =
|
156
|
+
if from.nil?
|
157
|
+
"#{separated_to}#{key}"
|
158
|
+
else
|
159
|
+
key.sub(
|
160
|
+
/^#{Regexp.escape(from.to_s)}#{Regexp.escape(KEY_SEPARATOR)}/,
|
161
|
+
separated_to || EMPTY_STRING
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
return self if new_key == key
|
166
|
+
|
167
|
+
self.class.new(new_key)
|
148
168
|
end
|
149
169
|
|
150
170
|
private
|
151
171
|
|
152
172
|
def segments
|
153
|
-
@segments ||=
|
173
|
+
@segments ||= key.split(KEY_SEPARATOR)
|
154
174
|
end
|
155
175
|
end
|
156
176
|
end
|
data/lib/dry/system/importer.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/container"
|
4
|
+
require_relative "constants"
|
5
|
+
|
3
6
|
module Dry
|
4
7
|
module System
|
5
8
|
# Default importer implementation
|
@@ -11,25 +14,34 @@ module Dry
|
|
11
14
|
#
|
12
15
|
# @api private
|
13
16
|
class Importer
|
14
|
-
|
17
|
+
# @api private
|
18
|
+
class Item
|
19
|
+
attr_reader :namespace, :container, :import_keys
|
20
|
+
|
21
|
+
def initialize(namespace:, container:, import_keys:)
|
22
|
+
@namespace = namespace
|
23
|
+
@container = container
|
24
|
+
@import_keys = import_keys
|
25
|
+
end
|
26
|
+
end
|
15
27
|
|
16
|
-
attr_reader :
|
28
|
+
attr_reader :container
|
17
29
|
|
18
30
|
attr_reader :registry
|
19
31
|
|
20
32
|
# @api private
|
21
33
|
def initialize(container)
|
22
34
|
@container = container
|
23
|
-
@separator = container.config.namespace_separator
|
24
35
|
@registry = {}
|
25
36
|
end
|
26
37
|
|
27
38
|
# @api private
|
28
|
-
def
|
29
|
-
registry
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
def register(namespace:, container:, keys: nil)
|
40
|
+
registry[namespace] = Item.new(
|
41
|
+
namespace: namespace,
|
42
|
+
container: container,
|
43
|
+
import_keys: keys
|
44
|
+
)
|
33
45
|
end
|
34
46
|
|
35
47
|
# @api private
|
@@ -41,15 +53,74 @@ module Dry
|
|
41
53
|
def key?(name)
|
42
54
|
registry.key?(name)
|
43
55
|
end
|
56
|
+
alias_method :namespace?, :key?
|
44
57
|
|
45
58
|
# @api private
|
46
|
-
def
|
47
|
-
|
59
|
+
def finalize!
|
60
|
+
registry.each_key { import(_1) }
|
61
|
+
self
|
48
62
|
end
|
49
63
|
|
50
64
|
# @api private
|
51
|
-
def
|
52
|
-
|
65
|
+
def import(namespace, keys: Undefined)
|
66
|
+
item = self[namespace]
|
67
|
+
keys = Undefined.default(keys, item.import_keys)
|
68
|
+
|
69
|
+
if keys
|
70
|
+
import_keys(item.container, namespace, keys_to_import(keys, item))
|
71
|
+
else
|
72
|
+
import_all(item.container, namespace)
|
73
|
+
end
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def keys_to_import(keys, item)
|
81
|
+
keys
|
82
|
+
.then { (arr = item.import_keys) ? _1 & arr : _1 }
|
83
|
+
.then { (arr = item.container.exports) ? _1 & arr : _1 }
|
84
|
+
end
|
85
|
+
|
86
|
+
def import_keys(other, namespace, keys)
|
87
|
+
container.merge(build_merge_container(other, keys), namespace: namespace)
|
88
|
+
end
|
89
|
+
|
90
|
+
def import_all(other, namespace)
|
91
|
+
merge_container =
|
92
|
+
if other.exports
|
93
|
+
build_merge_container(other, other.exports)
|
94
|
+
else
|
95
|
+
build_merge_container(other.finalize!, other.keys)
|
96
|
+
end
|
97
|
+
|
98
|
+
container.merge(merge_container, namespace: namespace)
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_merge_container(other, keys)
|
102
|
+
keys.each_with_object(Dry::Container.new) { |key, ic|
|
103
|
+
next unless other.key?(key)
|
104
|
+
|
105
|
+
# Access the other container's items directly so that we can preserve all their
|
106
|
+
# options when we merge them with the target container (e.g. if a component in
|
107
|
+
# the provider container was registered with a block, we want block registration
|
108
|
+
# behavior to be exhibited when later resolving that component from the target
|
109
|
+
# container). TODO: Make this part of dry-system's public API.
|
110
|
+
item = other._container[key]
|
111
|
+
|
112
|
+
# By default, we "protect" components that were themselves imported into the
|
113
|
+
# other container from being implicitly exported; imported components are
|
114
|
+
# considered "private" and must be explicitly included in `exports` to be
|
115
|
+
# exported.
|
116
|
+
next if item.options[:imported] && !other.exports
|
117
|
+
|
118
|
+
if item.callable?
|
119
|
+
ic.register(key, **item.options, imported: true, &item.item)
|
120
|
+
else
|
121
|
+
ic.register(key, item.item, **item.options, imported: true)
|
122
|
+
end
|
123
|
+
}
|
53
124
|
end
|
54
125
|
end
|
55
126
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module System
|
7
|
+
# An indirect component is a component that cannot be directly from a source file
|
8
|
+
# directly managed by the container. It may be component that needs to be loaded
|
9
|
+
# indirectly, either via a registration manifest file or an imported container
|
10
|
+
#
|
11
|
+
# Indirect components are an internal abstraction and, unlike ordinary components, are
|
12
|
+
# not exposed to users via component dir configuration hooks.
|
13
|
+
#
|
14
|
+
# @see Container#load_component
|
15
|
+
# @see Container#find_component
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
class IndirectComponent
|
19
|
+
include Dry::Equalizer(:identifier)
|
20
|
+
|
21
|
+
# @!attribute [r] identifier
|
22
|
+
# @return [String] the component's unique identifier
|
23
|
+
attr_reader :identifier
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def initialize(identifier)
|
27
|
+
@identifier = identifier
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns false, indicating that the component is not directly loadable from the
|
31
|
+
# files managed by the container
|
32
|
+
#
|
33
|
+
# This is the inverse of {Component#loadable?}
|
34
|
+
#
|
35
|
+
# @return [FalseClass]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def loadable?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the component's unique key
|
43
|
+
#
|
44
|
+
# @return [String] the key
|
45
|
+
#
|
46
|
+
# @see Identifier#key
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def key
|
50
|
+
identifier.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the root namespace segment of the component's key, as a symbol
|
54
|
+
#
|
55
|
+
# @see Identifier#root_key
|
56
|
+
#
|
57
|
+
# @return [Symbol] the root key
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
def root_key
|
61
|
+
identifier.root_key
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/dry/system/loader.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/system/errors"
|
4
|
+
|
3
5
|
module Dry
|
4
6
|
module System
|
5
7
|
# Default component loader implementation
|
@@ -17,7 +19,7 @@ module Dry
|
|
17
19
|
# class MyApp < Dry::System::Container
|
18
20
|
# configure do |config|
|
19
21
|
# # ...
|
20
|
-
# config.loader MyLoader
|
22
|
+
# config.component_dirs.loader = MyLoader
|
21
23
|
# end
|
22
24
|
# end
|
23
25
|
#
|
@@ -28,7 +30,7 @@ module Dry
|
|
28
30
|
#
|
29
31
|
# @api public
|
30
32
|
def require!(component)
|
31
|
-
require(component.
|
33
|
+
require(component.require_path)
|
32
34
|
self
|
33
35
|
end
|
34
36
|
|
@@ -61,8 +63,10 @@ module Dry
|
|
61
63
|
# @api public
|
62
64
|
def constant(component)
|
63
65
|
inflector = component.inflector
|
64
|
-
|
65
|
-
inflector.constantize(
|
66
|
+
const_name = inflector.camelize(component.const_path)
|
67
|
+
inflector.constantize(const_name)
|
68
|
+
rescue NameError => e
|
69
|
+
raise ComponentNotLoadableError.new(component, e)
|
66
70
|
end
|
67
71
|
|
68
72
|
private
|