dry-system 0.18.1 → 1.0.1
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 +678 -0
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-system.gemspec +18 -21
- data/lib/dry/system/auto_registrar.rb +9 -64
- data/lib/dry/system/component.rb +124 -104
- data/lib/dry/system/component_dir.rb +171 -0
- data/lib/dry/system/config/component_dir.rb +228 -0
- data/lib/dry/system/config/component_dirs.rb +289 -0
- data/lib/dry/system/config/namespace.rb +75 -0
- data/lib/dry/system/config/namespaces.rb +196 -0
- data/lib/dry/system/constants.rb +2 -4
- data/lib/dry/system/container.rb +305 -345
- data/lib/dry/system/errors.rb +73 -56
- data/lib/dry/system/identifier.rb +176 -0
- data/lib/dry/system/importer.rb +89 -12
- data/lib/dry/system/indirect_component.rb +63 -0
- data/lib/dry/system/loader/autoloading.rb +24 -0
- data/lib/dry/system/loader.rb +49 -41
- data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +13 -14
- data/lib/dry/system/plugins/bootsnap.rb +3 -2
- data/lib/dry/system/plugins/dependency_graph/strategies.rb +38 -2
- data/lib/dry/system/plugins/dependency_graph.rb +25 -21
- data/lib/dry/system/plugins/env.rb +3 -2
- data/lib/dry/system/plugins/logging.rb +9 -8
- data/lib/dry/system/plugins/monitoring.rb +1 -2
- data/lib/dry/system/plugins/notifications.rb +1 -1
- data/lib/dry/system/plugins/plugin.rb +61 -0
- 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 +5 -73
- data/lib/dry/system/provider/source.rb +276 -0
- data/lib/dry/system/provider/source_dsl.rb +55 -0
- data/lib/dry/system/provider.rb +261 -23
- data/lib/dry/system/provider_registrar.rb +251 -0
- data/lib/dry/system/provider_source_registry.rb +56 -0
- data/lib/dry/system/provider_sources/settings/config.rb +73 -0
- data/lib/dry/system/provider_sources/settings/loader.rb +44 -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 +6 -2
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +35 -13
- metadata +48 -97
- data/lib/dry/system/auto_registrar/configuration.rb +0 -43
- data/lib/dry/system/booter/component_registry.rb +0 -35
- data/lib/dry/system/booter.rb +0 -181
- data/lib/dry/system/components/bootable.rb +0 -289
- data/lib/dry/system/components/config.rb +0 -35
- data/lib/dry/system/components.rb +0 -8
- 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/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
<!--- this file is synced from dry-rb/template-gem project -->
|
1
2
|
[gem]: https://rubygems.org/gems/dry-system
|
2
3
|
[actions]: https://github.com/dry-rb/dry-system/actions
|
3
4
|
[codacy]: https://www.codacy.com/gh/dry-rb/dry-system
|
@@ -10,19 +11,19 @@
|
|
10
11
|
[][actions]
|
11
12
|
[][codacy]
|
12
13
|
[][codacy]
|
13
|
-
[][inchpages]
|
14
15
|
|
15
16
|
## Links
|
16
17
|
|
17
|
-
* [User documentation](
|
18
|
+
* [User documentation](https://dry-rb.org/gems/dry-system)
|
18
19
|
* [API documentation](http://rubydoc.info/gems/dry-system)
|
19
20
|
|
20
21
|
## Supported Ruby versions
|
21
22
|
|
22
23
|
This library officially supports the following Ruby versions:
|
23
24
|
|
24
|
-
* MRI
|
25
|
-
* jruby
|
25
|
+
* MRI `>= 2.7.0`
|
26
|
+
* jruby `>= 9.3` (postponed until 2.7 is supported)
|
26
27
|
|
27
28
|
## License
|
28
29
|
|
data/dry-system.gemspec
CHANGED
@@ -1,41 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# this file is managed by dry-rb/devtools project
|
3
2
|
|
4
|
-
|
3
|
+
# this file is synced from dry-rb/template-gem project
|
4
|
+
|
5
|
+
lib = File.expand_path("lib", __dir__)
|
5
6
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
-
require
|
7
|
+
require "dry/system/version"
|
7
8
|
|
8
9
|
Gem::Specification.new do |spec|
|
9
|
-
spec.name =
|
10
|
+
spec.name = "dry-system"
|
10
11
|
spec.authors = ["Piotr Solnica"]
|
11
12
|
spec.email = ["piotr.solnica@gmail.com"]
|
12
|
-
spec.license =
|
13
|
+
spec.license = "MIT"
|
13
14
|
spec.version = Dry::System::VERSION.dup
|
14
15
|
|
15
16
|
spec.summary = "Organize your code into reusable components"
|
16
17
|
spec.description = spec.summary
|
17
|
-
spec.homepage =
|
18
|
+
spec.homepage = "https://dry-rb.org/gems/dry-system"
|
18
19
|
spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-system.gemspec", "lib/**/*"]
|
19
|
-
spec.bindir =
|
20
|
+
spec.bindir = "bin"
|
20
21
|
spec.executables = []
|
21
|
-
spec.require_paths = [
|
22
|
+
spec.require_paths = ["lib"]
|
22
23
|
|
23
|
-
spec.metadata[
|
24
|
-
spec.metadata[
|
25
|
-
spec.metadata[
|
26
|
-
spec.metadata[
|
24
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-system/blob/main/CHANGELOG.md"
|
26
|
+
spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-system"
|
27
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-system/issues"
|
27
28
|
|
28
|
-
spec.required_ruby_version = ">= 2.
|
29
|
+
spec.required_ruby_version = ">= 2.7.0"
|
29
30
|
|
30
31
|
# to update dependencies edit project.yml
|
31
|
-
spec.add_runtime_dependency "
|
32
|
-
spec.add_runtime_dependency "dry-
|
33
|
-
spec.add_runtime_dependency "dry-
|
34
|
-
spec.add_runtime_dependency "dry-
|
35
|
-
spec.add_runtime_dependency "dry-core", "~> 0.3", ">= 0.3.1"
|
36
|
-
spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
|
37
|
-
spec.add_runtime_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
|
38
|
-
spec.add_runtime_dependency "dry-struct", "~> 1.0"
|
32
|
+
spec.add_runtime_dependency "dry-auto_inject", "~> 1.0", "< 2"
|
33
|
+
spec.add_runtime_dependency "dry-configurable", "~> 1.0", "< 2"
|
34
|
+
spec.add_runtime_dependency "dry-core", "~> 1.0", "< 2"
|
35
|
+
spec.add_runtime_dependency "dry-inflector", "~> 1.0", "< 2"
|
39
36
|
|
40
37
|
spec.add_development_dependency "bundler"
|
41
38
|
spec.add_development_dependency "rake"
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/system/constants"
|
4
|
-
require "dry/system/magic_comments_parser"
|
5
|
-
require "dry/system/auto_registrar/configuration"
|
6
4
|
|
7
5
|
module Dry
|
8
6
|
module System
|
@@ -17,83 +15,30 @@ module Dry
|
|
17
15
|
class AutoRegistrar
|
18
16
|
attr_reader :container
|
19
17
|
|
20
|
-
attr_reader :config
|
21
|
-
|
22
18
|
def initialize(container)
|
23
19
|
@container = container
|
24
|
-
@config = container.config
|
25
20
|
end
|
26
21
|
|
27
22
|
# @api private
|
28
23
|
def finalize!
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# @api private
|
33
|
-
def call(dir)
|
34
|
-
registration_config = Configuration.new
|
35
|
-
yield(registration_config) if block_given?
|
36
|
-
components(dir).each do |component|
|
37
|
-
next if !component.auto_register? || registration_config.exclude.(component)
|
38
|
-
|
39
|
-
container.require_component(component) do
|
40
|
-
register(component.identifier, memoize: registration_config.memoize) {
|
41
|
-
registration_config.instance.(component)
|
42
|
-
}
|
43
|
-
end
|
24
|
+
container.component_dirs.each do |component_dir|
|
25
|
+
call(component_dir) if component_dir.auto_register?
|
44
26
|
end
|
45
27
|
end
|
46
28
|
|
47
|
-
private
|
48
|
-
|
49
|
-
# @api private
|
50
|
-
def components(dir)
|
51
|
-
files(dir)
|
52
|
-
.map { |file_name| [file_name, file_options(file_name)] }
|
53
|
-
.map { |file_name, options| component(relative_path(dir, file_name), **options) }
|
54
|
-
.reject { |component| registered?(component.identifier) }
|
55
|
-
end
|
56
|
-
|
57
29
|
# @api private
|
58
|
-
def
|
59
|
-
|
30
|
+
def call(component_dir)
|
31
|
+
component_dir.each_component do |component|
|
32
|
+
next unless register_component?(component)
|
60
33
|
|
61
|
-
|
62
|
-
raise ComponentsDirMissing, "Components dir '#{components_dir}' not found"
|
34
|
+
container.register(component.key, memoize: component.memoize?) { component.instance }
|
63
35
|
end
|
64
|
-
|
65
|
-
::Dir["#{components_dir}/**/#{RB_GLOB}"].sort
|
66
|
-
end
|
67
|
-
|
68
|
-
# @api private
|
69
|
-
def relative_path(dir, file_path)
|
70
|
-
dir_root = root.join(dir.to_s.split("/")[0])
|
71
|
-
file_path.to_s.sub("#{dir_root}/", "").sub(RB_EXT, EMPTY_STRING)
|
72
36
|
end
|
73
37
|
|
74
|
-
|
75
|
-
def file_options(file_name)
|
76
|
-
MagicCommentsParser.(file_name)
|
77
|
-
end
|
78
|
-
|
79
|
-
# @api private
|
80
|
-
def component(path, **options)
|
81
|
-
container.component(path, **options)
|
82
|
-
end
|
83
|
-
|
84
|
-
# @api private
|
85
|
-
def root
|
86
|
-
container.root
|
87
|
-
end
|
88
|
-
|
89
|
-
# @api private
|
90
|
-
def registered?(name)
|
91
|
-
container.registered?(name)
|
92
|
-
end
|
38
|
+
private
|
93
39
|
|
94
|
-
|
95
|
-
|
96
|
-
container.register(*args, &block)
|
40
|
+
def register_component?(component)
|
41
|
+
!container.registered?(component.key) && component.auto_register?
|
97
42
|
end
|
98
43
|
end
|
99
44
|
end
|
data/lib/dry/system/component.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
|
5
|
-
require "dry-equalizer"
|
3
|
+
require "pathname"
|
6
4
|
require "dry/inflector"
|
7
|
-
require "dry/system/loader"
|
8
5
|
require "dry/system/errors"
|
9
6
|
require "dry/system/constants"
|
10
7
|
|
@@ -14,157 +11,180 @@ module Dry
|
|
14
11
|
# They expose an API to query this information and use a configurable
|
15
12
|
# loader object to initialize class instances.
|
16
13
|
#
|
17
|
-
# Components are created automatically through auto-registration and can be
|
18
|
-
# accessed through `Container.auto_register!` which yields them.
|
19
|
-
#
|
20
14
|
# @api public
|
21
15
|
class Component
|
22
|
-
include Dry::Equalizer(:identifier, :
|
16
|
+
include Dry::Equalizer(:identifier, :file_path, :namespace, :options)
|
23
17
|
|
24
18
|
DEFAULT_OPTIONS = {
|
25
|
-
|
26
|
-
|
27
|
-
inflector: Dry::Inflector.new
|
19
|
+
inflector: Dry::Inflector.new,
|
20
|
+
loader: Loader
|
28
21
|
}.freeze
|
29
22
|
|
30
23
|
# @!attribute [r] identifier
|
31
|
-
# @return [String] component's unique identifier
|
24
|
+
# @return [String] the component's unique identifier
|
32
25
|
attr_reader :identifier
|
33
26
|
|
34
|
-
# @!attribute [r]
|
35
|
-
# @return [
|
36
|
-
attr_reader :
|
27
|
+
# @!attribute [r] file_path
|
28
|
+
# @return [Pathname] the component's source file path
|
29
|
+
attr_reader :file_path
|
37
30
|
|
38
|
-
# @!attribute [r]
|
39
|
-
# @return [
|
40
|
-
attr_reader :
|
31
|
+
# @!attribute [r] namespace
|
32
|
+
# @return [Dry::System::Config::Namespace] the component's namespace
|
33
|
+
attr_reader :namespace
|
41
34
|
|
42
35
|
# @!attribute [r] options
|
43
|
-
# @return [Hash] component's options
|
36
|
+
# @return [Hash] the component's options
|
44
37
|
attr_reader :options
|
45
38
|
|
46
|
-
# @!attribute [r] loader
|
47
|
-
# @return [Object#call] component's loader object
|
48
|
-
attr_reader :loader
|
49
|
-
|
50
39
|
# @api private
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
ns, sep, inflector = options.values_at(:namespace, :separator, :inflector)
|
57
|
-
identifier = extract_identifier(name, ns, sep)
|
58
|
-
|
59
|
-
path = name.to_s.gsub(sep, PATH_SEPARATOR)
|
60
|
-
loader = options.fetch(:loader, Loader).new(path, inflector)
|
61
|
-
|
62
|
-
super(identifier, path, options.merge(loader: loader))
|
63
|
-
end
|
40
|
+
def initialize(identifier, file_path:, namespace:, **options)
|
41
|
+
@identifier = identifier
|
42
|
+
@file_path = Pathname(file_path)
|
43
|
+
@namespace = namespace
|
44
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
64
45
|
end
|
65
46
|
|
47
|
+
# Returns true, indicating that the component is directly loadable from the files
|
48
|
+
# managed by the container
|
49
|
+
#
|
50
|
+
# This is the inverse of {IndirectComponent#loadable?}
|
51
|
+
#
|
52
|
+
# @return [TrueClass]
|
53
|
+
#
|
66
54
|
# @api private
|
67
|
-
def
|
68
|
-
|
69
|
-
identifier = ns ? remove_namespace_from_name(name_s, ns) : name_s
|
70
|
-
|
71
|
-
identifier.scan(WORD_REGEX).join(sep)
|
55
|
+
def loadable?
|
56
|
+
true
|
72
57
|
end
|
73
58
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
59
|
+
# Returns the component's instance
|
60
|
+
#
|
61
|
+
# @return [Object] component's class instance
|
62
|
+
# @api public
|
63
|
+
def instance(*args)
|
64
|
+
options[:instance]&.call(self, *args) || loader.call(self, *args)
|
79
65
|
end
|
66
|
+
ruby2_keywords(:instance) if respond_to?(:ruby2_keywords, true)
|
80
67
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
68
|
+
# Returns the component's unique key
|
69
|
+
#
|
70
|
+
# @return [String] the key
|
71
|
+
#
|
72
|
+
# @see Identifier#key
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def key
|
76
|
+
identifier.key
|
84
77
|
end
|
85
78
|
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
79
|
+
# Returns the root namespace segment of the component's key, as a symbol
|
80
|
+
#
|
81
|
+
# @see Identifier#root_key
|
82
|
+
#
|
83
|
+
# @return [Symbol] the root key
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def root_key
|
87
|
+
identifier.root_key
|
94
88
|
end
|
95
89
|
|
96
|
-
# Returns
|
90
|
+
# Returns a path-delimited representation of the compnent, appropriate for passing
|
91
|
+
# to `Kernel#require` to require its source file
|
97
92
|
#
|
98
|
-
#
|
99
|
-
# class MyApp < Dry::System::Container
|
100
|
-
# configure do |config|
|
101
|
-
# config.name = :my_app
|
102
|
-
# config.root = Pathname('/my/app')
|
103
|
-
# end
|
93
|
+
# The path takes into account the rules of the namespace used to load the component.
|
104
94
|
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
# constant.create
|
109
|
-
# end
|
110
|
-
# end
|
95
|
+
# @example Component from a root namespace
|
96
|
+
# component.key # => "articles.create"
|
97
|
+
# component.require_path # => "articles/create"
|
111
98
|
#
|
112
|
-
# @
|
99
|
+
# @example Component from an "admin/" path namespace (with `key: nil`)
|
100
|
+
# component.key # => "articles.create"
|
101
|
+
# component.require_path # => "admin/articles/create"
|
102
|
+
#
|
103
|
+
# @see Config::Namespaces#add
|
104
|
+
# @see Config::Namespace
|
105
|
+
#
|
106
|
+
# @return [String] the require path
|
113
107
|
#
|
114
108
|
# @api public
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
def bootable?
|
122
|
-
false
|
123
|
-
end
|
124
|
-
|
125
|
-
# @api private
|
126
|
-
def file_exists?(paths)
|
127
|
-
paths.any? { |path| path.join(file).exist? }
|
109
|
+
def require_path
|
110
|
+
if namespace.path
|
111
|
+
"#{namespace.path}#{PATH_SEPARATOR}#{path_in_namespace}"
|
112
|
+
else
|
113
|
+
path_in_namespace
|
114
|
+
end
|
128
115
|
end
|
129
116
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
117
|
+
# Returns an "underscored", path-delimited representation of the component,
|
118
|
+
# appropriate for passing to the inflector for constantizing
|
119
|
+
#
|
120
|
+
# The const path takes into account the rules of the namespace used to load the
|
121
|
+
# component.
|
122
|
+
#
|
123
|
+
# @example Component from a namespace with `const: nil`
|
124
|
+
# component.key # => "articles.create_article"
|
125
|
+
# component.const_path # => "articles/create_article"
|
126
|
+
# component.inflector.constantize(component.const_path) # => Articles::CreateArticle
|
127
|
+
#
|
128
|
+
# @example Component from a namespace with `const: "admin"`
|
129
|
+
# component.key # => "articles.create_article"
|
130
|
+
# component.const_path # => "admin/articles/create_article"
|
131
|
+
# component.inflector.constantize(component.const_path) # => Admin::Articles::CreateArticle
|
132
|
+
#
|
133
|
+
# @see Config::Namespaces#add
|
134
|
+
# @see Config::Namespace
|
135
|
+
#
|
136
|
+
# @return [String] the const path
|
137
|
+
#
|
138
|
+
# @api public
|
139
|
+
def const_path
|
140
|
+
namespace_const_path = namespace.const&.gsub(KEY_SEPARATOR, PATH_SEPARATOR)
|
136
141
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
+
if namespace_const_path
|
143
|
+
"#{namespace_const_path}#{PATH_SEPARATOR}#{path_in_namespace}"
|
144
|
+
else
|
145
|
+
path_in_namespace
|
146
|
+
end
|
142
147
|
end
|
143
148
|
|
144
149
|
# @api private
|
145
|
-
def
|
146
|
-
options
|
150
|
+
def loader
|
151
|
+
options.fetch(:loader)
|
147
152
|
end
|
148
153
|
|
149
154
|
# @api private
|
150
|
-
def
|
151
|
-
options
|
155
|
+
def inflector
|
156
|
+
options.fetch(:inflector)
|
152
157
|
end
|
153
158
|
|
154
159
|
# @api private
|
155
160
|
def auto_register?
|
156
|
-
|
161
|
+
callable_option?(options[:auto_register])
|
157
162
|
end
|
158
163
|
|
159
164
|
# @api private
|
160
|
-
def
|
161
|
-
|
165
|
+
def memoize?
|
166
|
+
callable_option?(options[:memoize])
|
162
167
|
end
|
163
168
|
|
164
169
|
private
|
165
170
|
|
166
|
-
def
|
167
|
-
|
171
|
+
def path_in_namespace
|
172
|
+
identifier_in_namespace =
|
173
|
+
if namespace.key
|
174
|
+
identifier.namespaced(from: namespace.key, to: nil)
|
175
|
+
else
|
176
|
+
identifier
|
177
|
+
end
|
178
|
+
|
179
|
+
identifier_in_namespace.key_with_separator(PATH_SEPARATOR)
|
180
|
+
end
|
181
|
+
|
182
|
+
def callable_option?(value)
|
183
|
+
if value.respond_to?(:call)
|
184
|
+
!!value.call(self)
|
185
|
+
else
|
186
|
+
!!value
|
187
|
+
end
|
168
188
|
end
|
169
189
|
end
|
170
190
|
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "dry/system/constants"
|
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 the given key if a matching source file is found within
|
32
|
+
# the component dir
|
33
|
+
#
|
34
|
+
# This searches according to the component dir's configured namespaces, in order of
|
35
|
+
# definition, with the first match returned as the component.
|
36
|
+
#
|
37
|
+
# @param key [String] the component's key
|
38
|
+
# @return [Dry::System::Component, nil] the component, if found
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def component_for_key(key)
|
42
|
+
config.namespaces.each do |namespace|
|
43
|
+
identifier = Identifier.new(key)
|
44
|
+
|
45
|
+
next unless identifier.start_with?(namespace.key)
|
46
|
+
|
47
|
+
if (file_path = find_component_file(identifier, namespace))
|
48
|
+
return build_component(identifier, namespace, file_path)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def each_component
|
56
|
+
return enum_for(:each_component) unless block_given?
|
57
|
+
|
58
|
+
each_file do |file_path, namespace|
|
59
|
+
yield component_for_path(file_path, namespace)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def each_file
|
66
|
+
return enum_for(:each_file) unless block_given?
|
67
|
+
|
68
|
+
raise ComponentDirNotFoundError, full_path unless Dir.exist?(full_path)
|
69
|
+
|
70
|
+
config.namespaces.each do |namespace|
|
71
|
+
files(namespace).each do |file|
|
72
|
+
yield file, namespace
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def files(namespace)
|
78
|
+
if namespace.path?
|
79
|
+
Dir[File.join(full_path, namespace.path, "**", RB_GLOB)].sort
|
80
|
+
else
|
81
|
+
non_root_paths = config.namespaces.to_a.reject(&:root?).map(&:path)
|
82
|
+
|
83
|
+
Dir[File.join(full_path, "**", RB_GLOB)].reject { |file_path|
|
84
|
+
Pathname(file_path).relative_path_from(full_path).to_s.start_with?(*non_root_paths)
|
85
|
+
}.sort
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the full path of the component directory
|
90
|
+
#
|
91
|
+
# @return [Pathname]
|
92
|
+
def full_path
|
93
|
+
container.root.join(path)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns a component for a full path to a Ruby source file within the component dir
|
97
|
+
#
|
98
|
+
# @param path [String] the full path to the file
|
99
|
+
# @return [Dry::System::Component] the component
|
100
|
+
def component_for_path(path, namespace)
|
101
|
+
key = Pathname(path).relative_path_from(full_path).to_s
|
102
|
+
.sub(RB_EXT, EMPTY_STRING)
|
103
|
+
.scan(WORD_REGEX)
|
104
|
+
.join(KEY_SEPARATOR)
|
105
|
+
|
106
|
+
identifier = Identifier.new(key)
|
107
|
+
.namespaced(
|
108
|
+
from: namespace.path&.gsub(PATH_SEPARATOR, KEY_SEPARATOR),
|
109
|
+
to: namespace.key
|
110
|
+
)
|
111
|
+
|
112
|
+
build_component(identifier, namespace, path)
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_component_file(identifier, namespace)
|
116
|
+
# To properly find the file within a namespace with a key, we should strip the key
|
117
|
+
# from beginning of our given identifier
|
118
|
+
if namespace.key
|
119
|
+
identifier = identifier.namespaced(from: namespace.key, to: nil)
|
120
|
+
end
|
121
|
+
|
122
|
+
file_name = "#{identifier.key_with_separator(PATH_SEPARATOR)}#{RB_EXT}"
|
123
|
+
|
124
|
+
component_file =
|
125
|
+
if namespace.path?
|
126
|
+
full_path.join(namespace.path, file_name)
|
127
|
+
else
|
128
|
+
full_path.join(file_name)
|
129
|
+
end
|
130
|
+
|
131
|
+
component_file if component_file.exist?
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_component(identifier, namespace, file_path)
|
135
|
+
options = {
|
136
|
+
inflector: container.config.inflector,
|
137
|
+
**component_options,
|
138
|
+
**MagicCommentsParser.(file_path)
|
139
|
+
}
|
140
|
+
|
141
|
+
Component.new(
|
142
|
+
identifier,
|
143
|
+
namespace: namespace,
|
144
|
+
file_path: file_path,
|
145
|
+
**options
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def component_options
|
150
|
+
{
|
151
|
+
auto_register: auto_register,
|
152
|
+
loader: loader,
|
153
|
+
instance: instance,
|
154
|
+
memoize: memoize
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def method_missing(name, *args, &block)
|
159
|
+
if config.respond_to?(name)
|
160
|
+
config.public_send(name, *args, &block)
|
161
|
+
else
|
162
|
+
super
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def respond_to_missing?(name, include_all = false)
|
167
|
+
config.respond_to?(name) || super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|