toys-core 0.3.8 → 0.3.9
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 -0
- data/lib/toys/cli.rb +6 -4
- data/lib/toys/core_version.rb +1 -1
- data/lib/toys/definition/acceptor.rb +2 -9
- data/lib/toys/definition/alias.rb +4 -4
- data/lib/toys/definition/tool.rb +123 -30
- data/lib/toys/dsl/tool.rb +83 -18
- data/lib/toys/input_file.rb +7 -4
- data/lib/toys/runner.rb +1 -1
- data/lib/toys/standard_mixins/exec.rb +107 -8
- data/lib/toys/template.rb +1 -0
- data/lib/toys/tool.rb +11 -0
- data/lib/toys/utils/exec.rb +186 -18
- data/lib/toys/utils/gems.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5dae1937beb353c94635ea47760fec27761e258b31c1e09770384ea22d768938
|
4
|
+
data.tar.gz: de39a399a009af7d284d21115cec946349227b99fefaca6cd511696ad6b83550
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29ab72f03cd03a7efca54b0b9c9f5a9ac479f6cb0df74668b966d8162686f43d311c63758ba919084cac420cb9b35546b144e7afe779435a256014f9009745e4
|
7
|
+
data.tar.gz: 05f7d64072c690d2b9686455e85d7a499cc1bb67f3b3cf98c5a27e83eb2a101c57c3b93f3889f091a5c30eb1ce024876b45f5fca1359ba555eb166e005533505
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 0.3.9 / 2018-06-24
|
4
|
+
|
5
|
+
* CHANGED: Cli#add_search_path_hierarchy changed the behavior of the base/terminate param
|
6
|
+
* CHANGED: Removed alias_as directive since it's incompatible with selective loading.
|
7
|
+
* ADDED: Ability to define named templates in Toys files
|
8
|
+
* ADDED: Ability to disable argument parsing
|
9
|
+
* ADDED: Exec#exec_proc and Exec#exec_tool that supports all the stream redirects
|
10
|
+
* IMPROVED: Acceptors can be looked up recursively in the same way as mixins and templates
|
11
|
+
|
3
12
|
### 0.3.8 / 2018-06-10
|
4
13
|
|
5
14
|
* CHANGED: Renamed helpers to mixins.
|
data/lib/toys/cli.rb
CHANGED
@@ -183,16 +183,17 @@ module Toys
|
|
183
183
|
#
|
184
184
|
# @param [String] start The first directory to add. Defaults to the current
|
185
185
|
# working directory.
|
186
|
-
# @param [String]
|
186
|
+
# @param [Array<String>] terminate Optional list of directories that should
|
187
|
+
# terminate the search.
|
187
188
|
# @param [Boolean] high_priority Add the configs at the head of the
|
188
189
|
# priority list rather than the tail.
|
189
190
|
#
|
190
|
-
def add_search_path_hierarchy(start: nil,
|
191
|
+
def add_search_path_hierarchy(start: nil, terminate: [], high_priority: false)
|
191
192
|
path = start || ::Dir.pwd
|
192
193
|
paths = []
|
193
194
|
loop do
|
195
|
+
break if terminate.include?(path)
|
194
196
|
paths << path
|
195
|
-
break if path == base
|
196
197
|
next_path = ::File.dirname(path)
|
197
198
|
break if next_path == path
|
198
199
|
path = next_path
|
@@ -232,11 +233,12 @@ module Toys
|
|
232
233
|
# Make a clone with the same settings but no paths in the loader.
|
233
234
|
# This is sometimes useful for running sub-tools.
|
234
235
|
#
|
236
|
+
# @param [Hash] _opts Unused options that can be used by subclasses.
|
235
237
|
# @return [Toys::CLI]
|
236
238
|
# @yieldparam cli [Toys::CLI] If you pass a block, the new CLI is yielded
|
237
239
|
# to it so you can add paths and make other modifications.
|
238
240
|
#
|
239
|
-
def child
|
241
|
+
def child(_opts = {})
|
240
242
|
cli = CLI.new(binary_name: @binary_name,
|
241
243
|
config_dir_name: @config_dir_name,
|
242
244
|
config_file_name: @config_file_name,
|
data/lib/toys/core_version.rb
CHANGED
@@ -66,7 +66,7 @@ module Toys
|
|
66
66
|
# block.
|
67
67
|
#
|
68
68
|
def initialize(name, converter = nil, &block)
|
69
|
-
@name = name
|
69
|
+
@name = name.to_s
|
70
70
|
@converter = converter || block
|
71
71
|
end
|
72
72
|
|
@@ -75,14 +75,7 @@ module Toys
|
|
75
75
|
# @return [String]
|
76
76
|
#
|
77
77
|
attr_reader :name
|
78
|
-
|
79
|
-
##
|
80
|
-
# Name of the acceptor as a string
|
81
|
-
# @return [String]
|
82
|
-
#
|
83
|
-
def to_s
|
84
|
-
name.to_s
|
85
|
-
end
|
78
|
+
alias to_s name
|
86
79
|
|
87
80
|
##
|
88
81
|
# Validate the given input.
|
@@ -43,13 +43,13 @@ module Toys
|
|
43
43
|
#
|
44
44
|
def initialize(loader, full_name, target, priority)
|
45
45
|
@target_name =
|
46
|
-
if target.is_a?(::
|
47
|
-
|
46
|
+
if target.is_a?(::Array)
|
47
|
+
target.map(&:to_s)
|
48
48
|
else
|
49
|
-
target.
|
49
|
+
full_name[0..-2] + [target.to_s]
|
50
50
|
end
|
51
51
|
@target_name.freeze
|
52
|
-
@full_name = full_name.
|
52
|
+
@full_name = full_name.map(&:to_s).freeze
|
53
53
|
@priority = priority
|
54
54
|
@tool_class = DSL::Tool.new_class(@full_name, priority, loader)
|
55
55
|
end
|
data/lib/toys/definition/tool.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
;
|
29
29
|
|
30
30
|
require "optparse"
|
31
|
+
require "set"
|
31
32
|
|
32
33
|
module Toys
|
33
34
|
module Definition
|
@@ -44,21 +45,23 @@ module Toys
|
|
44
45
|
# You can reference these acceptors directly. Otherwise, you have to add
|
45
46
|
# one explicitly to the tool using {Tool#add_acceptor}.
|
46
47
|
#
|
47
|
-
OPTPARSER_ACCEPTORS =
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
48
|
+
OPTPARSER_ACCEPTORS = ::Set.new(
|
49
|
+
[
|
50
|
+
::Object,
|
51
|
+
::NilClass,
|
52
|
+
::String,
|
53
|
+
::Integer,
|
54
|
+
::Float,
|
55
|
+
::Numeric,
|
56
|
+
::TrueClass,
|
57
|
+
::FalseClass,
|
58
|
+
::Array,
|
59
|
+
::Regexp,
|
60
|
+
::OptionParser::DecimalInteger,
|
61
|
+
::OptionParser::OctalInteger,
|
62
|
+
::OptionParser::DecimalNumeric
|
63
|
+
]
|
64
|
+
).freeze
|
62
65
|
|
63
66
|
##
|
64
67
|
# Create a new tool.
|
@@ -78,16 +81,18 @@ module Toys
|
|
78
81
|
@long_desc = []
|
79
82
|
|
80
83
|
@default_data = {}
|
81
|
-
@acceptors = {}
|
82
|
-
OPTPARSER_ACCEPTORS.each { |a| @acceptors[a] = a }
|
83
84
|
@used_flags = []
|
84
85
|
|
86
|
+
@acceptors = {}
|
85
87
|
@mixins = {}
|
88
|
+
@templates = {}
|
86
89
|
|
87
90
|
@flag_definitions = []
|
88
91
|
@required_arg_definitions = []
|
89
92
|
@optional_arg_definitions = []
|
90
93
|
@remaining_args_definition = nil
|
94
|
+
|
95
|
+
@disable_argument_parsing = false
|
91
96
|
@runnable = false
|
92
97
|
end
|
93
98
|
|
@@ -239,6 +244,14 @@ module Toys
|
|
239
244
|
@definition_finished
|
240
245
|
end
|
241
246
|
|
247
|
+
##
|
248
|
+
# Returns true if this tool has disabled argument parsing.
|
249
|
+
# @return [Boolean]
|
250
|
+
#
|
251
|
+
def argument_parsing_disabled?
|
252
|
+
@disable_argument_parsing
|
253
|
+
end
|
254
|
+
|
242
255
|
##
|
243
256
|
# Returns all arg definitions in order: required, optional, remaining.
|
244
257
|
# @return [Array<Toys::Definition::Arg>]
|
@@ -264,6 +277,46 @@ module Toys
|
|
264
277
|
result.uniq
|
265
278
|
end
|
266
279
|
|
280
|
+
##
|
281
|
+
# Resolve the given acceptor. You may pass in a
|
282
|
+
# {Toys::Definition::Acceptor}, an acceptor name, a well-known acceptor
|
283
|
+
# understood by OptionParser, or `nil`.
|
284
|
+
#
|
285
|
+
# Returns either `nil` or an acceptor that is usable by OptionParser.
|
286
|
+
#
|
287
|
+
# If an acceptor name is given, it may be resolved by this tool or any of
|
288
|
+
# its ancestors. Raises {Toys::ToolDefinitionError} if the name is not
|
289
|
+
# recognized.
|
290
|
+
#
|
291
|
+
# @param [Object] accept An acceptor input.
|
292
|
+
# @return [Object] The resolved acceptor.
|
293
|
+
#
|
294
|
+
def resolve_acceptor(accept)
|
295
|
+
return accept if accept.nil? || accept.is_a?(Acceptor)
|
296
|
+
name = accept
|
297
|
+
accept = @acceptors.fetch(name) do |k|
|
298
|
+
if @parent
|
299
|
+
@parent.resolve_acceptor(k)
|
300
|
+
elsif OPTPARSER_ACCEPTORS.include?(k)
|
301
|
+
k
|
302
|
+
end
|
303
|
+
end
|
304
|
+
if accept.nil?
|
305
|
+
raise ToolDefinitionError, "Unknown acceptor: #{name.inspect}"
|
306
|
+
end
|
307
|
+
accept
|
308
|
+
end
|
309
|
+
|
310
|
+
##
|
311
|
+
# Get the named template from this tool or its ancestors.
|
312
|
+
#
|
313
|
+
# @param [String] name The template name
|
314
|
+
# @return [Class,nil] The template class, or `nil` if not found.
|
315
|
+
#
|
316
|
+
def resolve_template(name)
|
317
|
+
@templates.fetch(name.to_s) { |k| @parent ? @parent.resolve_template(k) : nil }
|
318
|
+
end
|
319
|
+
|
267
320
|
##
|
268
321
|
# Get the named mixin from this tool or its ancestors.
|
269
322
|
#
|
@@ -335,6 +388,11 @@ module Toys
|
|
335
388
|
# @param [Toys::Definition::Acceptor] acceptor The acceptor to add.
|
336
389
|
#
|
337
390
|
def add_acceptor(acceptor)
|
391
|
+
if @acceptors.key?(acceptor.name)
|
392
|
+
raise ToolDefinitionError,
|
393
|
+
"An acceptor named #{acceptor.name.inspect} has already been" \
|
394
|
+
" defined in tool #{display_name.inspect}."
|
395
|
+
end
|
338
396
|
@acceptors[acceptor.name] = acceptor
|
339
397
|
self
|
340
398
|
end
|
@@ -346,7 +404,44 @@ module Toys
|
|
346
404
|
# @param [Module] mixin_module The mixin module.
|
347
405
|
#
|
348
406
|
def add_mixin(name, mixin_module)
|
349
|
-
|
407
|
+
name = name.to_s
|
408
|
+
if @mixins.key?(name)
|
409
|
+
raise ToolDefinitionError,
|
410
|
+
"A mixin named #{name.inspect} has already been defined in tool" \
|
411
|
+
" #{display_name.inspect}."
|
412
|
+
end
|
413
|
+
@mixins[name] = mixin_module
|
414
|
+
self
|
415
|
+
end
|
416
|
+
|
417
|
+
##
|
418
|
+
# Add a named template class to this tool.
|
419
|
+
#
|
420
|
+
# @param [String] name The name of the template.
|
421
|
+
# @param [Class] template_class The template class.
|
422
|
+
#
|
423
|
+
def add_template(name, template_class)
|
424
|
+
name = name.to_s
|
425
|
+
if @templates.key?(name)
|
426
|
+
raise ToolDefinitionError,
|
427
|
+
"A template named #{name.inspect} has already been defined in tool" \
|
428
|
+
" #{display_name.inspect}."
|
429
|
+
end
|
430
|
+
@templates[name] = template_class
|
431
|
+
self
|
432
|
+
end
|
433
|
+
|
434
|
+
##
|
435
|
+
# Disable argument parsing for this tool
|
436
|
+
#
|
437
|
+
def disable_argument_parsing
|
438
|
+
check_definition_state
|
439
|
+
if includes_arguments?
|
440
|
+
raise ToolDefinitionError,
|
441
|
+
"Cannot disable argument parsing for tool #{display_name.inspect}" \
|
442
|
+
" because arguments have already been defined."
|
443
|
+
end
|
444
|
+
@disable_argument_parsing = true
|
350
445
|
self
|
351
446
|
end
|
352
447
|
|
@@ -385,7 +480,7 @@ module Toys
|
|
385
480
|
accept: nil, default: nil, handler: nil,
|
386
481
|
report_collisions: true,
|
387
482
|
desc: nil, long_desc: nil)
|
388
|
-
check_definition_state
|
483
|
+
check_definition_state(is_arg: true)
|
389
484
|
accept = resolve_acceptor(accept)
|
390
485
|
flag_def = Definition::Flag.new(key, flags, @used_flags, report_collisions,
|
391
486
|
accept, handler, default)
|
@@ -404,6 +499,7 @@ module Toys
|
|
404
499
|
# @param [String...] flags The flags to disable
|
405
500
|
#
|
406
501
|
def disable_flag(*flags)
|
502
|
+
check_definition_state(is_arg: true)
|
407
503
|
flags = flags.uniq
|
408
504
|
intersection = @used_flags & flags
|
409
505
|
unless intersection.empty?
|
@@ -435,7 +531,7 @@ module Toys
|
|
435
531
|
# formats. Defaults to the empty array.
|
436
532
|
#
|
437
533
|
def add_required_arg(key, accept: nil, display_name: nil, desc: nil, long_desc: nil)
|
438
|
-
check_definition_state
|
534
|
+
check_definition_state(is_arg: true)
|
439
535
|
accept = resolve_acceptor(accept)
|
440
536
|
arg_def = Definition::Arg.new(key, :required, accept, nil, desc, long_desc, display_name)
|
441
537
|
@required_arg_definitions << arg_def
|
@@ -469,7 +565,7 @@ module Toys
|
|
469
565
|
#
|
470
566
|
def add_optional_arg(key, default: nil, accept: nil, display_name: nil,
|
471
567
|
desc: nil, long_desc: nil)
|
472
|
-
check_definition_state
|
568
|
+
check_definition_state(is_arg: true)
|
473
569
|
accept = resolve_acceptor(accept)
|
474
570
|
arg_def = Definition::Arg.new(key, :optional, accept, default,
|
475
571
|
desc, long_desc, display_name)
|
@@ -504,7 +600,7 @@ module Toys
|
|
504
600
|
#
|
505
601
|
def set_remaining_args(key, default: [], accept: nil, display_name: nil,
|
506
602
|
desc: nil, long_desc: nil)
|
507
|
-
check_definition_state
|
603
|
+
check_definition_state(is_arg: true)
|
508
604
|
accept = resolve_acceptor(accept)
|
509
605
|
arg_def = Definition::Arg.new(key, :remaining, accept, default,
|
510
606
|
desc, long_desc, display_name)
|
@@ -557,19 +653,16 @@ module Toys
|
|
557
653
|
proc { middleware.config(self, loader, &next_config) }
|
558
654
|
end
|
559
655
|
|
560
|
-
def check_definition_state
|
656
|
+
def check_definition_state(is_arg: false)
|
561
657
|
if @definition_finished
|
562
658
|
raise ToolDefinitionError,
|
563
659
|
"Defintion of tool #{display_name.inspect} is already finished"
|
564
660
|
end
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
return accept if accept.nil? || accept.is_a?(Acceptor)
|
569
|
-
unless @acceptors.key?(accept)
|
570
|
-
raise ToolDefinitionError, "Unknown acceptor: #{accept.inspect}"
|
661
|
+
if is_arg && argument_parsing_disabled?
|
662
|
+
raise ToolDefinitionError,
|
663
|
+
"Tool #{display_name.inspect} has disabled argument parsing"
|
571
664
|
end
|
572
|
-
|
665
|
+
self
|
573
666
|
end
|
574
667
|
end
|
575
668
|
end
|
data/lib/toys/dsl/tool.rb
CHANGED
@@ -139,6 +139,30 @@ module Toys
|
|
139
139
|
self
|
140
140
|
end
|
141
141
|
|
142
|
+
##
|
143
|
+
# Create a named template class.
|
144
|
+
# This template may be expanded by name in this tool or any subtool.
|
145
|
+
#
|
146
|
+
# You should pass a block and define the template in that block. You do
|
147
|
+
# not need to include `Toys::Template` in the block. Otherwise, see
|
148
|
+
# {Toys::Template} for information on defining a template. In general,
|
149
|
+
# the block should define an initialize method, and call `to_expand` to
|
150
|
+
# define how to expand the template.
|
151
|
+
#
|
152
|
+
# @param [String] name Name of the template
|
153
|
+
#
|
154
|
+
def template(name, &block)
|
155
|
+
cur_tool = DSL::Tool.activate_tool(self)
|
156
|
+
if cur_tool
|
157
|
+
template_class = ::Class.new do
|
158
|
+
include ::Toys::Template
|
159
|
+
end
|
160
|
+
template_class.class_eval(&block)
|
161
|
+
cur_tool.add_template(name, template_class)
|
162
|
+
end
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
142
166
|
##
|
143
167
|
# Create a subtool. You must provide a block defining the subtool.
|
144
168
|
#
|
@@ -170,19 +194,6 @@ module Toys
|
|
170
194
|
self
|
171
195
|
end
|
172
196
|
|
173
|
-
##
|
174
|
-
# Create an alias of the current tool.
|
175
|
-
#
|
176
|
-
# @param [String] word The name of the alias
|
177
|
-
#
|
178
|
-
def alias_as(word)
|
179
|
-
if @__words.empty?
|
180
|
-
raise ToolDefinitionError, "Cannot make an alias of the root."
|
181
|
-
end
|
182
|
-
@__loader.make_alias(@__words[0..-2] + [word.to_s], @__words, @__priority)
|
183
|
-
self
|
184
|
-
end
|
185
|
-
|
186
197
|
##
|
187
198
|
# Include another config file or directory at the current location.
|
188
199
|
#
|
@@ -204,12 +215,14 @@ module Toys
|
|
204
215
|
# @param [Object...] args Template arguments
|
205
216
|
#
|
206
217
|
def expand(template_class, *args)
|
207
|
-
|
208
|
-
|
218
|
+
name = template_class.to_s
|
219
|
+
if template_class.is_a?(::String)
|
220
|
+
template_class = cur_tool.resolve_template(template_class)
|
221
|
+
elsif template_class.is_a?(::Symbol)
|
209
222
|
template_class = @__loader.resolve_standard_template(name)
|
210
|
-
|
211
|
-
|
212
|
-
|
223
|
+
end
|
224
|
+
if template_class.nil?
|
225
|
+
raise ToolDefinitionError, "Template not found: #{name.inspect}"
|
213
226
|
end
|
214
227
|
template = template_class.new(*args)
|
215
228
|
yield template if block_given?
|
@@ -446,6 +459,47 @@ module Toys
|
|
446
459
|
end
|
447
460
|
alias remaining remaining_args
|
448
461
|
|
462
|
+
##
|
463
|
+
# Set an option value statically.
|
464
|
+
#
|
465
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
466
|
+
# execution context.
|
467
|
+
# @param [Object] value The value to set.
|
468
|
+
#
|
469
|
+
def set(key, value)
|
470
|
+
cur_tool = DSL::Tool.activate_tool(self)
|
471
|
+
return self if cur_tool.nil?
|
472
|
+
cur_tool.default_data[key] = value
|
473
|
+
self
|
474
|
+
end
|
475
|
+
|
476
|
+
##
|
477
|
+
# Disable argument parsing for this tool. Arguments will not be parsed
|
478
|
+
# and the options will not be populated. Instead, tools can retrieve the
|
479
|
+
# full unparsed argument list by calling {Toys::Tool#args}.
|
480
|
+
#
|
481
|
+
# This directive is mutually exclusive with any of the directives that
|
482
|
+
# declare arguments or flags.
|
483
|
+
#
|
484
|
+
def disable_argument_parsing
|
485
|
+
cur_tool = DSL::Tool.activate_tool(self)
|
486
|
+
cur_tool.disable_argument_parsing unless cur_tool.nil?
|
487
|
+
self
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# Mark one or more flags as disabled, preventing their use by any
|
492
|
+
# subsequent flag definition. This may be used to prevent middleware from
|
493
|
+
# defining a particular flag.
|
494
|
+
#
|
495
|
+
# @param [String...] flags The flags to disable
|
496
|
+
#
|
497
|
+
def disable_flag(*flags)
|
498
|
+
cur_tool = DSL::Tool.activate_tool(self)
|
499
|
+
cur_tool.disable_flag(*flags) unless cur_tool.nil?
|
500
|
+
self
|
501
|
+
end
|
502
|
+
|
449
503
|
##
|
450
504
|
# Specify how to run this tool. You may do this by providing a block to
|
451
505
|
# this directive, or by defining the `run` method in the tool.
|
@@ -480,6 +534,17 @@ module Toys
|
|
480
534
|
super(mod)
|
481
535
|
end
|
482
536
|
|
537
|
+
##
|
538
|
+
# Activate the given gem. If it is not present, attempt to install it (or
|
539
|
+
# inform the user to update the bundle).
|
540
|
+
#
|
541
|
+
# @param [String] name Name of the gem
|
542
|
+
# @param [String...] requirements Version requirements
|
543
|
+
#
|
544
|
+
def gem(name, *requirements)
|
545
|
+
(@__gems ||= Utils::Gems.new).activate(name, *requirements)
|
546
|
+
end
|
547
|
+
|
483
548
|
## @private
|
484
549
|
def self.new_class(words, priority, loader)
|
485
550
|
tool_class = ::Class.new(::Toys::Tool)
|
data/lib/toys/input_file.rb
CHANGED
@@ -44,12 +44,15 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
44
44
|
include ::Toys::Tool::Keys
|
45
45
|
@tool_class = tool_class
|
46
46
|
end
|
47
|
-
|
47
|
+
basename = ::File.basename(path).tr(".-", "_").gsub(/\W/, "")
|
48
|
+
name = "M#{namespace.object_id}_#{basename}"
|
48
49
|
const_set(name, namespace)
|
49
50
|
str = <<-STR
|
50
|
-
module #{name}
|
51
|
-
|
52
|
-
|
51
|
+
module #{name}
|
52
|
+
@tool_class.class_eval do
|
53
|
+
#{::IO.read(path)}
|
54
|
+
end
|
55
|
+
end
|
53
56
|
STR
|
54
57
|
::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
|
55
58
|
::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
|
data/lib/toys/runner.rb
CHANGED
@@ -59,7 +59,7 @@ module Toys
|
|
59
59
|
#
|
60
60
|
def run(args, verbosity: 0)
|
61
61
|
data = create_data(args, verbosity)
|
62
|
-
parse_args(args, data)
|
62
|
+
parse_args(args, data) unless @tool_definition.argument_parsing_disabled?
|
63
63
|
tool = @tool_definition.tool_class.new(@cli, data)
|
64
64
|
|
65
65
|
original_level = @cli.logger.level
|
@@ -89,21 +89,49 @@ module Toys
|
|
89
89
|
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
90
90
|
# the exit code and any captured output.
|
91
91
|
#
|
92
|
-
def
|
93
|
-
Exec._exec(self).
|
92
|
+
def exec_ruby(args, opts = {}, &block)
|
93
|
+
Exec._exec(self).exec_ruby(args, Exec._setup_exec_opts(opts, self), &block)
|
94
94
|
end
|
95
|
+
alias ruby exec_ruby
|
95
96
|
|
96
97
|
##
|
97
|
-
# Execute
|
98
|
+
# Execute a proc in a subprocess.
|
98
99
|
#
|
99
|
-
#
|
100
|
+
# If you provide a block, a {Toys::Utils::Exec::Controller} will be
|
101
|
+
# yielded to it, allowing you to interact with the subprocess streams.
|
102
|
+
#
|
103
|
+
# @param [Proc] func The proc to call.
|
100
104
|
# @param [Hash] opts The command options. See the section on
|
101
105
|
# configuration options in the {Toys::Utils::Exec} module docs.
|
106
|
+
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller
|
107
|
+
# for the subprocess streams.
|
102
108
|
#
|
103
|
-
# @return [
|
109
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
110
|
+
# exit code and any captured output.
|
104
111
|
#
|
105
|
-
def
|
106
|
-
Exec._exec(self).
|
112
|
+
def exec_proc(func, opts = {}, &block)
|
113
|
+
Exec._exec(self).exec_proc(func, Exec._setup_exec_opts(opts, self), &block)
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Execute a tool. The command may be given as a single string or an array
|
118
|
+
# of strings, representing the tool to run and the arguments to pass.
|
119
|
+
#
|
120
|
+
# If you provide a block, a {Toys::Utils::Exec::Controller} will be
|
121
|
+
# yielded to it, allowing you to interact with the subprocess streams.
|
122
|
+
#
|
123
|
+
# @param [String,Array<String>] cmd The tool to execute.
|
124
|
+
# @param [Hash] opts The command options. See the section on
|
125
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
126
|
+
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller
|
127
|
+
# for the subprocess streams.
|
128
|
+
#
|
129
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
130
|
+
# exit code and any captured output.
|
131
|
+
#
|
132
|
+
def exec_tool(cmd, opts = {}, &block)
|
133
|
+
func = Exec._make_tool_caller(cmd)
|
134
|
+
Exec._exec(self).exec_proc(func, Exec._setup_exec_opts(opts, self), &block)
|
107
135
|
end
|
108
136
|
|
109
137
|
##
|
@@ -122,6 +150,66 @@ module Toys
|
|
122
150
|
Exec._exec(self).capture(cmd, Exec._setup_exec_opts(opts, self))
|
123
151
|
end
|
124
152
|
|
153
|
+
##
|
154
|
+
# Spawn a ruby process and pass the given arguments to it.
|
155
|
+
#
|
156
|
+
# Captures standard out and returns it as a string.
|
157
|
+
#
|
158
|
+
# @param [String,Array<String>] args The arguments to ruby.
|
159
|
+
# @param [Hash] opts The command options. See the section on
|
160
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
161
|
+
#
|
162
|
+
# @return [String] What was written to standard out.
|
163
|
+
#
|
164
|
+
def capture_ruby(args, opts = {})
|
165
|
+
Exec._exec(self).capture_ruby(args, Exec._setup_exec_opts(opts, self))
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Execute a proc in a subprocess.
|
170
|
+
#
|
171
|
+
# Captures standard out and returns it as a string.
|
172
|
+
#
|
173
|
+
# @param [Proc] func The proc to call.
|
174
|
+
# @param [Hash] opts The command options. See the section on
|
175
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
176
|
+
#
|
177
|
+
# @return [String] What was written to standard out.
|
178
|
+
#
|
179
|
+
def capture_proc(func, opts = {})
|
180
|
+
Exec._exec(self).capture_proc(func, Exec._setup_exec_opts(opts, self))
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Execute a tool. The command may be given as a single string or an array
|
185
|
+
# of strings, representing the tool to run and the arguments to pass.
|
186
|
+
#
|
187
|
+
# Captures standard out and returns it as a string.
|
188
|
+
#
|
189
|
+
# @param [String,Array<String>] cmd The tool to execute.
|
190
|
+
# @param [Hash] opts The command options. See the section on
|
191
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
192
|
+
#
|
193
|
+
# @return [String] What was written to standard out.
|
194
|
+
#
|
195
|
+
def capture_tool(cmd, opts = {})
|
196
|
+
func = Exec._make_tool_caller(cmd)
|
197
|
+
Exec._exec(self).capture_proc(func, Exec._setup_exec_opts(opts, self))
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Execute the given string in a shell. Returns the exit code.
|
202
|
+
#
|
203
|
+
# @param [String] cmd The shell command to execute.
|
204
|
+
# @param [Hash] opts The command options. See the section on
|
205
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
206
|
+
#
|
207
|
+
# @return [Integer] The exit code
|
208
|
+
#
|
209
|
+
def sh(cmd, opts = {})
|
210
|
+
Exec._exec(self).sh(cmd, Exec._setup_exec_opts(opts, self))
|
211
|
+
end
|
212
|
+
|
125
213
|
##
|
126
214
|
# Exit if the given status code is nonzero. Otherwise, returns 0.
|
127
215
|
#
|
@@ -137,10 +225,21 @@ module Toys
|
|
137
225
|
## @private
|
138
226
|
def self._exec(tool)
|
139
227
|
tool[Exec] ||= Utils::Exec.new do |k|
|
140
|
-
k
|
228
|
+
case k
|
229
|
+
when :logger
|
230
|
+
tool[Tool::Keys::LOGGER]
|
231
|
+
when :cli
|
232
|
+
tool[Tool::Keys::CLI]
|
233
|
+
end
|
141
234
|
end
|
142
235
|
end
|
143
236
|
|
237
|
+
## @private
|
238
|
+
def self._make_tool_caller(cmd)
|
239
|
+
cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
|
240
|
+
proc { |config| ::Kernel.exit(config[:cli].run(*cmd)) }
|
241
|
+
end
|
242
|
+
|
144
243
|
## @private
|
145
244
|
def self._setup_exec_opts(opts, tool)
|
146
245
|
return opts unless opts.key?(:exit_on_nonzero_status)
|
data/lib/toys/template.rb
CHANGED
data/lib/toys/tool.rb
CHANGED
@@ -269,6 +269,17 @@ module Toys
|
|
269
269
|
key.is_a?(::Symbol) || key.is_a?(::String) ? @__data[key] : nil
|
270
270
|
end
|
271
271
|
|
272
|
+
##
|
273
|
+
# Activate the given gem. If it is not present, attempt to install it (or
|
274
|
+
# inform the user to update the bundle).
|
275
|
+
#
|
276
|
+
# @param [String] name Name of the gem
|
277
|
+
# @param [String...] requirements Version requirements
|
278
|
+
#
|
279
|
+
def gem(name, *requirements)
|
280
|
+
(@__data[Utils::Gems] ||= Utils::Gems.new).activate(name, *requirements)
|
281
|
+
end
|
282
|
+
|
272
283
|
##
|
273
284
|
# Exit immediately with the given status code
|
274
285
|
#
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
;
|
29
29
|
|
30
30
|
require "logger"
|
31
|
+
require "shellwords"
|
31
32
|
|
32
33
|
module Toys
|
33
34
|
module Utils
|
@@ -187,22 +188,32 @@ module Toys
|
|
187
188
|
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
188
189
|
# exit code and any captured output.
|
189
190
|
#
|
190
|
-
def
|
191
|
+
def exec_ruby(args, opts = {}, &block)
|
191
192
|
cmd = args.is_a?(::Array) ? [::RbConfig.ruby] + args : "#{::RbConfig.ruby} #{args}"
|
192
|
-
|
193
|
+
log_cmd = args.is_a?(::Array) ? ["ruby"] + args : "ruby #{args}"
|
194
|
+
exec(cmd, {argv0: "ruby", log_cmd: log_cmd}.merge(opts), &block)
|
193
195
|
end
|
196
|
+
alias ruby exec_ruby
|
194
197
|
|
195
198
|
##
|
196
|
-
# Execute
|
199
|
+
# Execute a proc in a fork.
|
197
200
|
#
|
198
|
-
#
|
201
|
+
# If you provide a block, a {Toys::Utils::Exec::Controller} will be
|
202
|
+
# yielded to it, allowing you to interact with the subprocess streams.
|
203
|
+
#
|
204
|
+
# @param [Proc] func The proc to call.
|
199
205
|
# @param [Hash] opts The command options. See the section on
|
200
206
|
# configuration options in the {Toys::Utils::Exec} module docs.
|
207
|
+
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller
|
208
|
+
# for the subprocess streams.
|
201
209
|
#
|
202
|
-
# @return [
|
210
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
211
|
+
# exit code and any captured output.
|
203
212
|
#
|
204
|
-
def
|
205
|
-
|
213
|
+
def exec_proc(func, opts = {}, &block)
|
214
|
+
exec_opts = Opts.new(@default_opts).add(opts)
|
215
|
+
executor = Executor.new(exec_opts, func)
|
216
|
+
executor.execute(&block)
|
206
217
|
end
|
207
218
|
|
208
219
|
##
|
@@ -221,6 +232,49 @@ module Toys
|
|
221
232
|
exec(cmd, opts.merge(out: :capture)).captured_out
|
222
233
|
end
|
223
234
|
|
235
|
+
##
|
236
|
+
# Spawn a ruby process and pass the given arguments to it.
|
237
|
+
#
|
238
|
+
# Captures standard out and returns it as a string.
|
239
|
+
#
|
240
|
+
# @param [String,Array<String>] args The arguments to ruby.
|
241
|
+
# @param [Hash] opts The command options. See the section on
|
242
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
243
|
+
#
|
244
|
+
# @return [String] What was written to standard out.
|
245
|
+
#
|
246
|
+
def capture_ruby(args, opts = {})
|
247
|
+
ruby(args, opts.merge(out: :capture)).captured_out
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Execute a proc in a fork.
|
252
|
+
#
|
253
|
+
# Captures standard out and returns it as a string.
|
254
|
+
#
|
255
|
+
# @param [Proc] func The proc to call.
|
256
|
+
# @param [Hash] opts The command options. See the section on
|
257
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
258
|
+
#
|
259
|
+
# @return [String] What was written to standard out.
|
260
|
+
#
|
261
|
+
def capture_proc(func, opts = {})
|
262
|
+
exec_proc(func, opts.merge(out: :capture)).captured_out
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Execute the given string in a shell. Returns the exit code.
|
267
|
+
#
|
268
|
+
# @param [String] cmd The shell command to execute.
|
269
|
+
# @param [Hash] opts The command options. See the section on
|
270
|
+
# configuration options in the {Toys::Utils::Exec} module docs.
|
271
|
+
#
|
272
|
+
# @return [Integer] The exit code
|
273
|
+
#
|
274
|
+
def sh(cmd, opts = {})
|
275
|
+
exec(cmd, opts).exit_code
|
276
|
+
end
|
277
|
+
|
224
278
|
##
|
225
279
|
# An internal helper class storing the configuration of a subprocess invocation
|
226
280
|
# @private
|
@@ -232,10 +286,12 @@ module Toys
|
|
232
286
|
#
|
233
287
|
CONFIG_KEYS = %i[
|
234
288
|
argv0
|
289
|
+
cli
|
235
290
|
env
|
236
291
|
err
|
237
292
|
in
|
238
293
|
logger
|
294
|
+
log_cmd
|
239
295
|
log_level
|
240
296
|
nonzero_status_handler
|
241
297
|
out
|
@@ -414,21 +470,25 @@ module Toys
|
|
414
470
|
#
|
415
471
|
class Executor
|
416
472
|
def initialize(exec_opts, spawn_cmd)
|
417
|
-
@
|
473
|
+
@fork_func = spawn_cmd.respond_to?(:call) ? spawn_cmd : nil
|
474
|
+
@spawn_cmd = spawn_cmd.respond_to?(:call) ? nil : spawn_cmd
|
418
475
|
@config_opts = exec_opts.config_opts
|
419
476
|
@spawn_opts = exec_opts.spawn_opts
|
420
477
|
@captures = {}
|
421
478
|
@controller_streams = {}
|
422
479
|
@join_threads = []
|
423
480
|
@child_streams = []
|
481
|
+
@parent_streams = []
|
424
482
|
end
|
425
483
|
|
426
484
|
def execute(&block)
|
427
485
|
setup_in_stream
|
428
|
-
setup_out_stream(:out)
|
429
|
-
setup_out_stream(:err)
|
486
|
+
setup_out_stream(:out, $stdout, 1)
|
487
|
+
setup_out_stream(:err, $stderr, 2)
|
430
488
|
log_command
|
431
|
-
|
489
|
+
pid = @fork_func ? start_fork : start_process
|
490
|
+
@child_streams.each(&:close)
|
491
|
+
wait_thread = ::Process.detach(pid)
|
432
492
|
status = control_process(wait_thread, &block)
|
433
493
|
create_result(status)
|
434
494
|
end
|
@@ -438,8 +498,9 @@ module Toys
|
|
438
498
|
def log_command
|
439
499
|
logger = @config_opts[:logger]
|
440
500
|
if logger && @config_opts[:log_level] != false
|
441
|
-
cmd_str = @
|
442
|
-
|
501
|
+
cmd_str = @config_opts[:log_cmd]
|
502
|
+
cmd_str ||= @spawn_cmd.size == 1 ? @spawn_cmd.first : @spawn_cmd.inspect if @spawn_cmd
|
503
|
+
logger.add(@config_opts[:log_level] || ::Logger::INFO, cmd_str) if cmd_str
|
443
504
|
end
|
444
505
|
end
|
445
506
|
|
@@ -447,9 +508,107 @@ module Toys
|
|
447
508
|
args = []
|
448
509
|
args << @config_opts[:env] if @config_opts[:env]
|
449
510
|
args.concat(@spawn_cmd)
|
450
|
-
|
451
|
-
|
452
|
-
|
511
|
+
::Process.spawn(*args, @spawn_opts)
|
512
|
+
end
|
513
|
+
|
514
|
+
def start_fork
|
515
|
+
pid = ::Process.fork
|
516
|
+
return pid unless pid.nil?
|
517
|
+
exit_code = -1
|
518
|
+
begin
|
519
|
+
setup_env_within_fork
|
520
|
+
setup_streams_within_fork
|
521
|
+
exit_code = run_fork_func
|
522
|
+
rescue ::SystemExit => e
|
523
|
+
exit_code = e.status
|
524
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
525
|
+
warn(([e.inspect] + e.backtrace).join("\n"))
|
526
|
+
ensure
|
527
|
+
::Kernel.exit!(exit_code)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def run_fork_func
|
532
|
+
catch(:result) do
|
533
|
+
if @spawn_opts[:chdir]
|
534
|
+
::Dir.chdir(@spawn_opts[:chdir]) { @fork_func.call(@config_opts) }
|
535
|
+
else
|
536
|
+
@fork_func.call(@config_opts)
|
537
|
+
end
|
538
|
+
0
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
def setup_env_within_fork
|
543
|
+
if @config_opts[:unsetenv_others]
|
544
|
+
::ENV.each_key do |k|
|
545
|
+
::ENV.delete(k) unless @config_opts.key?(k)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
(@config_opts[:env] || {}).each { |k, v| ::ENV[k.to_s] = v.to_s }
|
549
|
+
end
|
550
|
+
|
551
|
+
def setup_streams_within_fork
|
552
|
+
@parent_streams.each(&:close)
|
553
|
+
setup_in_stream_within_fork(@spawn_opts[:in])
|
554
|
+
out_stream = interpret_out_stream_within_fork(@spawn_opts[:out])
|
555
|
+
err_stream = interpret_out_stream_within_fork(@spawn_opts[:err])
|
556
|
+
if out_stream == :close
|
557
|
+
$stdout.close
|
558
|
+
elsif out_stream
|
559
|
+
$stdout.reopen(out_stream)
|
560
|
+
$stdout.sync = true
|
561
|
+
end
|
562
|
+
if err_stream == :close
|
563
|
+
$stderr.close
|
564
|
+
elsif err_stream
|
565
|
+
$stderr.reopen(err_stream)
|
566
|
+
$stderr.sync = true
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
def setup_in_stream_within_fork(stream)
|
571
|
+
in_stream =
|
572
|
+
case stream
|
573
|
+
when ::Integer
|
574
|
+
::IO.open(stream)
|
575
|
+
when ::Array
|
576
|
+
::File.open(*stream)
|
577
|
+
when ::String
|
578
|
+
::File.open(stream, "r")
|
579
|
+
when :close
|
580
|
+
:close
|
581
|
+
else
|
582
|
+
stream if stream.respond_to?(:write)
|
583
|
+
end
|
584
|
+
if in_stream == :close
|
585
|
+
$stdin.close
|
586
|
+
elsif in_stream
|
587
|
+
$stdin.reopen(in_stream)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def interpret_out_stream_within_fork(stream)
|
592
|
+
case stream
|
593
|
+
when ::Integer
|
594
|
+
::IO.open(stream)
|
595
|
+
when ::Array
|
596
|
+
if stream.first == :child
|
597
|
+
if stream[1] == :err
|
598
|
+
$stderr
|
599
|
+
elsif stream[1] == :out
|
600
|
+
$stdout
|
601
|
+
end
|
602
|
+
else
|
603
|
+
::File.open(*stream)
|
604
|
+
end
|
605
|
+
when ::String
|
606
|
+
::File.open(stream, "w")
|
607
|
+
when :close
|
608
|
+
:close
|
609
|
+
else
|
610
|
+
stream if stream.respond_to?(:write)
|
611
|
+
end
|
453
612
|
end
|
454
613
|
|
455
614
|
def control_process(wait_thread)
|
@@ -476,6 +635,10 @@ module Toys
|
|
476
635
|
|
477
636
|
def setup_in_stream
|
478
637
|
setting = @config_opts[:in]
|
638
|
+
if setting.nil?
|
639
|
+
return if $stdin.respond_to?(:fileno) && $stdin.fileno.zero?
|
640
|
+
setting = $stdin
|
641
|
+
end
|
479
642
|
return unless setting
|
480
643
|
case setting
|
481
644
|
when ::Symbol
|
@@ -540,9 +703,12 @@ module Toys
|
|
540
703
|
@spawn_opts[:in] = args + [::File::RDONLY]
|
541
704
|
end
|
542
705
|
|
543
|
-
def setup_out_stream(key)
|
706
|
+
def setup_out_stream(key, stdstream, stdfileno)
|
544
707
|
setting = @config_opts[key]
|
545
|
-
|
708
|
+
if setting.nil?
|
709
|
+
return if setting.respond_to?(:fileno) && setting.fileno == stdfileno
|
710
|
+
setting = stdstream
|
711
|
+
end
|
546
712
|
case setting
|
547
713
|
when ::Symbol
|
548
714
|
setup_out_stream_of_type(key, setting, [])
|
@@ -617,6 +783,7 @@ module Toys
|
|
617
783
|
r, w = ::IO.pipe
|
618
784
|
@spawn_opts[:in] = r
|
619
785
|
@child_streams << r
|
786
|
+
@parent_streams << w
|
620
787
|
w.sync = true
|
621
788
|
w
|
622
789
|
end
|
@@ -625,6 +792,7 @@ module Toys
|
|
625
792
|
r, w = ::IO.pipe
|
626
793
|
@spawn_opts[key] = w
|
627
794
|
@child_streams << w
|
795
|
+
@parent_streams << r
|
628
796
|
r
|
629
797
|
end
|
630
798
|
|
data/lib/toys/utils/gems.rb
CHANGED
@@ -74,7 +74,8 @@ module Toys
|
|
74
74
|
end
|
75
75
|
|
76
76
|
##
|
77
|
-
# Activate the given gem.
|
77
|
+
# Activate the given gem. If it is not present, attempt to install it (or
|
78
|
+
# inform the user to update the bundle).
|
78
79
|
#
|
79
80
|
# @param [String] name Name of the gem
|
80
81
|
# @param [String...] requirements Version requirements
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toys-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-06-
|
11
|
+
date: 2018-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|