toys-core 0.3.8 → 0.3.9
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 -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
|