toys-core 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,9 +40,10 @@ module Toys
40
40
  # replace it outright, or leave it unmodified.
41
41
  #
42
42
  # Generally, a middleware is a class that implements the two methods defined
43
- # in this module: {Toys::Middleware#config} and {Toys::Middleware#run}. A
44
- # middleware can include this module to get default implementations that do
45
- # nothing, but this is not required.
43
+ # in this module: {Toys::Middleware#config} and {Toys::Middleware#run}. To
44
+ # get default implementations that do nothing, a middleware can
45
+ # `include Toys::Middleware` or subclass {Toys::Middleware::Base}, but this
46
+ # is not required.
46
47
  #
47
48
  module Middleware
48
49
  ##
@@ -89,5 +90,186 @@ module Toys
89
90
  def run(context) # rubocop:disable Lint/UnusedMethodArgument
90
91
  yield
91
92
  end
93
+
94
+ class << self
95
+ ##
96
+ # Create a middleware spec.
97
+ #
98
+ # @overload spec(middleware_object)
99
+ # Create a spec wrapping an existing middleware object
100
+ #
101
+ # @param middleware_object [Toys::Middleware] The middleware object
102
+ # @return [Toys::Middleware::Spec] A spec
103
+ #
104
+ # @overload spec(name, *args, **kwargs, &block)
105
+ # Create a spec indicating a given middleware name should be
106
+ # instantiated with the given arguments.
107
+ #
108
+ # @param name [String,Symbol,Class] The middleware name or class
109
+ # @param args [Array] The arguments to pass to the constructor
110
+ # @param kwargs [Hash] The keyword arguments to pass to the constructor
111
+ # @param block [Proc,nil] The block to pass to the constructor
112
+ # @return [Toys::Middleware::Spec] A spec
113
+ #
114
+ def spec(middleware, *args, **kwargs, &block)
115
+ if middleware.is_a?(::String) || middleware.is_a?(::Symbol) || middleware.is_a?(::Class)
116
+ Spec.new(nil, middleware, args, kwargs, block)
117
+ else
118
+ Spec.new(middleware, nil, nil, nil, nil)
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Create a middleware spec from an array specification.
124
+ #
125
+ # The array must be 1-4 elements long. The first element must be the
126
+ # middleware name or class. The other three arguments may include any or
127
+ # all of the following optional elements, in any order:
128
+ # * An array for the positional arguments to pass to the constructor
129
+ # * A hash for the keyword arguments to pass to the constructor
130
+ # * A proc for the block to pass to the constructor
131
+ #
132
+ # @param array [Array] The array input
133
+ # @return [Toys::Middleware::Spec] A spec
134
+ #
135
+ def spec_from_array(array)
136
+ middleware = array.first
137
+ if !middleware.is_a?(::String) && !middleware.is_a?(::Symbol) && !middleware.is_a?(::Class)
138
+ raise ::ArgumentError, "Bad middleware name: #{middleware.inspect}"
139
+ end
140
+ args = []
141
+ kwargs = {}
142
+ block = nil
143
+ array.slice(1..-1).each do |param|
144
+ case param
145
+ when ::Array
146
+ args += param
147
+ when ::Hash
148
+ kwargs = kwargs.merge(param)
149
+ when ::Proc
150
+ block = param
151
+ else
152
+ raise ::ArgumentError, "Bad param: #{param.inspect}"
153
+ end
154
+ end
155
+ Spec.new(nil, middleware, args, kwargs, block)
156
+ end
157
+
158
+ ##
159
+ # Resolve all arguments into an array of middleware specs. Each argument
160
+ # may be one of the following:
161
+ #
162
+ # * A {Toys::Middleware} object
163
+ # * A {Toys::Middleware::Spec}
164
+ # * An array whose first element is a middleware name or class, and the
165
+ # subsequent elements are params that define what to pass to the class
166
+ # constructor (see {Toys::Middleware.spec_from_array})
167
+ #
168
+ # @param items [Array<Toys::Middleware,Toys::Middleware::Spec,Array>]
169
+ # @return [Array<Toys::Middleware::Spec>]
170
+ #
171
+ def resolve_specs(*items)
172
+ items.map do |item|
173
+ case item
174
+ when ::Array
175
+ spec_from_array(item)
176
+ when Spec
177
+ item
178
+ else
179
+ spec(item)
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ ##
186
+ # A base class that provides default NOP implementations of the middleware
187
+ # interface. This base class may optionally be subclassed by a middleware
188
+ # implementation.
189
+ #
190
+ class Base
191
+ include Middleware
192
+ end
193
+
194
+ ##
195
+ # A middleware specification, including the middleware class and the
196
+ # arguments to pass to the constructor.
197
+ #
198
+ # Use {Toys::Middleware.spec} to create a middleware spec.
199
+ #
200
+ class Spec
201
+ ##
202
+ # Builds a middleware for this spec, given a ModuleLookup for middleware.
203
+ #
204
+ # If this spec wraps an existing middleware object, returns that object.
205
+ # Otherwise, constructs a middleware object from the spec.
206
+ #
207
+ # @param lookup [Toys::ModuleLookup] A module lookup to resolve
208
+ # middleware names
209
+ # @return [Toys::Middleware] The middleware
210
+ #
211
+ def build(lookup)
212
+ return @object unless @object.nil?
213
+ if @name.is_a?(::String) || @name.is_a?(::Symbol)
214
+ klass = lookup&.lookup(@name)
215
+ raise ::NameError, "Unknown middleware name #{@name.inspect}" if klass.nil?
216
+ else
217
+ klass = @name
218
+ end
219
+ # Due to a bug in Ruby < 2.7, passing an empty **kwargs splat to
220
+ # initialize will fail if there are no formal keyword args.
221
+ formals = klass.instance_method(:initialize).parameters
222
+ if @kwargs.empty? && formals.all? { |arg| arg.first != :key && arg.first != :keyrest }
223
+ klass.new(*@args, &@block)
224
+ else
225
+ klass.new(*@args, **@kwargs, &@block)
226
+ end
227
+ end
228
+
229
+ ##
230
+ # @return [Toys::Middleware] if this spec wraps a middleware object
231
+ # @return [nil] if this spec represents a class to instantiate
232
+ #
233
+ attr_reader :object
234
+
235
+ ##
236
+ # @return [String,Symbol] if this spec represents a middleware name
237
+ # @return [Class] if this spec represents a middleware class
238
+ # @return [nil] if this spec wraps a middleware object
239
+ #
240
+ attr_reader :name
241
+
242
+ ##
243
+ # @return [Array] the positional arguments to be passed to a middleware
244
+ # class constructor, or the empty array if there are no positional
245
+ # arguments
246
+ # @return [nil] if this spec wraps a middleware object
247
+ #
248
+ attr_reader :args
249
+
250
+ ##
251
+ # @return [Hash] the keyword arguments to be passed to a middleware class
252
+ # constructor, or the empty hash if there are no keyword arguments
253
+ # @return [nil] if this spec wraps a middleware object
254
+ #
255
+ attr_reader :kwargs
256
+
257
+ ##
258
+ # @return [Proc] if there is a block argument to be passed to a
259
+ # middleware class constructor
260
+ # @return [nil] if there is no block argument, or this spec wraps a
261
+ # middleware object
262
+ #
263
+ attr_reader :block
264
+
265
+ ## @private
266
+ def initialize(object, name, args, kwargs, block)
267
+ @object = object
268
+ @name = name
269
+ @args = args
270
+ @kwargs = kwargs
271
+ @block = block
272
+ end
273
+ end
92
274
  end
