toys-core 0.10.2 → 0.11.1
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 +31 -0
- data/README.md +5 -5
- data/docs/guide.md +2 -0
- data/lib/toys/acceptor.rb +1 -1
- data/lib/toys/compat.rb +13 -4
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/flag.rb +2 -2
- data/lib/toys/dsl/flag_group.rb +2 -2
- data/lib/toys/dsl/tool.rb +27 -6
- data/lib/toys/loader.rb +71 -31
- data/lib/toys/source_info.rb +22 -23
- data/lib/toys/standard_mixins/bundler.rb +121 -40
- data/lib/toys/standard_mixins/exec.rb +1 -1
- data/lib/toys/utils/exec.rb +88 -24
- data/lib/toys/utils/gems.rb +118 -31
- data/lib/toys/utils/help_text.rb +54 -59
- data/lib/toys/utils/terminal.rb +1 -1
- metadata +5 -131
data/lib/toys/utils/gems.rb
CHANGED
@@ -64,6 +64,19 @@ module Toys
|
|
64
64
|
class AlreadyBundledError < BundlerFailedError
|
65
65
|
end
|
66
66
|
|
67
|
+
##
|
68
|
+
# The bundle contained a toys or toys-core dependency that is
|
69
|
+
# incompatible with the currently running version.
|
70
|
+
#
|
71
|
+
class IncompatibleToysError < BundlerFailedError
|
72
|
+
end
|
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
|
+
|
67
80
|
##
|
68
81
|
# Activate the given gem. If it is not present, attempt to install it (or
|
69
82
|
# inform the user to update the bundle).
|
@@ -81,16 +94,22 @@ module Toys
|
|
81
94
|
#
|
82
95
|
# @param on_missing [:confirm,:error,:install] What to do if a needed gem
|
83
96
|
# is not installed. Possible values:
|
97
|
+
#
|
84
98
|
# * `:confirm` - prompt the user on whether to install
|
85
99
|
# * `:error` - raise an exception
|
86
100
|
# * `:install` - just install the gem
|
101
|
+
#
|
87
102
|
# The default is `:confirm`.
|
103
|
+
#
|
88
104
|
# @param on_conflict [:error,:warn,:ignore] What to do if bundler has
|
89
105
|
# already been run with a different Gemfile. Possible values:
|
106
|
+
#
|
90
107
|
# * `:error` - raise an exception
|
91
108
|
# * `:ignore` - just silently proceed without bundling again
|
92
109
|
# * `:warn` - print a warning and proceed without bundling again
|
110
|
+
#
|
93
111
|
# The default is `:error`.
|
112
|
+
#
|
94
113
|
# @param terminal [Toys::Utils::Terminal] Terminal to use (optional)
|
95
114
|
# @param input [IO] Input IO (optional, defaults to STDIN)
|
96
115
|
# @param output [IO] Output IO (optional, defaults to STDOUT)
|
@@ -136,26 +155,54 @@ module Toys
|
|
136
155
|
end
|
137
156
|
|
138
157
|
##
|
139
|
-
#
|
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}.
|
140
172
|
#
|
141
|
-
# @param groups [Array<String>] The groups to include in setup
|
142
|
-
# @param search_dirs [Array<String>] Directories to search for a Gemfile
|
143
173
|
# @return [void]
|
144
174
|
#
|
145
175
|
def bundle(groups: nil,
|
146
|
-
|
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
|
147
184
|
Gems.synchronize do
|
148
|
-
gemfile_path = find_gemfile(Array(search_dirs))
|
149
|
-
activate("bundler", "~> 2.1")
|
150
185
|
if configure_gemfile(gemfile_path)
|
186
|
+
activate("bundler", "~> 2.1")
|
187
|
+
require "bundler"
|
151
188
|
setup_bundle(gemfile_path, groups || [])
|
152
189
|
end
|
153
190
|
end
|
154
191
|
end
|
155
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
|
+
|
156
203
|
@global_mutex = ::Monitor.new
|
157
204
|
|
158
|
-
|
205
|
+
# @private
|
159
206
|
def self.synchronize(&block)
|
160
207
|
@global_mutex.synchronize(&block)
|
161
208
|
end
|
@@ -184,14 +231,14 @@ module Toys
|
|
184
231
|
error.message.include?("Could not find")
|
185
232
|
end
|
186
233
|
if !is_missing_spec || @on_missing == :error
|
187
|
-
|
234
|
+
report_activation_error(name, requirements, error)
|
188
235
|
return
|
189
236
|
end
|
190
237
|
confirm_and_install_gem(name, requirements)
|
191
238
|
begin
|
192
239
|
gem(name, *requirements)
|
193
240
|
rescue ::Gem::LoadError => e
|
194
|
-
|
241
|
+
report_activation_error(name, requirements, e)
|
195
242
|
end
|
196
243
|
end
|
197
244
|
|
@@ -215,7 +262,7 @@ module Toys
|
|
215
262
|
::Gem::Specification.reset
|
216
263
|
end
|
217
264
|
|
218
|
-
def
|
265
|
+
def report_activation_error(name, requirements, err)
|
219
266
|
if ::ENV["BUNDLE_GEMFILE"]
|
220
267
|
raise GemfileUpdateNeededError.new(gem_requirements_text(name, requirements),
|
221
268
|
::ENV["BUNDLE_GEMFILE"])
|
@@ -223,22 +270,16 @@ module Toys
|
|
223
270
|
raise ActivationFailedError, err.message
|
224
271
|
end
|
225
272
|
|
226
|
-
def find_gemfile(search_dirs)
|
227
|
-
search_dirs.each do |dir|
|
228
|
-
gemfile_path = ::File.join(dir, "Gemfile")
|
229
|
-
return gemfile_path if ::File.readable?(gemfile_path)
|
230
|
-
end
|
231
|
-
raise GemfileNotFoundError, "Gemfile not found"
|
232
|
-
end
|
233
|
-
|
234
273
|
def configure_gemfile(gemfile_path)
|
235
274
|
old_path = ::ENV["BUNDLE_GEMFILE"]
|
236
|
-
if old_path
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
275
|
+
if old_path
|
276
|
+
if gemfile_path != old_path
|
277
|
+
case @on_conflict
|
278
|
+
when :warn
|
279
|
+
terminal.puts("Warning: could not set up bundler because it is already set up.", :red)
|
280
|
+
when :error
|
281
|
+
raise AlreadyBundledError, "Could not set up bundler because it is already set up"
|
282
|
+
end
|
242
283
|
end
|
243
284
|
return false
|
244
285
|
end
|
@@ -247,21 +288,61 @@ module Toys
|
|
247
288
|
end
|
248
289
|
|
249
290
|
def setup_bundle(gemfile_path, groups)
|
250
|
-
require "bundler"
|
251
291
|
begin
|
252
|
-
|
253
|
-
|
292
|
+
modify_bundle_definition(gemfile_path)
|
293
|
+
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
294
|
+
rescue ::Bundler::GemNotFound, ::Bundler::VersionConflict
|
254
295
|
restore_toys_libs
|
255
296
|
install_bundle(gemfile_path)
|
256
297
|
::Bundler.reset!
|
257
|
-
|
298
|
+
modify_bundle_definition(gemfile_path)
|
299
|
+
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
258
300
|
end
|
259
301
|
restore_toys_libs
|
260
302
|
end
|
261
303
|
|
304
|
+
def modify_bundle_definition(gemfile_path)
|
305
|
+
builder = ::Bundler::Dsl.new
|
306
|
+
builder.eval_gemfile(gemfile_path)
|
307
|
+
toys_gems = ["toys-core"]
|
308
|
+
remove_gem_from_definition(builder, "toys-core")
|
309
|
+
removed_toys = remove_gem_from_definition(builder, "toys")
|
310
|
+
add_gem_to_definition(builder, "toys-core")
|
311
|
+
if removed_toys || ::Toys.const_defined?(:VERSION)
|
312
|
+
add_gem_to_definition(builder, "toys")
|
313
|
+
toys_gems << "toys"
|
314
|
+
end
|
315
|
+
definition = builder.to_definition(gemfile_path + ".lock", { gems: toys_gems })
|
316
|
+
::Bundler.instance_variable_set(:@definition, definition)
|
317
|
+
end
|
318
|
+
|
319
|
+
def remove_gem_from_definition(builder, name)
|
320
|
+
existing_dep = builder.dependencies.find { |dep| dep.name == name }
|
321
|
+
return false unless existing_dep
|
322
|
+
unless existing_dep.requirement.satisfied_by?(::Gem::Version.new(::Toys::Core::VERSION))
|
323
|
+
raise IncompatibleToysError,
|
324
|
+
"The bundle lists #{name} #{existing_dep.requirement} as a dependency, which is" \
|
325
|
+
" incompatible with the current version #{::Toys::Core::VERSION}."
|
326
|
+
end
|
327
|
+
builder.dependencies.delete(existing_dep)
|
328
|
+
true
|
329
|
+
end
|
330
|
+
|
331
|
+
def add_gem_to_definition(builder, name)
|
332
|
+
if ::ENV["TOYS_DEV"] == "true"
|
333
|
+
path = ::File.join(::File.dirname(::File.dirname(::Toys::CORE_LIB_PATH)), name)
|
334
|
+
end
|
335
|
+
command = "gem #{name.inspect}, #{::Toys::Core::VERSION.inspect}, path: #{path.inspect}\n"
|
336
|
+
builder.eval_gemfile("current #{name}", command)
|
337
|
+
end
|
338
|
+
|
262
339
|
def restore_toys_libs
|
340
|
+
$LOAD_PATH.delete(::Toys::CORE_LIB_PATH)
|
263
341
|
$LOAD_PATH.unshift(::Toys::CORE_LIB_PATH)
|
264
|
-
|
342
|
+
if ::Toys.const_defined?(:LIB_PATH)
|
343
|
+
$LOAD_PATH.delete(::Toys::LIB_PATH)
|
344
|
+
$LOAD_PATH.unshift(::Toys::LIB_PATH)
|
345
|
+
end
|
265
346
|
end
|
266
347
|
|
267
348
|
def permission_to_bundle?
|
@@ -271,7 +352,8 @@ module Toys
|
|
271
352
|
when :error
|
272
353
|
false
|
273
354
|
else
|
274
|
-
terminal.confirm("Your bundle
|
355
|
+
terminal.confirm("Your bundle requires additional gems. Install? ",
|
356
|
+
default: @default_confirm)
|
275
357
|
end
|
276
358
|
end
|
277
359
|
|
@@ -283,7 +365,12 @@ module Toys
|
|
283
365
|
" `cd #{gemfile_dir} && bundle install`"
|
284
366
|
end
|
285
367
|
require "bundler/cli"
|
286
|
-
|
368
|
+
begin
|
369
|
+
::Bundler::CLI.start(["install"])
|
370
|
+
rescue ::Bundler::GemNotFound, ::Bundler::InstallError
|
371
|
+
terminal.puts("Failed to install. Trying update...")
|
372
|
+
::Bundler::CLI.start(["update"])
|
373
|
+
end
|
287
374
|
end
|
288
375
|
end
|
289
376
|
end
|
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
|
|