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/component.rb
CHANGED
@@ -17,52 +17,42 @@ module Dry
|
|
17
17
|
#
|
18
18
|
# @api public
|
19
19
|
class Component
|
20
|
-
include Dry::Equalizer(:identifier, :
|
20
|
+
include Dry::Equalizer(:identifier, :namespace, :options)
|
21
21
|
|
22
22
|
DEFAULT_OPTIONS = {
|
23
|
-
separator: DEFAULT_SEPARATOR,
|
24
23
|
inflector: Dry::Inflector.new,
|
25
24
|
loader: Loader
|
26
25
|
}.freeze
|
27
26
|
|
28
27
|
# @!attribute [r] identifier
|
29
|
-
# @return [String] component's unique identifier
|
28
|
+
# @return [String] the component's unique identifier
|
30
29
|
attr_reader :identifier
|
31
30
|
|
32
|
-
# @!attribute [r]
|
33
|
-
# @return [
|
34
|
-
attr_reader :
|
31
|
+
# @!attribute [r] namespace
|
32
|
+
# @return [Dry::System::Config::Namespace] the component's namespace
|
33
|
+
attr_reader :namespace
|
35
34
|
|
36
35
|
# @!attribute [r] options
|
37
|
-
# @return [Hash] component's options
|
36
|
+
# @return [Hash] the component's options
|
38
37
|
attr_reader :options
|
39
38
|
|
40
39
|
# @api private
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
separator = options.delete(:separator)
|
46
|
-
|
47
|
-
identifier =
|
48
|
-
if identifier.is_a?(Identifier)
|
49
|
-
identifier
|
50
|
-
else
|
51
|
-
Identifier.new(
|
52
|
-
identifier,
|
53
|
-
namespace: namespace,
|
54
|
-
separator: separator
|
55
|
-
)
|
56
|
-
end
|
57
|
-
|
58
|
-
super(identifier, **options)
|
40
|
+
def initialize(identifier, namespace:, **options)
|
41
|
+
@identifier = identifier
|
42
|
+
@namespace = namespace
|
43
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
59
44
|
end
|
60
45
|
|
46
|
+
# Returns true, indicating that the component is directly loadable from the files
|
47
|
+
# managed by the container
|
48
|
+
#
|
49
|
+
# This is the inverse of {IndirectComponent#loadable?}
|
50
|
+
#
|
51
|
+
# @return [TrueClass]
|
52
|
+
#
|
61
53
|
# @api private
|
62
|
-
def
|
63
|
-
|
64
|
-
@file_path = file_path
|
65
|
-
@options = options
|
54
|
+
def loadable?
|
55
|
+
true
|
66
56
|
end
|
67
57
|
|
68
58
|
# Returns the component's instance
|
@@ -70,43 +60,99 @@ module Dry
|
|
70
60
|
# @return [Object] component's class instance
|
71
61
|
# @api public
|
72
62
|
def instance(*args)
|
73
|
-
loader.call(self, *args)
|
63
|
+
options[:instance]&.call(self, *args) || loader.call(self, *args)
|
74
64
|
end
|
75
65
|
ruby2_keywords(:instance) if respond_to?(:ruby2_keywords, true)
|
76
66
|
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
67
|
+
# Returns the component's unique key
|
68
|
+
#
|
69
|
+
# @return [String] the key
|
70
|
+
#
|
71
|
+
# @see Identifier#key
|
72
|
+
#
|
73
|
+
# @api public
|
82
74
|
def key
|
83
|
-
identifier.
|
84
|
-
end
|
85
|
-
|
86
|
-
def path
|
87
|
-
identifier.path
|
75
|
+
identifier.key
|
88
76
|
end
|
89
77
|
|
78
|
+
# Returns the root namespace segment of the component's key, as a symbol
|
79
|
+
#
|
80
|
+
# @see Identifier#root_key
|
81
|
+
#
|
82
|
+
# @return [Symbol] the root key
|
83
|
+
#
|
84
|
+
# @api public
|
90
85
|
def root_key
|
91
86
|
identifier.root_key
|
92
87
|
end
|
93
88
|
|
94
|
-
# Returns
|
89
|
+
# Returns a path-delimited representation of the compnent, appropriate for passing
|
90
|
+
# to `Kernel#require` to require its source file
|
95
91
|
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
|
99
|
-
|
92
|
+
# The path takes into account the rules of the namespace used to load the component.
|
93
|
+
#
|
94
|
+
# @example Component from a root namespace
|
95
|
+
# component.key # => "articles.create"
|
96
|
+
# component.require_path # => "articles/create"
|
97
|
+
#
|
98
|
+
# @example Component from an "admin/" path namespace (with `key: nil`)
|
99
|
+
# component.key # => "articles.create"
|
100
|
+
# component.require_path # => "admin/articles/create"
|
101
|
+
#
|
102
|
+
# @see Config::Namespaces#add
|
103
|
+
# @see Config::Namespace
|
104
|
+
#
|
105
|
+
# @return [String] the require path
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def require_path
|
109
|
+
if namespace.path
|
110
|
+
"#{namespace.path}#{PATH_SEPARATOR}#{path_in_namespace}"
|
111
|
+
else
|
112
|
+
path_in_namespace
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns an "underscored", path-delimited representation of the component,
|
117
|
+
# appropriate for passing to the inflector for constantizing
|
118
|
+
#
|
119
|
+
# The const path takes into account the rules of the namespace used to load the
|
120
|
+
# component.
|
121
|
+
#
|
122
|
+
# @example Component from a namespace with `const: nil`
|
123
|
+
# component.key # => "articles.create_article"
|
124
|
+
# component.const_path # => "articles/create_article"
|
125
|
+
# component.inflector.constantize(component.const_path) # => Articles::CreateArticle
|
126
|
+
#
|
127
|
+
# @example Component from a namespace with `const: "admin"`
|
128
|
+
# component.key # => "articles.create_article"
|
129
|
+
# component.const_path # => "admin/articles/create_article"
|
130
|
+
# component.inflector.constantize(component.const_path) # => Admin::Articles::CreateArticle
|
131
|
+
#
|
132
|
+
# @see Config::Namespaces#add
|
133
|
+
# @see Config::Namespace
|
134
|
+
#
|
135
|
+
# @return [String] the const path
|
136
|
+
#
|
137
|
+
# @api public
|
138
|
+
def const_path
|
139
|
+
namespace_const_path = namespace.const&.gsub(KEY_SEPARATOR, PATH_SEPARATOR)
|
140
|
+
|
141
|
+
if namespace_const_path
|
142
|
+
"#{namespace_const_path}#{PATH_SEPARATOR}#{path_in_namespace}"
|
143
|
+
else
|
144
|
+
path_in_namespace
|
145
|
+
end
|
100
146
|
end
|
101
147
|
|
102
148
|
# @api private
|
103
149
|
def loader
|
104
|
-
options
|
150
|
+
options.fetch(:loader)
|
105
151
|
end
|
106
152
|
|
107
153
|
# @api private
|
108
154
|
def inflector
|
109
|
-
options
|
155
|
+
options.fetch(:inflector)
|
110
156
|
end
|
111
157
|
|
112
158
|
# @api private
|
@@ -121,6 +167,17 @@ module Dry
|
|
121
167
|
|
122
168
|
private
|
123
169
|
|
170
|
+
def path_in_namespace
|
171
|
+
identifier_in_namespace =
|
172
|
+
if namespace.key
|
173
|
+
identifier.namespaced(from: namespace.key, to: nil)
|
174
|
+
else
|
175
|
+
identifier
|
176
|
+
end
|
177
|
+
|
178
|
+
identifier_in_namespace.key_with_separator(PATH_SEPARATOR)
|
179
|
+
end
|
180
|
+
|
124
181
|
def callable_option?(value)
|
125
182
|
if value.respond_to?(:call)
|
126
183
|
!!value.call(self)
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pathname"
|
4
|
+
require "dry/system/constants"
|
2
5
|
require_relative "constants"
|
3
6
|
require_relative "identifier"
|
4
7
|
require_relative "magic_comments_parser"
|
@@ -28,88 +31,126 @@ module Dry
|
|
28
31
|
@container = container
|
29
32
|
end
|
30
33
|
|
31
|
-
# Returns a component for
|
32
|
-
#
|
34
|
+
# Returns a component for the given key if a matching source file is found within
|
35
|
+
# the component dir
|
33
36
|
#
|
34
|
-
# This
|
35
|
-
#
|
37
|
+
# This searches according to the component dir's configured namespaces, in order of
|
38
|
+
# definition, with the first match returned as the component.
|
36
39
|
#
|
37
|
-
# @param
|
40
|
+
# @param key [String] the component's key
|
38
41
|
# @return [Dry::System::Component, nil] the component, if found
|
39
42
|
#
|
40
43
|
# @api private
|
41
|
-
def
|
42
|
-
|
43
|
-
identifier
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def component_for_key(key)
|
45
|
+
config.namespaces.each do |namespace|
|
46
|
+
identifier = Identifier.new(key)
|
47
|
+
|
48
|
+
next unless identifier.start_with?(namespace.key)
|
49
|
+
|
50
|
+
if (file_path = find_component_file(identifier, namespace))
|
51
|
+
return build_component(identifier, namespace, file_path)
|
52
|
+
end
|
50
53
|
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_component
|
59
|
+
return enum_for(:each_component) unless block_given?
|
60
|
+
|
61
|
+
each_file do |file_path, namespace|
|
62
|
+
yield component_for_path(file_path, namespace)
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
58
|
-
|
59
|
-
#
|
60
|
-
# @param path [String] the full path to the file
|
61
|
-
# @return [Dry::System::Component] the component
|
62
|
-
#
|
63
|
-
# @api private
|
64
|
-
def component_for_path(path)
|
65
|
-
separator = container.config.namespace_separator
|
66
|
+
private
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
.scan(WORD_REGEX)
|
70
|
-
.join(separator)
|
68
|
+
def each_file
|
69
|
+
return enum_for(:each_file) unless block_given?
|
71
70
|
|
72
|
-
|
71
|
+
raise ComponentDirNotFoundError, full_path unless Dir.exist?(full_path)
|
73
72
|
|
74
|
-
|
75
|
-
|
73
|
+
config.namespaces.each do |namespace|
|
74
|
+
files(namespace).each do |file|
|
75
|
+
yield file, namespace
|
76
|
+
end
|
76
77
|
end
|
78
|
+
end
|
77
79
|
|
78
|
-
|
80
|
+
def files(namespace)
|
81
|
+
if namespace.path?
|
82
|
+
Dir[File.join(full_path, namespace.path, "**", RB_GLOB)].sort
|
83
|
+
else
|
84
|
+
non_root_paths = config.namespaces.to_a.reject(&:root?).map(&:path)
|
85
|
+
|
86
|
+
Dir[File.join(full_path, "**", RB_GLOB)].reject { |file_path|
|
87
|
+
Pathname(file_path).relative_path_from(full_path).to_s.start_with?(*non_root_paths)
|
88
|
+
}.sort
|
89
|
+
end
|
79
90
|
end
|
80
91
|
|
81
92
|
# Returns the full path of the component directory
|
82
93
|
#
|
83
94
|
# @return [Pathname]
|
84
|
-
# @api private
|
85
95
|
def full_path
|
86
96
|
container.root.join(path)
|
87
97
|
end
|
88
98
|
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
99
|
+
# Returns a component for a full path to a Ruby source file within the component dir
|
100
|
+
#
|
101
|
+
# @param path [String] the full path to the file
|
102
|
+
# @return [Dry::System::Component] the component
|
103
|
+
def component_for_path(path, namespace)
|
104
|
+
key = Pathname(path).relative_path_from(full_path).to_s
|
105
|
+
.sub(RB_EXT, EMPTY_STRING)
|
106
|
+
.scan(WORD_REGEX)
|
107
|
+
.join(KEY_SEPARATOR)
|
108
|
+
|
109
|
+
identifier = Identifier.new(key)
|
110
|
+
.namespaced(
|
111
|
+
from: namespace.path&.gsub(PATH_SEPARATOR, KEY_SEPARATOR),
|
112
|
+
to: namespace.key
|
113
|
+
)
|
114
|
+
|
115
|
+
build_component(identifier, namespace, path)
|
96
116
|
end
|
97
117
|
|
98
|
-
|
118
|
+
def find_component_file(identifier, namespace)
|
119
|
+
# To properly find the file within a namespace with a key, we should strip the key
|
120
|
+
# from beginning of our given identifier
|
121
|
+
if namespace.key
|
122
|
+
identifier = identifier.namespaced(from: namespace.key, to: nil)
|
123
|
+
end
|
99
124
|
|
100
|
-
|
125
|
+
file_name = "#{identifier.key_with_separator(PATH_SEPARATOR)}#{RB_EXT}"
|
126
|
+
|
127
|
+
component_file =
|
128
|
+
if namespace.path?
|
129
|
+
full_path.join(namespace.path, file_name)
|
130
|
+
else
|
131
|
+
full_path.join(file_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
component_file if component_file.exist?
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_component(identifier, namespace, file_path)
|
101
138
|
options = {
|
102
139
|
inflector: container.config.inflector,
|
103
140
|
**component_options,
|
104
141
|
**MagicCommentsParser.(file_path)
|
105
142
|
}
|
106
143
|
|
107
|
-
Component.new(identifier,
|
144
|
+
Component.new(identifier, namespace: namespace, **options)
|
108
145
|
end
|
109
146
|
|
110
|
-
def
|
111
|
-
|
112
|
-
|
147
|
+
def component_options
|
148
|
+
{
|
149
|
+
auto_register: auto_register,
|
150
|
+
loader: loader,
|
151
|
+
instance: instance,
|
152
|
+
memoize: memoize
|
153
|
+
}
|
113
154
|
end
|
114
155
|
|
115
156
|
def method_missing(name, *args, &block)
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/
|
3
|
+
require "dry/core/deprecations"
|
4
4
|
|
5
|
-
Dry::
|
6
|
-
|
7
|
-
|
5
|
+
Dry::Core::Deprecations.announce(
|
6
|
+
"require \"dry/system/components\"",
|
7
|
+
"Use `require \"dry/system/provider_sources\"` instead",
|
8
|
+
tag: "dry-system",
|
9
|
+
uplevel: 1
|
8
10
|
)
|
11
|
+
|
12
|
+
require_relative "provider_sources"
|