93
275
  end
@@ -21,6 +21,8 @@
21
21
  # IN THE SOFTWARE.
22
22
  ;
23
23
 
24
+ require "monitor"
25
+
24
26
  module Toys
25
27
  ##
26
28
  # A helper module that provides methods to do module lookups. This is
@@ -71,11 +73,15 @@ module Toys
71
73
  end
72
74
  end
73
75
 
76
+ include ::MonitorMixin
77
+
74
78
  ##
75
79
  # Create an empty ModuleLookup
76
80
  #
77
81
  def initialize
82
+ super()
78
83
  @paths = []
84
+ @paths_locked = false
79
85
  end
80
86
 
81
87
  ##
@@ -90,10 +96,13 @@ module Toys
90
96
  #
91
97
  def add_path(path_base, module_base: nil, high_priority: false)
92
98
  module_base ||= ModuleLookup.path_to_module(path_base)
93
- if high_priority
94
- @paths.unshift([path_base, module_base])
95
- else
96
- @paths << [path_base, module_base]
99
+ synchronize do
100
+ raise "You cannot add a path after a lookup has already occurred." if @paths_locked
101
+ if high_priority
102
+ @paths.unshift([path_base, module_base])
103
+ else
104
+ @paths << [path_base, module_base]
105
+ end
97
106
  end
