toys-core 0.10.5 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/docs/guide.md +2 -0
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/tool.rb +17 -0
- data/lib/toys/loader.rb +71 -31
- data/lib/toys/source_info.rb +22 -23
- data/lib/toys/standard_mixins/bundler.rb +113 -56
- data/lib/toys/utils/exec.rb +88 -24
- data/lib/toys/utils/gems.rb +39 -14
- data/lib/toys/utils/help_text.rb +54 -59
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cf20d64fb78cfa559000a8007d3f0567db38d1c66ea220daaf8a33f1275fcad
|
4
|
+
data.tar.gz: da6b5f0f2e7d7bef30ded0a45b17014e0adc623583b7221f67b6b44fc9ca3805
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e429973defa957eb444c1395c9d6c235052f7f365978d216e7fc4633ebbb7361242ce649845304cd96c47d5227e563d79021c41bd0d2529e9d9b4695edb09ae6
|
7
|
+
data.tar.gz: 4cc2e28b0309130bd8c524a6ab3c17018e9160db4251225ad88aa082cf1ab1226eabda6d0e1ba8691d7ee2c107e8c6b4ea661265422d045a3a27692ce4b5745a
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 0.11.0 / 2020-08-21
|
4
|
+
|
5
|
+
* ADDED: The load path can be truncated using the `truncate_load_path!` directive.
|
6
|
+
* IMPROVED: Generated help for delegates now includes the information for the target tool, plus subtools of the delegate.
|
7
|
+
* IMPROVED: The `:bundler` mixin searches for `gems.rb` and `.gems.rb` in addition to `Gemfile`.
|
8
|
+
* IMPROVED: The `:budnler` mixin can load a specific Gemfile path.
|
9
|
+
* FIXED: The loader can now find data and lib directories at the root level of a Toys directory.
|
10
|
+
* FIXED: Exec::Result correctly reports processes that terminated due to signals.
|
11
|
+
* FIXED: Fixed a rare Exec capture failure that resulted from a race condition when closing streams.
|
12
|
+
|
3
13
|
### 0.10.5 / 2020-07-18
|
4
14
|
|
5
15
|
* IMPROVED: The bundler mixin silences bundler output during bundle setup.
|
6
16
|
* IMPROVED: The bundler mixin allows toys and toys-core to be in the Gemfile. It checks their version requirements against the running Toys version, and either adds the corret version to the bundle or raises IncompatibleToysError.
|
7
|
-
* IMPROVED: The bundler mixin
|
17
|
+
* IMPROVED: The bundler mixin automatically updates the bundle if install fails (typically because a transitive dependency has been explicitly updated.)
|
8
18
|
* FIXED: Some cases of transitive dependency handling by the bundler mixin.
|
9
19
|
* FIXED: Fixed a crash when computing suggestions, when running with a bundle on Ruby 2.6 or earlier.
|
10
20
|
|
data/docs/guide.md
CHANGED
data/lib/toys/core.rb
CHANGED
data/lib/toys/dsl/tool.rb
CHANGED
@@ -1619,6 +1619,23 @@ module Toys
|
|
1619
1619
|
self
|
1620
1620
|
end
|
1621
1621
|
|
1622
|
+
##
|
1623
|
+
# Remove lower-priority sources from the load path. This prevents lower-
|
1624
|
+
# priority sources (such as Toys files from parent or global directories)
|
1625
|
+
# from executing or defining tools.
|
1626
|
+
#
|
1627
|
+
# This works only if no such sources have already loaded yet.
|
1628
|
+
#
|
1629
|
+
# @raise [Toys::ToolDefinitionError] if any lower-priority tools have
|
1630
|
+
# already been loaded.
|
1631
|
+
#
|
1632
|
+
def truncate_load_path!
|
1633
|
+
unless @__loader.stop_loading_at_priority(@__priority)
|
1634
|
+
raise ToolDefinitionError,
|
1635
|
+
"Cannot truncate load path because tools have already been loaded"
|
1636
|
+
end
|
1637
|
+
end
|
1638
|
+
|
1622
1639
|
##
|
1623
1640
|
# Determines whether the current Toys version satisfies the given
|
1624
1641
|
# requirements.
|
data/lib/toys/loader.rb
CHANGED
@@ -8,6 +8,9 @@ module Toys
|
|
8
8
|
# appropriate tool given a set of command line arguments.
|
9
9
|
#
|
10
10
|
class Loader
|
11
|
+
# @private
|
12
|
+
BASE_PRIORITY = -999_999
|
13
|
+
|
11
14
|
##
|
12
15
|
# Create a Loader
|
13
16
|
#
|
@@ -69,9 +72,11 @@ module Toys
|
|
69
72
|
@worklist = []
|
70
73
|
@tool_data = {}
|
71
74
|
@max_priority = @min_priority = 0
|
75
|
+
@stop_priority = BASE_PRIORITY
|
76
|
+
@min_loaded_priority = 999_999
|
72
77
|
@middleware_stack = Middleware.stack(middleware_stack)
|
73
78
|
@delimiter_handler = DelimiterHandler.new(extra_delimiters)
|
74
|
-
get_tool([],
|
79
|
+
get_tool([], BASE_PRIORITY)
|
75
80
|
end
|
76
81
|
|
77
82
|
##
|
@@ -89,7 +94,7 @@ module Toys
|
|
89
94
|
raise "Cannot add a path after tool loading has started" if @loading_started
|
90
95
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
91
96
|
paths.each do |path|
|
92
|
-
source = SourceInfo.create_path_root(path)
|
97
|
+
source = SourceInfo.create_path_root(path, @data_dir_name, @lib_dir_name)
|
93
98
|
@worklist << [source, [], priority]
|
94
99
|
end
|
95
100
|
end
|
@@ -114,7 +119,7 @@ module Toys
|
|
114
119
|
@mutex.synchronize do
|
115
120
|
raise "Cannot add a block after tool loading has started" if @loading_started
|
116
121
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
117
|
-
source = SourceInfo.create_proc_root(block, name)
|
122
|
+
source = SourceInfo.create_proc_root(block, name, @data_dir_name, @lib_dir_name)
|
118
123
|
@worklist << [source, [], priority]
|
119
124
|
end
|
120
125
|
self
|
@@ -178,15 +183,15 @@ module Toys
|
|
178
183
|
load_for_prefix(words)
|
179
184
|
found_tools = []
|
180
185
|
len = words.length
|
181
|
-
|
182
|
-
|
186
|
+
all_cur_definitions.each do |tool|
|
187
|
+
name = tool.full_name
|
188
|
+
next if name.empty?
|
183
189
|
if recursive
|
184
|
-
next if
|
190
|
+
next if name.length <= len || name.slice(0, len) != words
|
185
191
|
else
|
186
|
-
next unless
|
192
|
+
next unless name.slice(0..-2) == words
|
187
193
|
end
|
188
|
-
|
189
|
-
found_tools << tool unless tool.nil?
|
194
|
+
found_tools << tool
|
190
195
|
end
|
191
196
|
sort_tools_by_name(found_tools)
|
192
197
|
include_hidden ? found_tools : filter_hidden_subtools(found_tools)
|
@@ -202,8 +207,9 @@ module Toys
|
|
202
207
|
def has_subtools?(words) # rubocop:disable Naming/PredicateName
|
203
208
|
load_for_prefix(words)
|
204
209
|
len = words.length
|
205
|
-
|
206
|
-
|
210
|
+
all_cur_definitions.each do |tool|
|
211
|
+
name = tool.full_name
|
212
|
+
if !name.empty? && name.length > len && name.slice(0, len) == words
|
207
213
|
return true
|
208
214
|
end
|
209
215
|
end
|
@@ -267,6 +273,20 @@ module Toys
|
|
267
273
|
Tool.new(self, parent, words, priority, middleware_stack, @middleware_lookup)
|
268
274
|
end
|
269
275
|
|
276
|
+
##
|
277
|
+
# Stop search at the given priority. Returns true if successful.
|
278
|
+
# Called only from the DSL.
|
279
|
+
#
|
280
|
+
# @private
|
281
|
+
#
|
282
|
+
def stop_loading_at_priority(priority)
|
283
|
+
@mutex.synchronize do
|
284
|
+
return false if priority > @min_loaded_priority || priority < @stop_priority
|
285
|
+
@stop_priority = priority
|
286
|
+
true
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
270
290
|
##
|
271
291
|
# Loads the subtree under the given prefix.
|
272
292
|
#
|
@@ -278,6 +298,7 @@ module Toys
|
|
278
298
|
cur_worklist = @worklist
|
279
299
|
@worklist = []
|
280
300
|
cur_worklist.each do |source, words, priority|
|
301
|
+
next if priority < @stop_priority
|
281
302
|
remaining_words = calc_remaining_words(prefix, words)
|
282
303
|
if source.source_proc
|
283
304
|
load_proc(source, words, remaining_words, priority)
|
@@ -349,8 +370,15 @@ module Toys
|
|
349
370
|
|
350
371
|
private
|
351
372
|
|
352
|
-
def
|
353
|
-
|
373
|
+
def all_cur_definitions
|
374
|
+
result = []
|
375
|
+
@mutex.synchronize do
|
376
|
+
@tool_data.map do |_name, td|
|
377
|
+
tool = td.cur_definition
|
378
|
+
result << tool unless tool.nil?
|
379
|
+
end
|
380
|
+
end
|
381
|
+
result
|
354
382
|
end
|
355
383
|
|
356
384
|
def get_tool_data(words)
|
@@ -364,14 +392,16 @@ module Toys
|
|
364
392
|
def finish_definitions_in_tree(words)
|
365
393
|
load_for_prefix(words)
|
366
394
|
len = words.length
|
367
|
-
|
368
|
-
|
369
|
-
|
395
|
+
all_cur_definitions.each do |tool|
|
396
|
+
name = tool.full_name
|
397
|
+
next if name.length < len || name.slice(0, len) != words
|
398
|
+
tool.finish_definition(self)
|
370
399
|
end
|
371
400
|
end
|
372
401
|
|
373
402
|
def load_proc(source, words, remaining_words, priority)
|
374
403
|
if remaining_words
|
404
|
+
update_min_loaded_priority(priority)
|
375
405
|
tool_class = get_tool(words, priority).tool_class
|
376
406
|
DSL::Tool.prepare(tool_class, remaining_words, source) do
|
377
407
|
ContextualError.capture("Error while loading Toys config!") do
|
@@ -393,6 +423,7 @@ module Toys
|
|
393
423
|
|
394
424
|
def load_relevant_path(source, words, remaining_words, priority)
|
395
425
|
if source.source_type == :file
|
426
|
+
update_min_loaded_priority(priority)
|
396
427
|
tool_class = get_tool(words, priority).tool_class
|
397
428
|
InputFile.evaluate(tool_class, remaining_words, source)
|
398
429
|
else
|
@@ -406,7 +437,7 @@ module Toys
|
|
406
437
|
|
407
438
|
def load_index_in(source, words, remaining_words, priority)
|
408
439
|
return unless @index_file_name
|
409
|
-
index_source = source.relative_child(@index_file_name
|
440
|
+
index_source = source.relative_child(@index_file_name)
|
410
441
|
load_relevant_path(index_source, words, remaining_words, priority) if index_source
|
411
442
|
end
|
412
443
|
|
@@ -414,7 +445,7 @@ module Toys
|
|
414
445
|
return if child.start_with?(".") || child == @index_file_name ||
|
415
446
|
child == @preload_file_name || child == @preload_dir_name ||
|
416
447
|
child == @data_dir_name || child == @lib_dir_name
|
417
|
-
child_source = source.relative_child(child
|
448
|
+
child_source = source.relative_child(child)
|
418
449
|
return unless child_source
|
419
450
|
child_word = ::File.basename(child, ".rb")
|
420
451
|
next_words = words + [child_word]
|
@@ -422,6 +453,10 @@ module Toys
|
|
422
453
|
load_validated_path(child_source, next_words, next_remaining, priority)
|
423
454
|
end
|
424
455
|
|
456
|
+
def update_min_loaded_priority(priority)
|
457
|
+
@min_loaded_priority = priority if @min_loaded_priority > priority
|
458
|
+
end
|
459
|
+
|
425
460
|
def do_preload(path)
|
426
461
|
if @preload_file_name
|
427
462
|
preload_file = ::File.join(path, @preload_file_name)
|
@@ -483,37 +518,42 @@ module Toys
|
|
483
518
|
# @private
|
484
519
|
#
|
485
520
|
class ToolData
|
486
|
-
|
521
|
+
# @private
|
487
522
|
def initialize(words)
|
488
523
|
@words = words
|
489
524
|
@definitions = {}
|
490
525
|
@top_priority = @active_priority = nil
|
526
|
+
@mutex = ::Monitor.new
|
491
527
|
end
|
492
528
|
|
493
|
-
|
529
|
+
# @private
|
494
530
|
def cur_definition
|
495
|
-
active_definition || top_definition
|
531
|
+
@mutex.synchronize { active_definition || top_definition }
|
496
532
|
end
|
497
533
|
|
498
|
-
|
534
|
+
# @private
|
499
535
|
def empty?
|
500
536
|
@definitions.empty?
|
501
537
|
end
|
502
538
|
|
503
|
-
|
539
|
+
# @private
|
504
540
|
def get_tool(priority, loader)
|
505
|
-
|
506
|
-
@top_priority
|
541
|
+
@mutex.synchronize do
|
542
|
+
if @top_priority.nil? || @top_priority < priority
|
543
|
+
@top_priority = priority
|
544
|
+
end
|
545
|
+
@definitions[priority] ||= loader.build_tool(@words, priority)
|
507
546
|
end
|
508
|
-
@definitions[priority] ||= loader.build_tool(@words, priority)
|
509
547
|
end
|
510
548
|
|
511
|
-
|
549
|
+
# @private
|
512
550
|
def activate_tool(priority, loader)
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
551
|
+
@mutex.synchronize do
|
552
|
+
return active_definition if @active_priority == priority
|
553
|
+
return nil if @active_priority && @active_priority > priority
|
554
|
+
@active_priority = priority
|
555
|
+
get_tool(priority, loader)
|
556
|
+
end
|
517
557
|
end
|
518
558
|
|
519
559
|
private
|
data/lib/toys/source_info.rb
CHANGED
@@ -10,7 +10,7 @@ module Toys
|
|
10
10
|
# @private
|
11
11
|
#
|
12
12
|
def initialize(parent, context_directory, source_type, source_path, source_proc,
|
13
|
-
source_name,
|
13
|
+
source_name, data_dir_name, lib_dir_name)
|
14
14
|
@parent = parent
|
15
15
|
@context_directory = context_directory
|
16
16
|
@source_type = source_type
|
@@ -18,8 +18,10 @@ module Toys
|
|
18
18
|
@source_path = source_path
|
19
19
|
@source_proc = source_proc
|
20
20
|
@source_name = source_name
|
21
|
-
@
|
22
|
-
@
|
21
|
+
@data_dir_name = data_dir_name
|
22
|
+
@lib_dir_name = lib_dir_name
|
23
|
+
@data_dir = find_special_dir(data_dir_name)
|
24
|
+
@lib_dir = find_special_dir(lib_dir_name)
|
23
25
|
end
|
24
26
|
|
25
27
|
##
|
@@ -118,15 +120,13 @@ module Toys
|
|
118
120
|
# Create a child SourceInfo relative to the parent path.
|
119
121
|
# @private
|
120
122
|
#
|
121
|
-
def relative_child(filename
|
122
|
-
raise "
|
123
|
+
def relative_child(filename)
|
124
|
+
raise "relative_child is valid only on a directory source" unless source_type == :directory
|
123
125
|
child_path = ::File.join(source_path, filename)
|
124
126
|
child_path, type = SourceInfo.check_path(child_path, true)
|
125
127
|
return nil unless child_path
|
126
|
-
|
127
|
-
|
128
|
-
SourceInfo.new(self, context_directory, type, child_path, source_proc, child_path,
|
129
|
-
data_dir, lib_dir)
|
128
|
+
SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
|
129
|
+
@data_dir_name, @lib_dir_name)
|
130
130
|
end
|
131
131
|
|
132
132
|
##
|
@@ -135,7 +135,8 @@ module Toys
|
|
135
135
|
#
|
136
136
|
def absolute_child(child_path)
|
137
137
|
child_path, type = SourceInfo.check_path(child_path, false)
|
138
|
-
SourceInfo.new(self, context_directory, type, child_path,
|
138
|
+
SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
|
139
|
+
@data_dir_name, @lib_dir_name)
|
139
140
|
end
|
140
141
|
|
141
142
|
##
|
@@ -144,25 +145,26 @@ module Toys
|
|
144
145
|
#
|
145
146
|
def proc_child(child_proc, source_name = nil)
|
146
147
|
source_name ||= self.source_name
|
147
|
-
SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name,
|
148
|
+
SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name,
|
149
|
+
@data_dir_name, @lib_dir_name)
|
148
150
|
end
|
149
151
|
|
150
152
|
##
|
151
153
|
# Create a root source info for a file path.
|
152
154
|
# @private
|
153
155
|
#
|
154
|
-
def self.create_path_root(source_path)
|
156
|
+
def self.create_path_root(source_path, data_dir_name, lib_dir_name)
|
155
157
|
source_path, type = check_path(source_path, false)
|
156
158
|
context_directory = ::File.dirname(source_path)
|
157
|
-
new(nil, context_directory, type, source_path, nil, source_path,
|
159
|
+
new(nil, context_directory, type, source_path, nil, source_path, data_dir_name, lib_dir_name)
|
158
160
|
end
|
159
161
|
|
160
162
|
##
|
161
163
|
# Create a root source info for a proc.
|
162
164
|
# @private
|
163
165
|
#
|
164
|
-
def self.create_proc_root(source_proc, source_name)
|
165
|
-
new(nil, nil, :proc, nil, source_proc, source_name,
|
166
|
+
def self.create_proc_root(source_proc, source_name, data_dir_name, lib_dir_name)
|
167
|
+
new(nil, nil, :proc, nil, source_proc, source_name, data_dir_name, lib_dir_name)
|
166
168
|
end
|
167
169
|
|
168
170
|
##
|
@@ -189,14 +191,11 @@ module Toys
|
|
189
191
|
end
|
190
192
|
end
|
191
193
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
return nil if source_path.nil? || dir_name.nil?
|
198
|
-
source_path = ::File.dirname(source_path) if type == :file
|
199
|
-
dir = ::File.join(source_path, dir_name)
|
194
|
+
private
|
195
|
+
|
196
|
+
def find_special_dir(dir_name)
|
197
|
+
return nil if @source_type != :directory || dir_name.nil?
|
198
|
+
dir = ::File.join(@source_path, dir_name)
|
200
199
|
dir if ::File.directory?(dir) && ::File.readable?(dir)
|
201
200
|
end
|
202
201
|
end
|
@@ -11,32 +11,51 @@ module Toys
|
|
11
11
|
# defining the tool. If `false` (the default), installs the bundle just
|
12
12
|
# before the tool runs.
|
13
13
|
#
|
14
|
-
# * `:groups` (Array
|
14
|
+
# * `:groups` (Array\<String\>) The groups to include in setup.
|
15
15
|
#
|
16
|
-
# * `:
|
17
|
-
# Gemfile.
|
16
|
+
# * `:gemfile_path` (String) The path to the Gemfile to use. If `nil` or
|
17
|
+
# not given, the `:search_dirs` will be searched for a Gemfile.
|
18
|
+
#
|
19
|
+
# * `:search_dirs` (String,Symbol,Array\<String,Symbol\>) Directories to
|
20
|
+
# search for a Gemfile.
|
18
21
|
#
|
19
22
|
# You can pass full directory paths, and/or any of the following:
|
20
|
-
# * `:context` - the current context directory
|
21
|
-
# * `:current` - the current working directory
|
22
|
-
# * `:toys` - the Toys directory containing the tool definition
|
23
|
+
# * `:context` - the current context directory.
|
24
|
+
# * `:current` - the current working directory.
|
25
|
+
# * `:toys` - the Toys directory containing the tool definition, and
|
26
|
+
# any of its parents within the Toys directory hierarchy.
|
23
27
|
#
|
24
28
|
# The default is to search `[:toys, :context, :current]` in that order.
|
29
|
+
# See {DEFAULT_SEARCH_DIRS}.
|
30
|
+
#
|
31
|
+
# For most directories, the bundler mixin will look for the files
|
32
|
+
# ".gems.rb", "gems.rb", and "Gemfile", in that order. In `:toys`
|
33
|
+
# directories, it will look only for ".gems.rb" and "Gemfile", in that
|
34
|
+
# order. These can be overridden by setting the `:gemfile_names` and/or
|
35
|
+
# `:toys_gemfile_names` arguments.
|
36
|
+
#
|
37
|
+
# * `:gemfile_names` (Array\<String\>) File names that are recognized as
|
38
|
+
# Gemfiles when searching in directories other than Toys directories.
|
39
|
+
# Defaults to {Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES}.
|
40
|
+
#
|
41
|
+
# * `:toys_gemfile_names` (Array\<String\>) File names that are
|
42
|
+
# recognized as Gemfiles when wearching in Toys directories.
|
43
|
+
# Defaults to {DEFAULT_TOYS_GEMFILE_NAMES}.
|
25
44
|
#
|
26
45
|
# * `:on_missing` (Symbol) What to do if a needed gem is not installed.
|
27
46
|
#
|
28
47
|
# Supported values:
|
29
|
-
# * `:confirm` - prompt the user on whether to install (default)
|
30
|
-
# * `:error` - raise an exception
|
31
|
-
# * `:install` - just install the gem
|
48
|
+
# * `:confirm` - prompt the user on whether to install (default).
|
49
|
+
# * `:error` - raise an exception.
|
50
|
+
# * `:install` - just install the gem.
|
32
51
|
#
|
33
52
|
# * `:on_conflict` (Symbol) What to do if bundler has already been run
|
34
53
|
# with a different Gemfile.
|
35
54
|
#
|
36
55
|
# Supported values:
|
37
|
-
# * `:error` - raise an exception (default)
|
38
|
-
# * `:ignore` - just silently proceed without bundling again
|
39
|
-
# * `:warn` - print a warning and proceed without bundling again
|
56
|
+
# * `:error` - raise an exception (default).
|
57
|
+
# * `:ignore` - just silently proceed without bundling again.
|
58
|
+
# * `:warn` - print a warning and proceed without bundling again.
|
40
59
|
#
|
41
60
|
# * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
|
42
61
|
# * `:input` (IO) Input IO (optional, defaults to STDIN)
|
@@ -45,68 +64,106 @@ module Toys
|
|
45
64
|
module Bundler
|
46
65
|
include Mixin
|
47
66
|
|
48
|
-
on_initialize do |static: false,
|
67
|
+
on_initialize do |static: false, **kwargs|
|
49
68
|
unless static
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
self[::Toys::Context::Key::CONTEXT_DIRECTORY],
|
54
|
-
self[::Toys::Context::Key::TOOL_SOURCE]
|
55
|
-
)
|
56
|
-
::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
|
69
|
+
context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
|
70
|
+
source_info = self[::Toys::Context::Key::TOOL_SOURCE]
|
71
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
57
72
|
end
|
58
73
|
end
|
59
74
|
|
60
|
-
on_include do |static: false,
|
75
|
+
on_include do |static: false, **kwargs|
|
61
76
|
if static
|
62
|
-
|
63
|
-
search_dirs = ::Toys::StandardMixins::Bundler.resolve_search_dirs(
|
64
|
-
search_dirs, context_directory, source_info
|
65
|
-
)
|
66
|
-
::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
|
77
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
67
78
|
end
|
68
79
|
end
|
69
80
|
|
70
|
-
##
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
when :context
|
76
|
-
context_dir
|
77
|
-
when :current
|
78
|
-
::Dir.getwd
|
79
|
-
when :toys
|
80
|
-
toys_dir_stack(source_info)
|
81
|
-
when ::String
|
82
|
-
dir
|
83
|
-
else
|
84
|
-
raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
81
|
+
##
|
82
|
+
# Default search directories for Gemfiles.
|
83
|
+
# @return [Array<String,Symbol>]
|
84
|
+
#
|
85
|
+
DEFAULT_SEARCH_DIRS = [:toys, :context, :current].freeze
|
88
86
|
|
89
|
-
##
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
source_info = source_info.parent
|
95
|
-
end
|
96
|
-
dirs
|
97
|
-
end
|
87
|
+
##
|
88
|
+
# The gemfile names that are searched by default in Toys directories.
|
89
|
+
# @return [Array<String>]
|
90
|
+
#
|
91
|
+
DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
|
98
92
|
|
99
|
-
|
100
|
-
def self.setup_bundle(
|
93
|
+
# @private
|
94
|
+
def self.setup_bundle(context_directory,
|
95
|
+
source_info,
|
96
|
+
gemfile_path: nil,
|
97
|
+
search_dirs: nil,
|
98
|
+
gemfile_names: nil,
|
99
|
+
toys_gemfile_names: nil,
|
101
100
|
groups: nil,
|
102
101
|
on_missing: nil,
|
103
102
|
on_conflict: nil,
|
104
103
|
terminal: nil,
|
105
104
|
input: nil,
|
106
105
|
output: nil)
|
106
|
+
require "toys/utils/gems"
|
107
|
+
gemfile_path ||= begin
|
108
|
+
gemfile_finder = GemfileFinder.new(context_directory, source_info,
|
109
|
+
gemfile_names, toys_gemfile_names)
|
110
|
+
gemfile_finder.search(search_dirs || DEFAULT_SEARCH_DIRS)
|
111
|
+
end
|
107
112
|
gems = ::Toys::Utils::Gems.new(on_missing: on_missing, on_conflict: on_conflict,
|
108
113
|
terminal: terminal, input: input, output: output)
|
109
|
-
gems.bundle(groups: groups,
|
114
|
+
gems.bundle(groups: groups, gemfile_path: gemfile_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
class GemfileFinder
|
119
|
+
# @private
|
120
|
+
def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
|
121
|
+
@context_directory = context_directory
|
122
|
+
@source_info = source_info
|
123
|
+
@gemfile_names = gemfile_names
|
124
|
+
@toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
def search(search_dir)
|
129
|
+
case search_dir
|
130
|
+
when ::Array
|
131
|
+
search_array(search_dir)
|
132
|
+
when ::String
|
133
|
+
::Toys::Utils::Gems.find_gemfile(search_dir, gemfile_names: @gemfile_names)
|
134
|
+
when :context
|
135
|
+
search(@context_directory)
|
136
|
+
when :current
|
137
|
+
search(::Dir.getwd)
|
138
|
+
when :toys
|
139
|
+
search_toys
|
140
|
+
else
|
141
|
+
raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def search_array(search_dirs)
|
148
|
+
search_dirs.each do |search_dir|
|
149
|
+
result = search(search_dir)
|
150
|
+
return result if result
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def search_toys
|
156
|
+
source_info = @source_info
|
157
|
+
while source_info
|
158
|
+
if source_info.source_type == :directory
|
159
|
+
result = ::Toys::Utils::Gems.find_gemfile(source_info.source_path,
|
160
|
+
gemfile_names: @toys_gemfile_names)
|
161
|
+
return result if result
|
162
|
+
end
|
163
|
+
source_info = source_info.parent
|
164
|
+
end
|
165
|
+
nil
|
166
|
+
end
|
110
167
|
end
|
111
168
|
end
|
112
169
|
end
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -547,7 +547,7 @@ module Toys
|
|
547
547
|
##
|
548
548
|
# The process ID.
|
549
549
|
#
|
550
|
-
# Exactly one of
|
550
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
551
551
|
#
|
552
552
|
# @return [Integer] if the process start was successful
|
553
553
|
# @return [nil] if the process could not be started.
|
@@ -557,7 +557,7 @@ module Toys
|
|
557
557
|
##
|
558
558
|
# The exception raised when the process failed to start.
|
559
559
|
#
|
560
|
-
# Exactly one of
|
560
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
561
561
|
#
|
562
562
|
# @return [Exception] if the process failed to start.
|
563
563
|
# @return [nil] if the process start was successful.
|
@@ -722,15 +722,20 @@ module Toys
|
|
722
722
|
##
|
723
723
|
# Wait for the subcommand to complete, and return a result object.
|
724
724
|
#
|
725
|
+
# Closes the control streams if present. The stdin stream is always
|
726
|
+
# closed, even if the call times out. The stdout and stderr streams are
|
727
|
+
# closed only after the command terminates.
|
728
|
+
#
|
725
729
|
# @param timeout [Numeric,nil] The timeout in seconds, or `nil` to
|
726
730
|
# wait indefinitely.
|
727
731
|
# @return [Toys::Utils::Exec::Result] The result object
|
728
732
|
# @return [nil] if a timeout occurred.
|
729
733
|
#
|
730
734
|
def result(timeout: nil)
|
735
|
+
close_streams(:in)
|
731
736
|
return nil if @wait_thread && !@wait_thread.join(timeout)
|
732
737
|
@result ||= begin
|
733
|
-
close_streams
|
738
|
+
close_streams(:out)
|
734
739
|
@join_threads.each(&:join)
|
735
740
|
Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
|
736
741
|
.tap { |result| @result_callback&.call(result) }
|
@@ -738,13 +743,13 @@ module Toys
|
|
738
743
|
end
|
739
744
|
|
740
745
|
##
|
741
|
-
# Close
|
746
|
+
# Close the controller's streams.
|
742
747
|
# @private
|
743
748
|
#
|
744
|
-
def close_streams
|
745
|
-
@in.close if @in && !@in.closed?
|
746
|
-
@out.close if @out && !@out.closed?
|
747
|
-
@err.close if @err && !@err.closed?
|
749
|
+
def close_streams(which)
|
750
|
+
@in.close if which != :out && @in && !@in.closed?
|
751
|
+
@out.close if which != :in && @out && !@out.closed?
|
752
|
+
@err.close if which != :in && @err && !@err.closed?
|
748
753
|
self
|
749
754
|
end
|
750
755
|
|
@@ -773,7 +778,21 @@ module Toys
|
|
773
778
|
end
|
774
779
|
|
775
780
|
##
|
776
|
-
# The result returned from a subcommand execution.
|
781
|
+
# The result returned from a subcommand execution. This includes the
|
782
|
+
# identifying name of the execution (if any), the result status of the
|
783
|
+
# execution, and any captured stream output.
|
784
|
+
#
|
785
|
+
# Possible result statuses are:
|
786
|
+
#
|
787
|
+
# * The process failed to start. {Result#failed?} will return true, and
|
788
|
+
# {Result#exception} will return an exception describing the failure
|
789
|
+
# (often an errno).
|
790
|
+
# * The process executed and exited with a normal exit code. Either
|
791
|
+
# {Result#success?} or {Result#error?} will return true, and
|
792
|
+
# {Result.exit_code} will return the numeric exit code.
|
793
|
+
# * The process executed but was terminated by an uncaught signal.
|
794
|
+
# {Result#signaled?} will return true, and {Result#term_signal} will
|
795
|
+
# return the numeric signal code.
|
777
796
|
#
|
778
797
|
class Result
|
779
798
|
## @private
|
@@ -809,11 +828,13 @@ module Toys
|
|
809
828
|
attr_reader :captured_err
|
810
829
|
|
811
830
|
##
|
812
|
-
# The status
|
831
|
+
# The Ruby process status object, providing various information about
|
832
|
+
# the ending state of the process.
|
813
833
|
#
|
814
|
-
# Exactly one of
|
834
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
815
835
|
#
|
816
|
-
# @return [Process::Status] The status
|
836
|
+
# @return [Process::Status] The status, if the process was successfully
|
837
|
+
# spanwed and terminated.
|
817
838
|
# @return [nil] if the process could not be started.
|
818
839
|
#
|
819
840
|
attr_reader :status
|
@@ -821,7 +842,7 @@ module Toys
|
|
821
842
|
##
|
822
843
|
# The exception raised if a process couldn't be started.
|
823
844
|
#
|
824
|
-
# Exactly one of
|
845
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
825
846
|
#
|
826
847
|
# @return [Exception] The exception raised from process start.
|
827
848
|
# @return [nil] if the process started successfully.
|
@@ -829,33 +850,76 @@ module Toys
|
|
829
850
|
attr_reader :exception
|
830
851
|
|
831
852
|
##
|
832
|
-
# The numeric status code
|
853
|
+
# The numeric status code for a process that exited normally,
|
833
854
|
#
|
834
|
-
#
|
835
|
-
#
|
855
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
856
|
+
# non-nil.
|
836
857
|
#
|
837
|
-
# @return [Integer]
|
858
|
+
# @return [Integer] the numeric status code, if the process started
|
859
|
+
# successfully and exited normally.
|
860
|
+
# @return [nil] if the process did not start successfully, or was
|
861
|
+
# terminated by an uncaught signal.
|
838
862
|
#
|
839
863
|
def exit_code
|
840
|
-
status
|
864
|
+
status&.exitstatus
|
865
|
+
end
|
866
|
+
|
867
|
+
##
|
868
|
+
# The numeric signal code that caused process termination.
|
869
|
+
#
|
870
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
871
|
+
# non-nil.
|
872
|
+
#
|
873
|
+
# @return [Integer] The signal that caused the process to terminate.
|
874
|
+
# @return [nil] if the process did not start successfully, or executed
|
875
|
+
# and exited with a normal exit code.
|
876
|
+
#
|
877
|
+
def term_signal
|
878
|
+
status&.termsig
|
879
|
+
end
|
880
|
+
|
881
|
+
##
|
882
|
+
# Returns true if the subprocess failed to start, or false if the
|
883
|
+
# process was able to execute.
|
884
|
+
#
|
885
|
+
# @return [Boolean]
|
886
|
+
#
|
887
|
+
def failed?
|
888
|
+
status.nil?
|
889
|
+
end
|
890
|
+
|
891
|
+
##
|
892
|
+
# Returns true if the subprocess terminated due to an unhandled signal,
|
893
|
+
# or false if the process failed to start or exited normally.
|
894
|
+
#
|
895
|
+
# @return [Boolean]
|
896
|
+
#
|
897
|
+
def signaled?
|
898
|
+
!term_signal.nil?
|
841
899
|
end
|
842
900
|
|
843
901
|
##
|
844
|
-
# Returns true if the subprocess terminated with a zero status
|
902
|
+
# Returns true if the subprocess terminated with a zero status, or
|
903
|
+
# false if the process failed to start, terminated due to a signal, or
|
904
|
+
# returned a nonzero status.
|
845
905
|
#
|
846
906
|
# @return [Boolean]
|
847
907
|
#
|
848
908
|
def success?
|
849
|
-
exit_code
|
909
|
+
code = exit_code
|
910
|
+
!code.nil? && code.zero?
|
850
911
|
end
|
851
912
|
|
852
913
|
##
|
853
|
-
# Returns true if the subprocess terminated with a nonzero status
|
914
|
+
# Returns true if the subprocess terminated with a nonzero status, or
|
915
|
+
# false if the process failed to start, terminated due to a signal, or
|
916
|
+
# returned a zero status.
|
854
917
|
#
|
855
918
|
# @return [Boolean]
|
856
919
|
#
|
857
920
|
def error?
|
858
|
-
|
921
|
+
code = exit_code
|
922
|
+
!code.nil? && !code.zero?
|
859
923
|
end
|
860
924
|
end
|
861
925
|
|
@@ -887,10 +951,10 @@ module Toys
|
|
887
951
|
return controller if @config_opts[:background]
|
888
952
|
begin
|
889
953
|
@block&.call(controller)
|
954
|
+
controller.result
|
890
955
|
ensure
|
891
|
-
controller.close_streams
|
956
|
+
controller.close_streams(:both)
|
892
957
|
end
|
893
|
-
controller.result
|
894
958
|
end
|
895
959
|
|
896
960
|
private
|
data/lib/toys/utils/gems.rb
CHANGED
@@ -71,6 +71,12 @@ module Toys
|
|
71
71
|
class IncompatibleToysError < BundlerFailedError
|
72
72
|
end
|
73
73
|
|
74
|
+
##
|
75
|
+
# The gemfile names that are searched by default.
|
76
|
+
# @return [Array<String>]
|
77
|
+
#
|
78
|
+
DEFAULT_GEMFILE_NAMES = [".gems.rb", "gems.rb", "Gemfile"].freeze
|
79
|
+
|
74
80
|
##
|
75
81
|
# Activate the given gem. If it is not present, attempt to install it (or
|
76
82
|
# inform the user to update the bundle).
|
@@ -149,16 +155,33 @@ module Toys
|
|
149
155
|
end
|
150
156
|
|
151
157
|
##
|
152
|
-
#
|
158
|
+
# Search for an appropriate Gemfile, and set up the bundle.
|
159
|
+
#
|
160
|
+
# @param groups [Array<String>] The groups to include in setup.
|
161
|
+
#
|
162
|
+
# @param gemfile_path [String] The path to the Gemfile to use. If `nil`
|
163
|
+
# or not given, the `:search_dirs` will be searched for a Gemfile.
|
164
|
+
#
|
165
|
+
# @param search_dirs [String,Array<String>] Directories in which to
|
166
|
+
# search for a Gemfile, if gemfile_path is not given. You can provide
|
167
|
+
# a single directory or an array of directories.
|
168
|
+
#
|
169
|
+
# @param gemfile_names [String,Array<String>] File names that are
|
170
|
+
# recognized as Gemfiles, when searching because gemfile_path is not
|
171
|
+
# given. Defaults to {DEFAULT_GEMFILE_NAMES}.
|
153
172
|
#
|
154
|
-
# @param groups [Array<String>] The groups to include in setup
|
155
|
-
# @param search_dirs [Array<String>] Directories to search for a Gemfile
|
156
173
|
# @return [void]
|
157
174
|
#
|
158
175
|
def bundle(groups: nil,
|
159
|
-
|
176
|
+
gemfile_path: nil,
|
177
|
+
search_dirs: nil,
|
178
|
+
gemfile_names: nil)
|
179
|
+
Array(search_dirs).each do |dir|
|
180
|
+
break if gemfile_path
|
181
|
+
gemfile_path = Gems.find_gemfile(dir, gemfile_names: gemfile_names)
|
182
|
+
end
|
183
|
+
raise GemfileNotFoundError, "Gemfile not found" unless gemfile_path
|
160
184
|
Gems.synchronize do
|
161
|
-
gemfile_path = find_gemfile(Array(search_dirs))
|
162
185
|
if configure_gemfile(gemfile_path)
|
163
186
|
activate("bundler", "~> 2.1")
|
164
187
|
require "bundler"
|
@@ -167,9 +190,19 @@ module Toys
|
|
167
190
|
end
|
168
191
|
end
|
169
192
|
|
193
|
+
# @private
|
194
|
+
def self.find_gemfile(search_dir, gemfile_names: nil)
|
195
|
+
gemfile_names ||= DEFAULT_GEMFILE_NAMES
|
196
|
+
Array(gemfile_names).each do |file|
|
197
|
+
gemfile_path = ::File.join(search_dir, file)
|
198
|
+
return gemfile_path if ::File.readable?(gemfile_path)
|
199
|
+
end
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
170
203
|
@global_mutex = ::Monitor.new
|
171
204
|
|
172
|
-
|
205
|
+
# @private
|
173
206
|
def self.synchronize(&block)
|
174
207
|
@global_mutex.synchronize(&block)
|
175
208
|
end
|
@@ -237,14 +270,6 @@ module Toys
|
|
237
270
|
raise ActivationFailedError, err.message
|
238
271
|
end
|
239
272
|
|
240
|
-
def find_gemfile(search_dirs)
|
241
|
-
search_dirs.each do |dir|
|
242
|
-
gemfile_path = ::File.join(dir, "Gemfile")
|
243
|
-
return gemfile_path if ::File.readable?(gemfile_path)
|
244
|
-
end
|
245
|
-
raise GemfileNotFoundError, "Gemfile not found"
|
246
|
-
end
|
247
|
-
|
248
273
|
def configure_gemfile(gemfile_path)
|
249
274
|
old_path = ::ENV["BUNDLE_GEMFILE"]
|
250
275
|
if old_path
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -32,14 +32,13 @@ module Toys
|
|
32
32
|
# @return [Toys::Utils::HelpText]
|
33
33
|
#
|
34
34
|
def self.from_context(context)
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
delegates = []
|
36
|
+
cur = context
|
37
|
+
while (cur = cur[Context::Key::DELEGATED_FROM])
|
38
|
+
delegates << cur[Context::Key::TOOL]
|
38
39
|
end
|
39
|
-
delegate_target = orig_context == context ? nil : orig_context[Context::Key::TOOL_NAME]
|
40
40
|
cli = context[Context::Key::CLI]
|
41
|
-
new(context[Context::Key::TOOL], cli.loader, cli.executable_name,
|
42
|
-
delegate_target: delegate_target)
|
41
|
+
new(context[Context::Key::TOOL], cli.loader, cli.executable_name, delegates: delegates)
|
43
42
|
end
|
44
43
|
|
45
44
|
##
|
@@ -49,16 +48,15 @@ module Toys
|
|
49
48
|
# @param loader [Toys::Loader] A loader that can provide subcommands.
|
50
49
|
# @param executable_name [String] The name of the executable.
|
51
50
|
# e.g. `"toys"`.
|
52
|
-
# @param
|
53
|
-
# tool will delegate to. Default is `nil` for no delegation.
|
51
|
+
# @param delegates [Array<Toys::Tool>] The delegation path to the tool.
|
54
52
|
#
|
55
53
|
# @return [Toys::Utils::HelpText]
|
56
54
|
#
|
57
|
-
def initialize(tool, loader, executable_name,
|
55
|
+
def initialize(tool, loader, executable_name, delegates: [])
|
58
56
|
@tool = tool
|
59
57
|
@loader = loader
|
60
58
|
@executable_name = executable_name
|
61
|
-
@
|
59
|
+
@delegates = delegates
|
62
60
|
end
|
63
61
|
|
64
62
|
##
|
@@ -88,8 +86,7 @@ module Toys
|
|
88
86
|
indent ||= DEFAULT_INDENT
|
89
87
|
subtools = find_subtools(recursive, nil, include_hidden)
|
90
88
|
assembler = UsageStringAssembler.new(
|
91
|
-
@tool, @executable_name,
|
92
|
-
indent, left_column_width, wrap_width
|
89
|
+
@tool, @executable_name, subtools, indent, left_column_width, wrap_width
|
93
90
|
)
|
94
91
|
assembler.result
|
95
92
|
end
|
@@ -121,7 +118,7 @@ module Toys
|
|
121
118
|
indent2 ||= DEFAULT_INDENT
|
122
119
|
subtools = find_subtools(recursive, search, include_hidden)
|
123
120
|
assembler = HelpStringAssembler.new(
|
124
|
-
@tool, @executable_name, @
|
121
|
+
@tool, @executable_name, @delegates, subtools, search, show_source_path,
|
125
122
|
indent, indent2, wrap_width, styled
|
126
123
|
)
|
127
124
|
assembler.result
|
@@ -155,22 +152,29 @@ module Toys
|
|
155
152
|
private
|
156
153
|
|
157
154
|
def find_subtools(recursive, search, include_hidden)
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
subtools_by_name = {}
|
156
|
+
([@tool] + @delegates).each do |tool|
|
157
|
+
name_len = tool.full_name.length
|
158
|
+
subtools = @loader.list_subtools(tool.full_name,
|
159
|
+
recursive: recursive, include_hidden: include_hidden)
|
160
|
+
subtools.each do |subtool|
|
161
|
+
local_name = subtool.full_name.slice(name_len..-1).join(" ")
|
162
|
+
subtools_by_name[local_name] = subtool
|
163
|
+
end
|
164
|
+
end
|
165
|
+
subtool_list = subtools_by_name.sort_by { |(local_name, _tool)| local_name }
|
166
|
+
return subtool_list if search.nil? || search.empty?
|
161
167
|
regex = ::Regexp.new(search, ::Regexp::IGNORECASE)
|
162
|
-
|
163
|
-
regex =~
|
168
|
+
subtool_list.find_all do |local_name, tool|
|
169
|
+
regex =~ local_name || regex =~ tool.desc.to_s
|
164
170
|
end
|
165
171
|
end
|
166
172
|
|
167
173
|
## @private
|
168
174
|
class UsageStringAssembler
|
169
|
-
def initialize(tool, executable_name,
|
170
|
-
indent, left_column_width, wrap_width)
|
175
|
+
def initialize(tool, executable_name, subtools, indent, left_column_width, wrap_width)
|
171
176
|
@tool = tool
|
172
177
|
@executable_name = executable_name
|
173
|
-
@delegate_target = delegate_target
|
174
178
|
@subtools = subtools
|
175
179
|
@indent = indent
|
176
180
|
@left_column_width = left_column_width
|
@@ -195,7 +199,7 @@ module Toys
|
|
195
199
|
def add_synopsis_section
|
196
200
|
synopses = []
|
197
201
|
synopses << namespace_synopsis unless @subtools.empty?
|
198
|
-
synopses <<
|
202
|
+
synopses << tool_synopsis
|
199
203
|
first = true
|
200
204
|
synopses.each do |synopsis|
|
201
205
|
@lines << (first ? "Usage: #{synopsis}" : " #{synopsis}")
|
@@ -212,11 +216,6 @@ module Toys
|
|
212
216
|
synopsis.join(" ")
|
213
217
|
end
|
214
218
|
|
215
|
-
def delegate_synopsis
|
216
|
-
target = @delegate_target.join(" ")
|
217
|
-
"#{@executable_name} #{@tool.display_name} [ARGUMENTS FOR \"#{target}\"...]"
|
218
|
-
end
|
219
|
-
|
220
219
|
def namespace_synopsis
|
221
220
|
"#{@executable_name} #{@tool.display_name} TOOL [ARGUMENTS...]"
|
222
221
|
end
|
@@ -256,12 +255,10 @@ module Toys
|
|
256
255
|
|
257
256
|
def add_subtool_list_section
|
258
257
|
return if @subtools.empty?
|
259
|
-
name_len = @tool.full_name.length
|
260
258
|
@lines << ""
|
261
259
|
@lines << "Tools:"
|
262
|
-
@subtools.each do |subtool|
|
263
|
-
|
264
|
-
add_right_column_desc(tool_name, wrap_desc(subtool.desc))
|
260
|
+
@subtools.each do |local_name, subtool|
|
261
|
+
add_right_column_desc(local_name, wrap_desc(subtool.desc))
|
265
262
|
end
|
266
263
|
end
|
267
264
|
|
@@ -301,12 +298,12 @@ module Toys
|
|
301
298
|
|
302
299
|
## @private
|
303
300
|
class HelpStringAssembler
|
304
|
-
def initialize(tool, executable_name,
|
301
|
+
def initialize(tool, executable_name, delegates, subtools, search_term,
|
305
302
|
show_source_path, indent, indent2, wrap_width, styled)
|
306
303
|
require "toys/utils/terminal"
|
307
304
|
@tool = tool
|
308
305
|
@executable_name = executable_name
|
309
|
-
@
|
306
|
+
@delegates = delegates
|
310
307
|
@subtools = subtools
|
311
308
|
@search_term = search_term
|
312
309
|
@show_source_path = show_source_path
|
@@ -356,7 +353,7 @@ module Toys
|
|
356
353
|
@lines << ""
|
357
354
|
@lines << bold("SYNOPSIS")
|
358
355
|
add_synopsis_clause(namespace_synopsis) unless @subtools.empty?
|
359
|
-
add_synopsis_clause(@
|
356
|
+
add_synopsis_clause(tool_synopsis(@tool))
|
360
357
|
end
|
361
358
|
|
362
359
|
def add_synopsis_clause(synopsis)
|
@@ -367,8 +364,8 @@ module Toys
|
|
367
364
|
end
|
368
365
|
end
|
369
366
|
|
370
|
-
def tool_synopsis
|
371
|
-
synopsis = [full_executable_name]
|
367
|
+
def tool_synopsis(tool_for_name)
|
368
|
+
synopsis = [full_executable_name(tool_for_name)]
|
372
369
|
@tool.flag_groups.each do |flag_group|
|
373
370
|
case flag_group
|
374
371
|
when FlagGroup::Required
|
@@ -441,19 +438,14 @@ module Toys
|
|
441
438
|
end
|
442
439
|
|
443
440
|
def namespace_synopsis
|
444
|
-
synopsis = [full_executable_name
|
441
|
+
synopsis = [full_executable_name(@tool),
|
442
|
+
underline("TOOL"),
|
443
|
+
"[#{underline('ARGUMENTS')}...]"]
|
445
444
|
wrap_indent_indent2(WrappableString.new(synopsis))
|
446
445
|
end
|
447
446
|
|
448
|
-
def
|
449
|
-
|
450
|
-
args_clause = underline("ARGUMENTS FOR \"#{target}\"")
|
451
|
-
synopsis = [full_executable_name, "[#{args_clause}...]"]
|
452
|
-
wrap_indent_indent2(WrappableString.new(synopsis))
|
453
|
-
end
|
454
|
-
|
455
|
-
def full_executable_name
|
456
|
-
bold(([@executable_name] + @tool.full_name).join(" "))
|
447
|
+
def full_executable_name(tool_for_name)
|
448
|
+
bold(([@executable_name] + tool_for_name.full_name).join(" "))
|
457
449
|
end
|
458
450
|
|
459
451
|
def add_source_section
|
@@ -461,15 +453,22 @@ module Toys
|
|
461
453
|
@lines << ""
|
462
454
|
@lines << bold("SOURCE")
|
463
455
|
@lines << indent_str("Defined in #{@tool.source_info.source_name}")
|
456
|
+
@delegates.each do |delegate|
|
457
|
+
@lines << indent_str("Delegated from \"#{delegate.display_name}\"" \
|
458
|
+
" defined in #{delegate.source_info.source_name}")
|
459
|
+
end
|
464
460
|
end
|
465
461
|
|
466
462
|
def add_description_section
|
467
|
-
desc = @tool.long_desc
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
463
|
+
desc = @tool.long_desc.dup
|
464
|
+
@delegates.each do |delegate|
|
465
|
+
desc << "" << "Delegated from \"#{delegate.display_name}\""
|
466
|
+
unless delegate.long_desc.empty?
|
467
|
+
desc << ""
|
468
|
+
desc += delegate.long_desc
|
469
|
+
end
|
472
470
|
end
|
471
|
+
desc = desc[1..-1] if desc.first == ""
|
473
472
|
desc = wrap_indent(desc)
|
474
473
|
return if desc.empty?
|
475
474
|
@lines << ""
|
@@ -533,10 +532,8 @@ module Toys
|
|
533
532
|
@lines << indent_str("Showing search results for \"#{@search_term}\"")
|
534
533
|
@lines << ""
|
535
534
|
end
|
536
|
-
|
537
|
-
|
538
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
539
|
-
add_prefix_with_desc(bold(tool_name), subtool.desc)
|
535
|
+
@subtools.each do |local_name, subtool|
|
536
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
540
537
|
end
|
541
538
|
end
|
542
539
|
|
@@ -637,10 +634,8 @@ module Toys
|
|
637
634
|
end
|
638
635
|
|
639
636
|
def add_list
|
640
|
-
|
641
|
-
|
642
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
643
|
-
add_prefix_with_desc(bold(tool_name), subtool.desc)
|
637
|
+
@subtools.each do |local_name, subtool|
|
638
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
644
639
|
end
|
645
640
|
end
|
646
641
|
|
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.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Toys-Core is the command line tool framework underlying Toys. It can
|
14
14
|
be used to create command line executables using the Toys DSL and classes.
|
@@ -69,10 +69,10 @@ homepage: https://github.com/dazuma/toys
|
|
69
69
|
licenses:
|
70
70
|
- MIT
|
71
71
|
metadata:
|
72
|
-
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
72
|
+
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.11.0/file.CHANGELOG.html
|
73
73
|
source_code_uri: https://github.com/dazuma/toys
|
74
74
|
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
75
|
-
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
75
|
+
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.11.0
|
76
76
|
post_install_message:
|
77
77
|
rdoc_options: []
|
78
78
|
require_paths:
|