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.
- 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
|