toys-core 0.10.5 → 0.11.0
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 +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:
|