plumbum 0.1.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 +7 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE +22 -0
- data/README.md +163 -0
- data/lib/plumbum/consumer.rb +148 -0
- data/lib/plumbum/consumers/class_methods.rb +319 -0
- data/lib/plumbum/consumers/instance_methods.rb +105 -0
- data/lib/plumbum/consumers/scoped_consumer.rb +25 -0
- data/lib/plumbum/consumers.rb +12 -0
- data/lib/plumbum/errors/immutable_error.rb +8 -0
- data/lib/plumbum/errors/invalid_dependency_error.rb +8 -0
- data/lib/plumbum/errors/invalid_key_error.rb +8 -0
- data/lib/plumbum/errors/missing_dependency_error.rb +8 -0
- data/lib/plumbum/errors.rb +13 -0
- data/lib/plumbum/many_provider.rb +86 -0
- data/lib/plumbum/one_provider.rb +56 -0
- data/lib/plumbum/parameters.rb +39 -0
- data/lib/plumbum/provider.rb +148 -0
- data/lib/plumbum/providers/lazy.rb +26 -0
- data/lib/plumbum/providers/plural.rb +31 -0
- data/lib/plumbum/providers/singular.rb +26 -0
- data/lib/plumbum/providers.rb +12 -0
- data/lib/plumbum/rspec/deferred/consumer_examples.rb +2122 -0
- data/lib/plumbum/rspec/deferred/provider_examples.rb +848 -0
- data/lib/plumbum/rspec/deferred.rb +8 -0
- data/lib/plumbum/rspec/stub_provider.rb +75 -0
- data/lib/plumbum/rspec.rb +8 -0
- data/lib/plumbum/version.rb +57 -0
- data/lib/plumbum.rb +25 -0
- metadata +91 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sleeping_king_studios/tools'
|
|
4
|
+
|
|
5
|
+
require 'plumbum/consumers'
|
|
6
|
+
|
|
7
|
+
module Plumbum::Consumers
|
|
8
|
+
# Class methods for defining the Consumer interface.
|
|
9
|
+
module ClassMethods # rubocop:disable Metrics/ModuleLength
|
|
10
|
+
PROVIDER_METHODS = %i[get has?].freeze
|
|
11
|
+
private_constant :PROVIDER_METHODS
|
|
12
|
+
|
|
13
|
+
UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
|
|
14
|
+
private_constant :UNDEFINED
|
|
15
|
+
|
|
16
|
+
class << self # rubocop:disable Metrics/ClassLength
|
|
17
|
+
INVALID_OPTIONS_FOR_METHOD_DEPENDENCY = {
|
|
18
|
+
memoize: true,
|
|
19
|
+
predicate: false
|
|
20
|
+
}.freeze
|
|
21
|
+
private_constant :INVALID_OPTIONS_FOR_METHOD_DEPENDENCY
|
|
22
|
+
|
|
23
|
+
# @private
|
|
24
|
+
def define_delegated_method( # rubocop:disable Metrics/MethodLength
|
|
25
|
+
receiver,
|
|
26
|
+
key:,
|
|
27
|
+
method_name:,
|
|
28
|
+
path:,
|
|
29
|
+
**options
|
|
30
|
+
)
|
|
31
|
+
validate_delegated_method_options(path:, **options)
|
|
32
|
+
|
|
33
|
+
*path, inner_name = path
|
|
34
|
+
method_name = method_name[1..] if method_name.start_with?('#')
|
|
35
|
+
inner_name = inner_name[1..]
|
|
36
|
+
|
|
37
|
+
dependency_methods_for(receiver)
|
|
38
|
+
.define_method(method_name) do |*args, **keywords, &block|
|
|
39
|
+
inner = get_scoped_plumbum_dependency(key, path:)
|
|
40
|
+
|
|
41
|
+
inner.public_send(inner_name, *args, **keywords, &block)
|
|
42
|
+
end # rubocop:disable Style/MultilineBlockChain
|
|
43
|
+
.tap do |method_name|
|
|
44
|
+
receiver.send(:private, method_name) if options[:private]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @private
|
|
49
|
+
def define_memoized_reader( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
50
|
+
receiver,
|
|
51
|
+
default:,
|
|
52
|
+
key:,
|
|
53
|
+
method_name:,
|
|
54
|
+
optional:,
|
|
55
|
+
path:,
|
|
56
|
+
**options
|
|
57
|
+
)
|
|
58
|
+
dependency_methods_for(receiver)
|
|
59
|
+
.define_method(method_name) do
|
|
60
|
+
if (@plumbum_dependencies ||= {}).key?(key)
|
|
61
|
+
return @plumbum_dependencies[key]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
get_scoped_plumbum_dependency(key, default:, optional:, path:)
|
|
65
|
+
.tap do |value|
|
|
66
|
+
@plumbum_dependencies[key] = value unless value.nil?
|
|
67
|
+
end
|
|
68
|
+
end # rubocop:disable Style/MultilineBlockChain
|
|
69
|
+
.tap do |method_name|
|
|
70
|
+
receiver.send(:private, method_name) if options[:private]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
def define_methods(receiver, key:, method_name:, memoize:, predicate:, **) # rubocop:disable Metrics/ParameterLists
|
|
76
|
+
define_predicate(receiver, key:, method_name:, **) if predicate
|
|
77
|
+
|
|
78
|
+
if memoize
|
|
79
|
+
define_memoized_reader(receiver, key:, method_name:, **)
|
|
80
|
+
else
|
|
81
|
+
define_reader(receiver, key:, method_name:, **)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
method_name.to_sym
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @private
|
|
88
|
+
def define_predicate(receiver, key:, method_name:, **options)
|
|
89
|
+
method_name = :"#{method_name}?"
|
|
90
|
+
|
|
91
|
+
dependency_methods_for(receiver)
|
|
92
|
+
.define_method(method_name) do
|
|
93
|
+
has_plumbum_dependency?(key)
|
|
94
|
+
end # rubocop:disable Style/MultilineBlockChain
|
|
95
|
+
.tap do |method_name|
|
|
96
|
+
receiver.send(:private, method_name) if options[:private]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @private
|
|
101
|
+
def define_reader( # rubocop:disable Metrics/ParameterLists
|
|
102
|
+
receiver,
|
|
103
|
+
default:,
|
|
104
|
+
key:,
|
|
105
|
+
method_name:,
|
|
106
|
+
optional:,
|
|
107
|
+
path:,
|
|
108
|
+
**options
|
|
109
|
+
)
|
|
110
|
+
dependency_methods_for(receiver)
|
|
111
|
+
.define_method(method_name) do
|
|
112
|
+
get_scoped_plumbum_dependency(key, default:, optional:, path:)
|
|
113
|
+
end # rubocop:disable Style/MultilineBlockChain
|
|
114
|
+
.tap do |method_name|
|
|
115
|
+
receiver.send(:private, method_name) if options[:private]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @private
|
|
120
|
+
def dependency_methods_for(receiver)
|
|
121
|
+
if receiver.const_defined?(:PlumbumDependencyMethods, false)
|
|
122
|
+
return receiver.const_get(:PlumbumDependencyMethods)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Module
|
|
126
|
+
.new
|
|
127
|
+
.tap { |mod| receiver.include mod }
|
|
128
|
+
.then { |mod| receiver.const_set(:PlumbumDependencyMethods, mod) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @private
|
|
132
|
+
def split_key(key, as:, scope:)
|
|
133
|
+
ClassMethods.validate_name(key, as: :key)
|
|
134
|
+
ClassMethods.validate_name(scope, as: :scope) if scope
|
|
135
|
+
|
|
136
|
+
key = "#{scope}.#{key}" if scope
|
|
137
|
+
|
|
138
|
+
segments = key.to_s.split('.')
|
|
139
|
+
|
|
140
|
+
return [key, as || key, nil] if segments.size == 1
|
|
141
|
+
|
|
142
|
+
[segments.first, as || segments.last, segments[1..]]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @private
|
|
146
|
+
def validate_name(value, as: nil)
|
|
147
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
148
|
+
.instance
|
|
149
|
+
.assertions
|
|
150
|
+
.validate_name(value, as:)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def validate_delegated_method_options(path: nil, **options) # rubocop:disable Metrics/MethodLength
|
|
156
|
+
if path.nil?
|
|
157
|
+
message =
|
|
158
|
+
'delegated methods must have a scope - use a scoped key or pass ' \
|
|
159
|
+
'the :scope option to #dependency'
|
|
160
|
+
|
|
161
|
+
raise ArgumentError, message
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
INVALID_OPTIONS_FOR_METHOD_DEPENDENCY.each \
|
|
165
|
+
do |option_name, default_value|
|
|
166
|
+
next if options[option_name] == default_value
|
|
167
|
+
|
|
168
|
+
raise ArgumentError,
|
|
169
|
+
"invalid option #{option_name.inspect} for method dependency"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @overload plumbum_dependency(*keys, as: nil, memoize: true, optional: false, predicate: false, scope: nil)
|
|
175
|
+
# Defines injected dependencies for instances of the class.
|
|
176
|
+
#
|
|
177
|
+
# @param keys [Array<String, Symbol>] the keys for the dependency. A new
|
|
178
|
+
# dependency will be defined for each key using the same options.
|
|
179
|
+
# @param as [String, Symbol] the method name used to define dependency
|
|
180
|
+
# methods. Defaults to the key. Cannot be used with multiple keys.
|
|
181
|
+
# @param default [Object, Proc] if given, the default value will be
|
|
182
|
+
# returned when the consumer does not have a provider for the
|
|
183
|
+
# dependency. If the default value is a Proc, the default will be lazily
|
|
184
|
+
# evaluated in the context of the consumer. A default value *will not*
|
|
185
|
+
# be returned if a matching provider is defined but does not support the
|
|
186
|
+
# given scope.
|
|
187
|
+
# @param memoize [true, false] if true, memoizes the value of the
|
|
188
|
+
# dependency the first time it is successfully called. Defaults to true.
|
|
189
|
+
# @param optional [true, false] if true, calling the dependency returns
|
|
190
|
+
# nil if the dependency is not defined. Defaults to false.
|
|
191
|
+
# @param predicate [true, false] if true, also defines a predicate method
|
|
192
|
+
# that returns true if the dependency has a defined value. Defaults to
|
|
193
|
+
# false.
|
|
194
|
+
# @param private [true, false] if true, the generated methods will be
|
|
195
|
+
# generated with private visibility. Defaults to false.
|
|
196
|
+
# @param scope [String, Symbol] if given, combined with the key or keys to
|
|
197
|
+
# determine the dependency name and the path from the dependency to the
|
|
198
|
+
# returned value.
|
|
199
|
+
#
|
|
200
|
+
# @return [Symbol, Array<Symbol>] the name of the generated method, or the
|
|
201
|
+
# method names if given more than one key.
|
|
202
|
+
#
|
|
203
|
+
# @raise [ArgumentError] if any key is not a String or Symbol, or is
|
|
204
|
+
# empty.
|
|
205
|
+
def plumbum_dependency( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
206
|
+
*keys,
|
|
207
|
+
as: nil,
|
|
208
|
+
default: UNDEFINED,
|
|
209
|
+
memoize: true,
|
|
210
|
+
optional: false,
|
|
211
|
+
predicate: false,
|
|
212
|
+
private: false,
|
|
213
|
+
scope: nil
|
|
214
|
+
)
|
|
215
|
+
if keys.size > 1 && as
|
|
216
|
+
raise ArgumentError, 'invalid option :as when providing multiple keys'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
scoped_keys = keys.map do |key|
|
|
220
|
+
define_plumbum_dependency(
|
|
221
|
+
key,
|
|
222
|
+
as:,
|
|
223
|
+
default:,
|
|
224
|
+
memoize:,
|
|
225
|
+
optional:,
|
|
226
|
+
predicate:,
|
|
227
|
+
private:,
|
|
228
|
+
scope:
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
scoped_keys.size == 1 ? scoped_keys.first : scoped_keys
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# @param cache [true, false] if false,.clears the memoized value and
|
|
236
|
+
# recalculates the keys.
|
|
237
|
+
#
|
|
238
|
+
# @return [Set<String>] the keys of the dependencies declared by the class
|
|
239
|
+
# and its ancestors.
|
|
240
|
+
def plumbum_dependency_keys(cache: true)
|
|
241
|
+
@plumbum_dependency_keys = nil if cache == false
|
|
242
|
+
|
|
243
|
+
return @plumbum_dependency_keys if @plumbum_dependency_keys
|
|
244
|
+
|
|
245
|
+
@plumbum_dependency_keys = ancestors.reduce(Set.new) do |set, ancestor|
|
|
246
|
+
next set unless ancestor.respond_to?(:own_plumbum_dependency_keys, true)
|
|
247
|
+
|
|
248
|
+
set.union(ancestor.own_plumbum_dependency_keys)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Registers a provider for the class.
|
|
253
|
+
#
|
|
254
|
+
# @param provider [#get, #has?] the provider to register.
|
|
255
|
+
#
|
|
256
|
+
# @return void
|
|
257
|
+
def plumbum_provider(provider)
|
|
258
|
+
PROVIDER_METHODS.each do |method_name|
|
|
259
|
+
next if provider.respond_to?(method_name)
|
|
260
|
+
|
|
261
|
+
# @todo [0.2] use tools error message for assertions.respond_to
|
|
262
|
+
message = "provider does not respond to ##{method_name}"
|
|
263
|
+
|
|
264
|
+
raise ArgumentError, message
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
own_plumbum_providers.prepend(provider)
|
|
268
|
+
|
|
269
|
+
nil
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# @param cache [true, false] if false,.clears the memoized value and
|
|
273
|
+
# reaggregates the providers.
|
|
274
|
+
#
|
|
275
|
+
# @return [Array<Plumbum::Provider>] the providers defined for the class.
|
|
276
|
+
def plumbum_providers(cache: true)
|
|
277
|
+
@plumbum_providers = nil if cache == false
|
|
278
|
+
|
|
279
|
+
@plumbum_providers ||= each_plumbum_provider.to_a
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
protected
|
|
283
|
+
|
|
284
|
+
def own_plumbum_dependency_keys
|
|
285
|
+
@own_plumbum_dependency_keys ||= Set.new
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def own_plumbum_providers
|
|
289
|
+
@own_plumbum_providers ||= []
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private
|
|
293
|
+
|
|
294
|
+
def define_plumbum_dependency(key, as: nil, scope: nil, **)
|
|
295
|
+
ClassMethods.validate_name(as, as: :as) unless as.nil?
|
|
296
|
+
|
|
297
|
+
key, method_name, path = ClassMethods.split_key(key, as:, scope:)
|
|
298
|
+
|
|
299
|
+
own_plumbum_dependency_keys << key.to_s
|
|
300
|
+
|
|
301
|
+
if method_name.start_with?('#') || path&.last&.start_with?('#')
|
|
302
|
+
ClassMethods
|
|
303
|
+
.define_delegated_method(self, key:, method_name:, path:, **)
|
|
304
|
+
else
|
|
305
|
+
ClassMethods.define_methods(self, key:, method_name:, path:, **)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def each_plumbum_provider(&)
|
|
310
|
+
return enum_for(:each_plumbum_provider) unless block_given?
|
|
311
|
+
|
|
312
|
+
ancestors.reverse_each do |ancestor|
|
|
313
|
+
next unless ancestor.respond_to?(:own_plumbum_providers, true)
|
|
314
|
+
|
|
315
|
+
ancestor.own_plumbum_providers.each(&)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sleeping_king_studios/tools'
|
|
4
|
+
|
|
5
|
+
require 'plumbum/consumers'
|
|
6
|
+
|
|
7
|
+
module Plumbum::Consumers
|
|
8
|
+
# Instance methods for defining the Consumer interface.
|
|
9
|
+
module InstanceMethods
|
|
10
|
+
UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
|
|
11
|
+
private_constant :UNDEFINED
|
|
12
|
+
|
|
13
|
+
# Retrieves the dependency with the specified key.
|
|
14
|
+
#
|
|
15
|
+
# @param key [String, Symbol] the key for the requested dependency.
|
|
16
|
+
# @param optional [true, false] if true, returns nil if the dependency is
|
|
17
|
+
# not defined. Defaults to false.
|
|
18
|
+
#
|
|
19
|
+
# @return [Object] the dependency value.
|
|
20
|
+
#
|
|
21
|
+
# @raise [ArgumentError] if the key is not a String or Symbol, or is empty.
|
|
22
|
+
# @raise [Plumbum::Errors::MissingDependencyError] if no matching dependency
|
|
23
|
+
# is found.
|
|
24
|
+
def get_plumbum_dependency(key, optional: false)
|
|
25
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
26
|
+
.instance
|
|
27
|
+
.assertions
|
|
28
|
+
.validate_name(key, as: :key)
|
|
29
|
+
|
|
30
|
+
find_plumbum_dependency(key) do
|
|
31
|
+
handle_missing_plumbum_dependency(key, optional:)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Checks if the dependency with the given key is defined.
|
|
36
|
+
#
|
|
37
|
+
# @param key [String, Symbol] the key for the requested dependency.
|
|
38
|
+
#
|
|
39
|
+
# @return [true, false] true if the dependency is defined, otherwise false.
|
|
40
|
+
#
|
|
41
|
+
# @raise [ArgumentError] if the key is not a String or Symbol, or is empty.
|
|
42
|
+
def has_plumbum_dependency?(key) # rubocop:disable Naming/PredicatePrefix
|
|
43
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
44
|
+
.instance
|
|
45
|
+
.assertions
|
|
46
|
+
.validate_name(key, as: :key)
|
|
47
|
+
|
|
48
|
+
find_plumbum_dependency(key) { return false }
|
|
49
|
+
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def find_plumbum_dependency(key)
|
|
56
|
+
plumbum_providers.each do |provider|
|
|
57
|
+
return provider.get(key) if provider.has?(key)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
yield
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def get_scoped_plumbum_dependency( # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
64
|
+
key,
|
|
65
|
+
path:,
|
|
66
|
+
default: UNDEFINED,
|
|
67
|
+
optional: false
|
|
68
|
+
)
|
|
69
|
+
optional ||= default != UNDEFINED
|
|
70
|
+
dependency = get_plumbum_dependency(key, optional:)
|
|
71
|
+
|
|
72
|
+
if dependency.nil?
|
|
73
|
+
return if default == UNDEFINED
|
|
74
|
+
|
|
75
|
+
return default unless default.is_a?(Proc)
|
|
76
|
+
|
|
77
|
+
return instance_exec(&default)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return dependency if path.nil? || path.empty?
|
|
81
|
+
|
|
82
|
+
path.reduce(dependency) do |memo, method_name|
|
|
83
|
+
SleepingKingStudios::Tools::Toolbelt
|
|
84
|
+
.instance
|
|
85
|
+
.object_tools
|
|
86
|
+
.fetch(memo, method_name, indifferent_key: true)
|
|
87
|
+
end
|
|
88
|
+
rescue KeyError, IndexError, NoMethodError => exception # rubocop:disable Lint/ShadowedException
|
|
89
|
+
raise Plumbum::Errors::MissingDependencyError,
|
|
90
|
+
exception.message,
|
|
91
|
+
cause: exception
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def handle_missing_plumbum_dependency(key, optional: false)
|
|
95
|
+
return nil if optional
|
|
96
|
+
|
|
97
|
+
raise Plumbum::Errors::MissingDependencyError,
|
|
98
|
+
"dependency not found with key #{key.inspect}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def plumbum_providers
|
|
102
|
+
self.class.plumbum_providers
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sleeping_king_studios/tools'
|
|
4
|
+
|
|
5
|
+
require 'plumbum/consumers'
|
|
6
|
+
require 'plumbum/consumers/class_methods'
|
|
7
|
+
require 'plumbum/consumers/instance_methods'
|
|
8
|
+
|
|
9
|
+
module Plumbum::Consumers
|
|
10
|
+
# Consumer implementation with fully-scoped method names for compatibility.
|
|
11
|
+
#
|
|
12
|
+
# Use a scoped consumer when the standard Consumer DSL class methods (such as
|
|
13
|
+
# .dependency and .provider) might conflict with existing methods.
|
|
14
|
+
#
|
|
15
|
+
# @see Plumbum::Consumer
|
|
16
|
+
module ScopedConsumer
|
|
17
|
+
extend SleepingKingStudios::Tools::Toolbox::Mixin
|
|
18
|
+
include Plumbum::Consumers::InstanceMethods
|
|
19
|
+
|
|
20
|
+
# Class methods to extend when including Plumbum::Consumer.
|
|
21
|
+
module ClassMethods
|
|
22
|
+
include Plumbum::Consumers::ClassMethods
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plumbum'
|
|
4
|
+
|
|
5
|
+
module Plumbum
|
|
6
|
+
# Functionality for defining consumers.
|
|
7
|
+
module Consumers
|
|
8
|
+
autoload :ClassMethods, 'plumbum/consumers/class_methods'
|
|
9
|
+
autoload :InstanceMethods, 'plumbum/consumers/instance_methods'
|
|
10
|
+
autoload :ScopedConsumer, 'plumbum/consumers/scoped_consumer'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plumbum'
|
|
4
|
+
|
|
5
|
+
module Plumbum
|
|
6
|
+
# Namespace for exceptions raised when handling Plumbum errors.
|
|
7
|
+
module Errors
|
|
8
|
+
autoload :ImmutableError, 'plumbum/errors/immutable_error'
|
|
9
|
+
autoload :InvalidDependencyError, 'plumbum/errors/invalid_dependency_error'
|
|
10
|
+
autoload :InvalidKeyError, 'plumbum/errors/invalid_key_error'
|
|
11
|
+
autoload :MissingDependencyError, 'plumbum/errors/missing_dependency_error'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plumbum'
|
|
4
|
+
require 'plumbum/providers/plural'
|
|
5
|
+
|
|
6
|
+
module Plumbum
|
|
7
|
+
# Provider that provides a mapping of keys to values.
|
|
8
|
+
class ManyProvider
|
|
9
|
+
include Plumbum::Providers::Plural
|
|
10
|
+
|
|
11
|
+
# @param values [Hash{String, Symbol => Object}] the provided values.
|
|
12
|
+
# @param options [Hash] additional options for the provider.
|
|
13
|
+
def initialize(values: Plumbum::UNDEFINED, **options)
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
@values = normalize_values(values)
|
|
17
|
+
@options = validate_options(options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# (see Plumbum::Providers::Plural#values)
|
|
21
|
+
def values
|
|
22
|
+
@values == Plumbum::UNDEFINED ? {} : super.dup
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param values [Hash{String, Symbol => Object}] the updated values.
|
|
26
|
+
def values=(values)
|
|
27
|
+
validate_values(values)
|
|
28
|
+
|
|
29
|
+
values = values.transform_keys(&:to_s)
|
|
30
|
+
|
|
31
|
+
changed_values = find_changed_values(values)
|
|
32
|
+
|
|
33
|
+
changed_values.each_key { |key| require_mutable(key) }
|
|
34
|
+
|
|
35
|
+
@values = self.values.merge(changed_values)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def find_changed_values(updated_values)
|
|
41
|
+
missing_values = values.dup
|
|
42
|
+
changed_values = updated_values.each.with_object({}) \
|
|
43
|
+
do |(key, value), hsh|
|
|
44
|
+
missing_values.delete(key)
|
|
45
|
+
|
|
46
|
+
next if value == values[key]
|
|
47
|
+
|
|
48
|
+
hsh[key] = value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
missing_values.each_key { |key| changed_values[key] = Plumbum::UNDEFINED }
|
|
52
|
+
|
|
53
|
+
changed_values
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def get_value(key)
|
|
57
|
+
value = super
|
|
58
|
+
|
|
59
|
+
value == Plumbum::UNDEFINED ? nil : value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def has_value?(key, allow_undefined: false) # rubocop:disable Naming/PredicatePrefix
|
|
63
|
+
super && (allow_undefined || @values[key] != Plumbum::UNDEFINED)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def normalize_values(values)
|
|
67
|
+
return values if values == Plumbum::UNDEFINED
|
|
68
|
+
|
|
69
|
+
if values.is_a?(Array)
|
|
70
|
+
values = values.to_h { |key| [key, Plumbum::UNDEFINED] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
validate_values(values)
|
|
74
|
+
|
|
75
|
+
@values = values.transform_keys(&:to_s)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def validate_values(values)
|
|
79
|
+
tools.assertions.validate_instance_of(values, as: :values, expected: Hash)
|
|
80
|
+
|
|
81
|
+
values.each_key.with_index do |key, index|
|
|
82
|
+
tools.assertions.validate_name(key, as: :"values.keys[#{index}]")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plumbum'
|
|
4
|
+
require 'plumbum/providers/singular'
|
|
5
|
+
|
|
6
|
+
module Plumbum
|
|
7
|
+
# Provider that provides a single value for a specified key.
|
|
8
|
+
class OneProvider
|
|
9
|
+
include Plumbum::Providers::Singular
|
|
10
|
+
|
|
11
|
+
# @param key [String, Symbol] the key used to identify the provided value.
|
|
12
|
+
# @param value [Object] the provided value, if any.
|
|
13
|
+
# @param options [Hash] additional options for the provider.
|
|
14
|
+
def initialize(key, value: Plumbum::UNDEFINED, **options)
|
|
15
|
+
super()
|
|
16
|
+
|
|
17
|
+
tools
|
|
18
|
+
.assertions
|
|
19
|
+
.validate_name(key, as: :key)
|
|
20
|
+
|
|
21
|
+
@key = key.to_s
|
|
22
|
+
@value = value
|
|
23
|
+
@options = validate_options(options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [String, Symbol] the key used to identify the provided value.
|
|
27
|
+
attr_reader :key
|
|
28
|
+
|
|
29
|
+
# @return [Object, nil] the provided value, or nil if the value is not
|
|
30
|
+
# defined.
|
|
31
|
+
def value
|
|
32
|
+
@value == Plumbum::UNDEFINED ? nil : @value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param value [Object] the changed value.
|
|
36
|
+
def value=(value)
|
|
37
|
+
require_mutable(key)
|
|
38
|
+
|
|
39
|
+
set_value(key, value)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def get_value(key)
|
|
45
|
+
return nil if @value == Plumbum::UNDEFINED
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_value?(key, allow_undefined: false) # rubocop:disable Naming/PredicatePrefix
|
|
51
|
+
return false if !allow_undefined && @value == Plumbum::UNDEFINED
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plumbum'
|
|
4
|
+
require 'plumbum/many_provider'
|
|
5
|
+
|
|
6
|
+
module Plumbum
|
|
7
|
+
# Utility module that converts constructor parameters to a Provider.
|
|
8
|
+
module Parameters
|
|
9
|
+
# @overload initialize(*arguments, **keywords, &block)
|
|
10
|
+
# @param arguments [Array] the arguments passed to the constructor.
|
|
11
|
+
# @param keywords [Hash] the keywords passed to the constructor, including
|
|
12
|
+
# any injected dependencies.
|
|
13
|
+
# @param block [Proc] the block passed to the constructor.
|
|
14
|
+
def initialize(*, **keywords, &)
|
|
15
|
+
values, keywords = extract_plumbum_dependencies(keywords)
|
|
16
|
+
|
|
17
|
+
@plumbum_parameters_provider = Plumbum::ManyProvider.new(values:)
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def extract_plumbum_dependencies(keywords)
|
|
25
|
+
dependency_keys = self.class.dependency_keys
|
|
26
|
+
|
|
27
|
+
keywords
|
|
28
|
+
.partition { |key, _| dependency_keys.include?(key.to_s) }
|
|
29
|
+
.map(&:to_h)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def plumbum_providers
|
|
33
|
+
[
|
|
34
|
+
@plumbum_parameters_provider,
|
|
35
|
+
*super
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|