toys-core 0.9.1 → 0.9.2

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.
@@ -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
  ##