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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +3 -3
- data/docs/guide.md +109 -14
- data/lib/toys/cli.rb +33 -28
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/tool.rb +4 -3
- data/lib/toys/loader.rb +177 -132
- data/lib/toys/middleware.rb +185 -3
- data/lib/toys/module_lookup.rb +28 -16
- data/lib/toys/standard_mixins/exec.rb +42 -32
- data/lib/toys/standard_mixins/gems.rb +1 -1
- data/lib/toys/standard_mixins/terminal.rb +1 -1
- data/lib/toys/tool.rb +5 -4
- data/lib/toys/utils/exec.rb +28 -23
- metadata +8 -8
data/lib/toys/middleware.rb
CHANGED
@@ -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}.
|
44
|
-
#
|
45
|
-
#
|
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
|
data/lib/toys/module_lookup.rb
CHANGED
@@ -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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
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 [
|
118
|
+
# @param opts [keywords] The default options.
|
119
119
|
# @return [self]
|
120
120
|
#
|
121
|
-
def configure_exec(opts
|
122
|
-
|
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 [
|
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
|
146
|
-
|
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 [
|
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
|
168
|
-
|
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 [
|
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
|
191
|
-
|
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 [
|
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
|
217
|
+
def exec_tool(cmd, **opts, &block)
|
214
218
|
func = Exec._make_tool_caller(cmd)
|
215
|
-
|
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 [
|
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
|
238
|
-
|
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 [
|
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
|
260
|
-
|
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 [
|
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
|
282
|
-
|
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 [
|
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
|
312
|
+
def capture_tool(cmd, **opts, &block)
|
305
313
|
func = Exec._make_tool_caller(cmd)
|
306
|
-
|
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 [
|
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
|
326
|
-
|
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
|
##
|