98
107
  self
99
108
  end
@@ -105,19 +114,22 @@ module Toys
105
114
  # @return [Module] The specified module
106
115
  #
107
116
  def lookup(name)
108
- @paths.each do |path_base, module_base|
109
- path = "#{path_base}/#{ModuleLookup.to_path_name(name)}"
110
- begin
111
- require path
112
- rescue ::LoadError
113
- next
114
- end
115
- mod_name = ModuleLookup.to_module_name(name)
116
- unless module_base.constants.include?(mod_name)
117
- raise ::NameError,
118
- "File #{path.inspect} did not define #{module_base.name}::#{mod_name}"
117
+ synchronize do
118
+ @paths_locked = true
119
+ @paths.each do |path_base, module_base|
120
+ path = "#{path_base}/#{ModuleLookup.to_path_name(name)}"
121
+ begin
122
+ require path
123
+ rescue ::LoadError
124
+ next
125
+ end
126
+ mod_name = ModuleLookup.to_module_name(name)
127
+ unless module_base.constants.include?(mod_name)
128
+ raise ::NameError,
129
+ "File #{path.inspect} did not define #{module_base.name}::#{mod_name}"
130
+ end
131
+ return module_base.const_get(mod_name)
119
132
  end
120
- return module_base.const_get(mod_name)
121
133
  end
122
134
  nil
123
135
  end
@@ -95,11 +95,11 @@ module Toys
95
95
  #
96
96
  KEY = ::Object.new.freeze
97
97
 
98
- on_initialize do |opts = {}|
98
+ on_initialize do |**opts|
99
99
  require "toys/utils/exec"
100
100
  context = self
101
101
  opts = Exec._setup_exec_opts(opts, context)
102
- context[KEY] = Utils::Exec.new(opts) do |k|
102
+ context[KEY] = Utils::Exec.new(**opts) do |k|
103
103
  case k
104
104
  when :logger
105
105
  context[Context::Key::LOGGER]
@@ -115,11 +115,12 @@ module Toys
115
115
  # All options listed in the {Toys::Utils::Exec} documentation are
116
116
  # supported, plus the `exit_on_nonzero_status` option.
117
117
  #
118
- # @param opts [Hash] The default options.
118
+ # @param opts [keywords] The default options.
119
119
  # @return [self]
120
120
  #
121
- def configure_exec(opts = {})
122
- self[KEY].configure_defaults(Exec._setup_exec_opts(opts, self))
121
+ def configure_exec(**opts)
122
+ opts = Exec._setup_exec_opts(opts, self)
123
+ self[KEY].configure_defaults(**opts)
123
124
  self
124
125
  end
125
126
 
@@ -131,7 +132,7 @@ module Toys
131
132
  # provided, a {Toys::Utils::Exec::Controller} will be yielded to it.
132
133
  #
133
134
  # @param cmd [String,Array<String>] The command to execute.
134
- # @param opts [Hash] The command options. All options listed in the
135
+ # @param opts [keywords] The command options. All options listed in the
135
136
  # {Toys::Utils::Exec} documentation are supported, plus the
136
137
  # `exit_on_nonzero_status` option.
137
138
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
@@ -142,8 +143,9 @@ module Toys
142
143
  # @return [Toys::Utils::Exec::Result] The result, if the process ran in
143
144
  # the foreground.
144
145
  #
145
- def exec(cmd, opts = {}, &block)
146
- self[KEY].exec(cmd, Exec._setup_exec_opts(opts, self), &block)
146
+ def exec(cmd, **opts, &block)
147
+ opts = Exec._setup_exec_opts(opts, self)
148
+ self[KEY].exec(cmd, **opts, &block)
147
149
  end
