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