dry-system 0.18.2 → 0.19.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 +88 -4
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/dry-system.gemspec +3 -4
- data/lib/dry/system/auto_registrar.rb +16 -58
- data/lib/dry/system/booter.rb +34 -15
- data/lib/dry/system/component.rb +56 -94
- data/lib/dry/system/component_dir.rb +128 -0
- data/lib/dry/system/config/component_dir.rb +202 -0
- data/lib/dry/system/config/component_dirs.rb +184 -0
- data/lib/dry/system/container.rb +78 -144
- data/lib/dry/system/errors.rb +21 -12
- data/lib/dry/system/identifier.rb +157 -0
- data/lib/dry/system/loader/autoloading.rb +26 -0
- data/lib/dry/system/loader.rb +40 -41
- data/lib/dry/system/plugins/logging.rb +4 -1
- data/lib/dry/system/version.rb +1 -1
- metadata +16 -26
- data/lib/dry/system/auto_registrar/configuration.rb +0 -43
@@ -0,0 +1,128 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require_relative "constants"
|
3
|
+
require_relative "identifier"
|
4
|
+
require_relative "magic_comments_parser"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module System
|
8
|
+
# A configured component directory within the container's root. Provides access to the
|
9
|
+
# component directory's configuration, as well as methods for locating component files
|
10
|
+
# within the directory
|
11
|
+
#
|
12
|
+
# @see Dry::System::Config::ComponentDir
|
13
|
+
# @api private
|
14
|
+
class ComponentDir
|
15
|
+
# @!attribute [r] config
|
16
|
+
# @return [Dry::System::Config::ComponentDir] the component directory configuration
|
17
|
+
# @api private
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
# @!attribute [r] container
|
21
|
+
# @return [Dry::System::Container] the container managing the component directory
|
22
|
+
# @api private
|
23
|
+
attr_reader :container
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def initialize(config:, container:)
|
27
|
+
@config = config
|
28
|
+
@container = container
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a component for a given identifier if a matching component file could be
|
32
|
+
# found within the component dir
|
33
|
+
#
|
34
|
+
# This will search within the component dir's configured default_namespace first,
|
35
|
+
# then fall back to searching for a non-namespaced file
|
36
|
+
#
|
37
|
+
# @param identifier [String] the identifier string
|
38
|
+
# @return [Dry::System::Component, nil] the component, if found
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def component_for_identifier(identifier)
|
42
|
+
identifier = Identifier.new(
|
43
|
+
identifier,
|
44
|
+
namespace: default_namespace,
|
45
|
+
separator: container.config.namespace_separator
|
46
|
+
)
|
47
|
+
|
48
|
+
if (file_path = find_component_file(identifier.path))
|
49
|
+
return build_component(identifier, file_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
identifier = identifier.with(namespace: nil)
|
53
|
+
if (file_path = find_component_file(identifier.path))
|
54
|
+
build_component(identifier, file_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a component for a full path to a Ruby source file within the component dir
|
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
|
+
|
67
|
+
key = Pathname(path).relative_path_from(full_path).to_s
|
68
|
+
.sub(RB_EXT, EMPTY_STRING)
|
69
|
+
.scan(WORD_REGEX)
|
70
|
+
.join(separator)
|
71
|
+
|
72
|
+
identifier = Identifier.new(key, separator: separator)
|
73
|
+
|
74
|
+
if identifier.start_with?(default_namespace)
|
75
|
+
identifier = identifier.dequalified(default_namespace, namespace: default_namespace)
|
76
|
+
end
|
77
|
+
|
78
|
+
build_component(identifier, path)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the full path of the component directory
|
82
|
+
#
|
83
|
+
# @return [Pathname]
|
84
|
+
# @api private
|
85
|
+
def full_path
|
86
|
+
container.root.join(path)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def component_options
|
91
|
+
{
|
92
|
+
auto_register: auto_register,
|
93
|
+
loader: loader,
|
94
|
+
memoize: memoize
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def build_component(identifier, file_path)
|
101
|
+
options = {
|
102
|
+
inflector: container.config.inflector,
|
103
|
+
**component_options,
|
104
|
+
**MagicCommentsParser.(file_path)
|
105
|
+
}
|
106
|
+
|
107
|
+
Component.new(identifier, file_path: file_path, **options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_component_file(component_path)
|
111
|
+
component_file = full_path.join("#{component_path}#{RB_EXT}")
|
112
|
+
component_file if component_file.exist?
|
113
|
+
end
|
114
|
+
|
115
|
+
def method_missing(name, *args, &block)
|
116
|
+
if config.respond_to?(name)
|
117
|
+
config.public_send(name, *args, &block)
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_to_missing?(name, include_all = false)
|
124
|
+
config.respond_to?(name) || super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require "dry/configurable"
|
2
|
+
require "dry/system/loader"
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module System
|
6
|
+
module Config
|
7
|
+
class ComponentDir
|
8
|
+
include Dry::Configurable
|
9
|
+
|
10
|
+
# @!group Settings
|
11
|
+
|
12
|
+
# @!method auto_register=(policy)
|
13
|
+
#
|
14
|
+
# Sets the auto-registration policy for the component dir.
|
15
|
+
#
|
16
|
+
# This may be a simple boolean to enable or disable auto-registration for all
|
17
|
+
# components, or a proc accepting a `Dry::Sytem::Component` and returning a
|
18
|
+
# boolean to configure auto-registration on a per-component basis
|
19
|
+
#
|
20
|
+
# Defaults to `true`.
|
21
|
+
#
|
22
|
+
# @param policy [Boolean, Proc]
|
23
|
+
# @return [Boolean, Proc]
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# dir.auto_register = false
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# dir.auto_register = proc do |component|
|
30
|
+
# !component.start_with?("entities")
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @see auto_register
|
34
|
+
# @see Component
|
35
|
+
#
|
36
|
+
# @!method auto_register
|
37
|
+
#
|
38
|
+
# Returns the configured auto-registration policy.
|
39
|
+
#
|
40
|
+
# @return [Boolean, Proc] the configured policy
|
41
|
+
#
|
42
|
+
# @see auto_register=
|
43
|
+
setting :auto_register, true
|
44
|
+
|
45
|
+
# @!method add_to_load_path=(policy)
|
46
|
+
#
|
47
|
+
# Sets whether the dir should be added to the `$LOAD_PATH` after the container
|
48
|
+
# is configured.
|
49
|
+
#
|
50
|
+
# Defaults to `true`. This may need to be set to `false` when using a class
|
51
|
+
# autoloading system.
|
52
|
+
#
|
53
|
+
# @param policy [Boolean]
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @see add_to_load_path
|
57
|
+
# @see Container.configure
|
58
|
+
#
|
59
|
+
# @!method add_to_load_path
|
60
|
+
#
|
61
|
+
# Returns the configured value.
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
#
|
65
|
+
# @see add_to_load_path=
|
66
|
+
setting :add_to_load_path, true
|
67
|
+
|
68
|
+
# @!method default_namespace=(leading_namespace)
|
69
|
+
#
|
70
|
+
# Sets the leading namespace segments to be stripped when registering components
|
71
|
+
# from the dir in the container.
|
72
|
+
#
|
73
|
+
# This is useful to configure when the dir contains components in a module
|
74
|
+
# namespace that you don't want repeated in their identifiers.
|
75
|
+
#
|
76
|
+
# Defaults to `nil`.
|
77
|
+
#
|
78
|
+
# @param leading_namespace [String, nil]
|
79
|
+
# @return [String, nil]
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# dir.default_namespace = "my_app"
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# dir.default_namespace = "my_app.admin"
|
86
|
+
#
|
87
|
+
# @see default_namespace
|
88
|
+
#
|
89
|
+
# @!method default_namespace
|
90
|
+
#
|
91
|
+
# Returns the configured value.
|
92
|
+
#
|
93
|
+
# @return [String, nil]
|
94
|
+
#
|
95
|
+
# @see default_namespace=
|
96
|
+
setting :default_namespace
|
97
|
+
|
98
|
+
# @!method loader=(loader)
|
99
|
+
#
|
100
|
+
# Sets the loader to use when registering coponents from the dir in the container.
|
101
|
+
#
|
102
|
+
# Defaults to `Dry::System::Loader`.
|
103
|
+
#
|
104
|
+
# When using a class autoloader, consider using `Dry::System::Loader::Autoloading`
|
105
|
+
#
|
106
|
+
# @param loader [#call] the loader
|
107
|
+
# @return [#call] the configured loader
|
108
|
+
#
|
109
|
+
# @see loader
|
110
|
+
# @see Loader
|
111
|
+
# @see Loader::Autoloading
|
112
|
+
#
|
113
|
+
# @!method loader
|
114
|
+
#
|
115
|
+
# Returns the configured loader.
|
116
|
+
#
|
117
|
+
# @return [#call]
|
118
|
+
#
|
119
|
+
# @see loader=
|
120
|
+
setting :loader, Dry::System::Loader
|
121
|
+
|
122
|
+
# @!method memoize=(policy)
|
123
|
+
#
|
124
|
+
# Sets whether to memoize components from the dir when registered in the
|
125
|
+
# container.
|
126
|
+
#
|
127
|
+
# This may be a simple boolean to enable or disable memoization for all
|
128
|
+
# components, or a proc accepting a `Dry::Sytem::Component` and returning a
|
129
|
+
# boolean to configure memoization on a per-component basis
|
130
|
+
#
|
131
|
+
# Defaults to `false`.
|
132
|
+
#
|
133
|
+
# @param policy [Boolean, Proc]
|
134
|
+
# @return [Boolean, Proc] the configured memoization policy
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# dir.memoize = true
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# dir.memoize = proc do |component|
|
141
|
+
# !component.start_with?("providers")
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# @see memoize
|
145
|
+
# @see Component
|
146
|
+
#
|
147
|
+
# @!method memoize
|
148
|
+
#
|
149
|
+
# Returns the configured memoization policy.
|
150
|
+
#
|
151
|
+
# @return [Boolean, Proc] the configured memoization policy
|
152
|
+
#
|
153
|
+
# @see memoize=
|
154
|
+
setting :memoize, false
|
155
|
+
|
156
|
+
# @!endgroup
|
157
|
+
|
158
|
+
# Returns the component dir path, relative to the configured container root
|
159
|
+
#
|
160
|
+
# @return [String] the path
|
161
|
+
attr_reader :path
|
162
|
+
|
163
|
+
# @api private
|
164
|
+
def initialize(path)
|
165
|
+
super()
|
166
|
+
@path = path
|
167
|
+
yield self if block_given?
|
168
|
+
end
|
169
|
+
|
170
|
+
# @api private
|
171
|
+
def auto_register?
|
172
|
+
!!config.auto_register
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns true if a setting has been explicitly configured and is not returning
|
176
|
+
# just a default value.
|
177
|
+
#
|
178
|
+
# This is used to determine which settings from `ComponentDirs` should be applied
|
179
|
+
# as additional defaults.
|
180
|
+
#
|
181
|
+
# @api private
|
182
|
+
def configured?(key)
|
183
|
+
config._settings[key].input_defined?
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def method_missing(name, *args, &block)
|
189
|
+
if config.respond_to?(name)
|
190
|
+
config.public_send(name, *args, &block)
|
191
|
+
else
|
192
|
+
super
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def respond_to_missing?(name, include_all = false)
|
197
|
+
config.respond_to?(name) || super
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require "concurrent/map"
|
2
|
+
require "dry/configurable"
|
3
|
+
require "dry/system/constants"
|
4
|
+
require "dry/system/errors"
|
5
|
+
require_relative "component_dir"
|
6
|
+
|
7
|
+
module Dry
|
8
|
+
module System
|
9
|
+
module Config
|
10
|
+
class ComponentDirs
|
11
|
+
include Dry::Configurable
|
12
|
+
|
13
|
+
# Settings from ComponentDir are configured here as defaults for all added dirs
|
14
|
+
ComponentDir._settings.each do |setting|
|
15
|
+
_settings << setting.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
# @!group Settings
|
19
|
+
|
20
|
+
# @!method auto_register=(value)
|
21
|
+
#
|
22
|
+
# Sets a default `auto_register` for all added component dirs
|
23
|
+
#
|
24
|
+
# @see ComponentDir.auto_register
|
25
|
+
# @see auto_register
|
26
|
+
#
|
27
|
+
# @!method auto_register
|
28
|
+
#
|
29
|
+
# Returns the configured default `auto_register`
|
30
|
+
#
|
31
|
+
# @see auto_register=
|
32
|
+
|
33
|
+
# @!method add_to_load_path=(value)
|
34
|
+
#
|
35
|
+
# Sets a default `add_to_load_path` value for all added component dirs
|
36
|
+
#
|
37
|
+
# @see ComponentDir.add_to_load_path
|
38
|
+
# @see add_to_load_path
|
39
|
+
#
|
40
|
+
# @!method add_to_load_path
|
41
|
+
#
|
42
|
+
# Returns the configured default `add_to_load_path`
|
43
|
+
#
|
44
|
+
# @see add_to_load_path=
|
45
|
+
|
46
|
+
# @!method default_namespace=(value)
|
47
|
+
#
|
48
|
+
# Sets a default `default_namespace` value for all added component dirs
|
49
|
+
#
|
50
|
+
# @see ComponentDir.default_namespace
|
51
|
+
# @see default_namespace
|
52
|
+
#
|
53
|
+
# @!method default_namespace
|
54
|
+
#
|
55
|
+
# Returns the configured default `default_namespace`
|
56
|
+
#
|
57
|
+
# @see default_namespace=
|
58
|
+
|
59
|
+
# @!method loader=(value)
|
60
|
+
#
|
61
|
+
# Sets a default `loader` value for all added component dirs
|
62
|
+
#
|
63
|
+
# @see ComponentDir.loader
|
64
|
+
# @see loader
|
65
|
+
#
|
66
|
+
# @!method loader
|
67
|
+
#
|
68
|
+
# Returns the configured default `loader`
|
69
|
+
#
|
70
|
+
# @see loader=
|
71
|
+
|
72
|
+
# @!method memoize=(value)
|
73
|
+
#
|
74
|
+
# Sets a default `memoize` value for all added component dirs
|
75
|
+
#
|
76
|
+
# @see ComponentDir.memoize
|
77
|
+
# @see memoize
|
78
|
+
#
|
79
|
+
# @!method memoize
|
80
|
+
#
|
81
|
+
# Returns the configured default `memoize`
|
82
|
+
#
|
83
|
+
# @see memoize=
|
84
|
+
|
85
|
+
# @!endgroup
|
86
|
+
|
87
|
+
# @api private
|
88
|
+
def initialize
|
89
|
+
@dirs = Concurrent::Map.new
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
def initialize_copy(source)
|
94
|
+
super
|
95
|
+
@dirs = source.dirs.dup
|
96
|
+
end
|
97
|
+
|
98
|
+
# Adds and configures a component dir
|
99
|
+
#
|
100
|
+
# @param path [String] the path for the component dir, relative to the configured
|
101
|
+
# container root
|
102
|
+
#
|
103
|
+
# @yieldparam dir [ComponentDir] the component dir to configure
|
104
|
+
#
|
105
|
+
# @return [ComponentDir] the added component dir
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# component_dirs.add "lib" do |dir|
|
109
|
+
# dir.default_namespace = "my_app"
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# @see ComponentDir
|
113
|
+
def add(path)
|
114
|
+
raise ComponentDirAlreadyAddedError, path if dirs.key?(path)
|
115
|
+
|
116
|
+
dirs[path] = ComponentDir.new(path).tap do |dir|
|
117
|
+
apply_defaults_to_dir(dir)
|
118
|
+
yield dir if block_given?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the added component dirs, with default settings applied
|
123
|
+
#
|
124
|
+
# @return [Hash<String, ComponentDir>] the component dirs as a hash, keyed by path
|
125
|
+
def dirs
|
126
|
+
@dirs.each { |_, dir| apply_defaults_to_dir(dir) }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the added component dirs, with default settings applied
|
130
|
+
#
|
131
|
+
# @return [Array<ComponentDir>]
|
132
|
+
def to_a
|
133
|
+
dirs.values
|
134
|
+
end
|
135
|
+
|
136
|
+
# Calls the given block once for each added component dir, passing the dir as an
|
137
|
+
# argument.
|
138
|
+
#
|
139
|
+
# @yieldparam dir [ComponentDir] the yielded component dir
|
140
|
+
def each(&block)
|
141
|
+
to_a.each(&block)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Apply default settings to a component dir. This is run every time the dirs are
|
147
|
+
# accessed to ensure defaults are applied regardless of when new component dirs
|
148
|
+
# are added. This method must be idempotent.
|
149
|
+
#
|
150
|
+
# @return [void]
|
151
|
+
def apply_defaults_to_dir(dir)
|
152
|
+
dir.config.values.each do |key, _value|
|
153
|
+
if configured?(key) && !dir.configured?(key)
|
154
|
+
dir.public_send(:"#{key}=", public_send(key))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns true if a setting has been explicitly configured and is not returning
|
160
|
+
# just a default value.
|
161
|
+
#
|
162
|
+
# This is used to determine which settings should be applied to added component
|
163
|
+
# dirs as additional defaults.
|
164
|
+
#
|
165
|
+
# @api private
|
166
|
+
def configured?(key)
|
167
|
+
config._settings[key].input_defined?
|
168
|
+
end
|
169
|
+
|
170
|
+
def method_missing(name, *args, &block)
|
171
|
+
if config.respond_to?(name)
|
172
|
+
config.public_send(name, *args, &block)
|
173
|
+
else
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def respond_to_missing?(name, include_all = false)
|
179
|
+
config.respond_to?(name) || super
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|