148
150
 
149
151
  ##
@@ -153,7 +155,7 @@ module Toys
153
155
  # provided, a {Toys::Utils::Exec::Controller} will be yielded to it.
154
156
  #
155
157
  # @param args [String,Array<String>] The arguments to ruby.
156
- # @param opts [Hash] The command options. All options listed in the
158
+ # @param opts [keywords] The command options. All options listed in the
157
159
  # {Toys::Utils::Exec} documentation are supported, plus the
158
160
  # `exit_on_nonzero_status` option.
159
161
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
@@ -164,8 +166,9 @@ module Toys
164
166
  # @return [Toys::Utils::Exec::Result] The result, if the process ran in
165
167
  # the foreground.
166
168
  #
167
- def exec_ruby(args, opts = {}, &block)
168
- self[KEY].exec_ruby(args, Exec._setup_exec_opts(opts, self), &block)
169
+ def exec_ruby(args, **opts, &block)
170
+ opts = Exec._setup_exec_opts(opts, self)
171
+ self[KEY].exec_ruby(args, **opts, &block)
169
172
  end
170
173
  alias ruby exec_ruby
171
174
 
@@ -176,7 +179,7 @@ module Toys
176
179
  # provided, a {Toys::Utils::Exec::Controller} will be yielded to it.
177
180
  #
178
181
  # @param func [Proc] The proc to call.
179
- # @param opts [Hash] The command options. Most options listed in the
182
+ # @param opts [keywords] The command options. Most options listed in the
180
183
  # {Toys::Utils::Exec} documentation are supported, plus the
181
184
  # `exit_on_nonzero_status` option.
182
185
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -187,8 +190,9 @@ module Toys
187
190
  # @return [Toys::Utils::Exec::Result] The result, if the process ran in
188
191
  # the foreground.
189
192
  #
190
- def exec_proc(func, opts = {}, &block)
191
- self[KEY].exec_proc(func, Exec._setup_exec_opts(opts, self), &block)
193
+ def exec_proc(func, **opts, &block)
194
+ opts = Exec._setup_exec_opts(opts, self)
195
+ self[KEY].exec_proc(func, **opts, &block)
192
196
  end
193
197
 
194
198
  ##
@@ -199,7 +203,7 @@ module Toys
199
203
  # provided, a {Toys::Utils::Exec::Controller} will be yielded to it.
200
204
  #
201
205
  # @param cmd [String,Array<String>] The tool to execute.
202
- # @param opts [Hash] The command options. Most options listed in the
206
+ # @param opts [keywords] The command options. Most options listed in the
203
207
  # {Toys::Utils::Exec} documentation are supported, plus the
204
208
  # `exit_on_nonzero_status` option.
205
209
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -210,9 +214,10 @@ module Toys
210
214
  # @return [Toys::Utils::Exec::Result] The result, if the process ran in
211
215
  # the foreground.
212
216
  #
213
- def exec_tool(cmd, opts = {}, &block)
217
+ def exec_tool(cmd, **opts, &block)
214
218
  func = Exec._make_tool_caller(cmd)
215
- self[KEY].exec_proc(func, Exec._setup_exec_opts(opts, self), &block)
219
+ opts = Exec._setup_exec_opts(opts, self)
220
+ self[KEY].exec_proc(func, **opts, &block)
216
221
  end
217
222
 
218
223
  ##
@@ -226,7 +231,7 @@ module Toys
226
231
  # yielded to it.
227
232
  #
228
233
  # @param cmd [String,Array<String>] The command to execute.
229
- # @param opts [Hash] The command options. All options listed in the
234
+ # @param opts [keywords] The command options. All options listed in the
230
235
  # {Toys::Utils::Exec} documentation are supported, plus the
231
236
  # `exit_on_nonzero_status` option.
232
237
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -234,8 +239,9 @@ module Toys
234
239
  #
235
240
  # @return [String] What was written to standard out.
236
241
  #
237
- def capture(cmd, opts = {}, &block)
238
- self[KEY].capture(cmd, Exec._setup_exec_opts(opts, self), &block)
242
+ def capture(cmd, **opts, &block)
243
+ opts = Exec._setup_exec_opts(opts, self)
244
+ self[KEY].capture(cmd, **opts, &block)
239
245
  end
240
246
 
241
247
  ##
@@ -248,7 +254,7 @@ module Toys
248
254
  # yielded to it.
249
255
  #
250
256
  # @param args [String,Array<String>] The arguments to ruby.
251
- # @param opts [Hash] The command options. All options listed in the
257
+ # @param opts [keywords] The command options. All options listed in the
252
258
  # {Toys::Utils::Exec} documentation are supported, plus the
253
259
  # `exit_on_nonzero_status` option.
254
260
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -256,8 +262,9 @@ module Toys
256
262
  #
257
263
  # @return [String] What was written to standard out.
258
264
  #
259
- def capture_ruby(args, opts = {}, &block)
260
- self[KEY].capture_ruby(args, Exec._setup_exec_opts(opts, self), &block)
265
+ def capture_ruby(args, **opts, &block)
266
+ opts = Exec._setup_exec_opts(opts, self)
267
+ self[KEY].capture_ruby(args, **opts, &block)
261
268
  end
262
269
 
263
270
  ##
@@ -270,7 +277,7 @@ module Toys
270
277
  # yielded to it.
271
278
  #
272
279
  # @param func [Proc] The proc to call.
273
- # @param opts [Hash] The command options. Most options listed in the
280
+ # @param opts [keywords] The command options. Most options listed in the
274
281
  # {Toys::Utils::Exec} documentation are supported, plus the
275
282
  # `exit_on_nonzero_status` option.
276
283
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -278,8 +285,9 @@ module Toys
278
285
  #
279
286
  # @return [String] What was written to standard out.
280
287
  #
281
- def capture_proc(func, opts = {}, &block)
282
- self[KEY].capture_proc(func, Exec._setup_exec_opts(opts, self), &block)
288
+ def capture_proc(func, **opts, &block)
289
+ opts = Exec._setup_exec_opts(opts, self)
290
+ self[KEY].capture_proc(func, **opts, &block)
283
291
  end
284
292
 
285
293
  ##
@@ -293,7 +301,7 @@ module Toys
293
301
  # yielded to it.
294
302
  #
295
303
  # @param cmd [String,Array<String>] The tool to execute.
296
- # @param opts [Hash] The command options. Most options listed in the
304
+ # @param opts [keywords] The command options. Most options listed in the
297
305
  # {Toys::Utils::Exec} documentation are supported, plus the
298
306
  # `exit_on_nonzero_status` option.
299
307
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -301,9 +309,10 @@ module Toys
301
309
  #
302
310
  # @return [String] What was written to standard out.
303
311
  #
304
- def capture_tool(cmd, opts = {}, &block)
312
+ def capture_tool(cmd, **opts, &block)
305
313
  func = Exec._make_tool_caller(cmd)
306
- self[KEY].capture_proc(func, Exec._setup_exec_opts(opts, self), &block)
314
+ opts = Exec._setup_exec_opts(opts, self)
315
+ self[KEY].capture_proc(func, **opts, &block)
307
316
  end
308
317
 
309
318
  ##
@@ -314,7 +323,7 @@ module Toys
314
323
  # yielded to it.
315
324
  #
316
325
  # @param cmd [String] The shell command to execute.
317
- # @param opts [Hash] The command options. All options listed in the
326
+ # @param opts [keywords] The command options. All options listed in the
318
327
  # {Toys::Utils::Exec} documentation are supported, plus the
319
328
  # `exit_on_nonzero_status` option.
320
329
  # @yieldparam controller [Toys::Utils::Exec::Controller] A controller
@@ -322,8 +331,9 @@ module Toys
322
331
  #
323
332
  # @return [Integer] The exit code
324
333
  #
325
- def sh(cmd, opts = {}, &block)
326
- self[KEY].sh(cmd, Exec._setup_exec_opts(opts, self), &block)
334
+ def sh(cmd, **opts, &block)
335
+ opts = Exec._setup_exec_opts(opts, self)
336
+ self[KEY].sh(cmd, **opts, &block)
327
337
  end
328
338
 
329
339
  ##