toys-core 0.8.1 → 0.9.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.
@@ -49,8 +49,8 @@ module Toys
49
49
  @display_name = display_name
50
50
  @desc = desc
51
51
  @long_desc = long_desc || []
52
- accept(acceptor)
53
- complete(completion)
52
+ accept(acceptor, **{})
53
+ complete(completion, **{})
54
54
  end
55
55
 
56
56
  ##
@@ -65,9 +65,7 @@ module Toys
65
65
  # @return [self]
66
66
  #
67
67
  def accept(spec = nil, **options, &block)
68
- @acceptor_spec = spec
69
- @acceptor_options = options
70
- @acceptor_block = block
68
+ @acceptor = Acceptor.scalarize_spec(spec, options, block)
71
69
  self
72
70
  end
73
71
 
@@ -94,9 +92,7 @@ module Toys
94
92
  # @return [self]
95
93
  #
96
94
  def complete(spec = nil, **options, &block)
97
- @completion_spec = spec
98
- @completion_options = options
99
- @completion_block = block
95
+ @completion = Completion.scalarize_spec(spec, options, block)
100
96
  self
101
97
  end
102
98
 
@@ -178,31 +174,22 @@ module Toys
178
174
 
179
175
  ## @private
180
176
  def _add_required_to(tool, key)
181
- acceptor = tool.scalar_acceptor(@acceptor_spec, @acceptor_options, &@acceptor_block)
182
- completion = tool.scalar_completion(@completion_spec, @completion_options,
183
- &@completion_block)
184
177
  tool.add_required_arg(key,
185
- accept: acceptor, complete: completion,
178
+ accept: @acceptor, complete: @completion,
186
179
  display_name: @display_name, desc: @desc, long_desc: @long_desc)
187
180
  end
188
181
 
189
182
  ## @private
190
183
  def _add_optional_to(tool, key)
191
- acceptor = tool.scalar_acceptor(@acceptor_spec, @acceptor_options, &@acceptor_block)
192
- completion = tool.scalar_completion(@completion_spec, @completion_options,
193
- &@completion_block)
194
184
  tool.add_optional_arg(key,
195
- accept: acceptor, default: @default, complete: completion,
185
+ accept: @acceptor, default: @default, complete: @completion,
196
186
  display_name: @display_name, desc: @desc, long_desc: @long_desc)
197
187
  end
198
188
 
199
189
  ## @private
200
190
  def _set_remaining_on(tool, key)
201
- acceptor = tool.scalar_acceptor(@acceptor_spec, @acceptor_options, &@acceptor_block)
202
- completion = tool.scalar_completion(@completion_spec, @completion_options,
203
- &@completion_block)
204
191
  tool.set_remaining_args(key,
205
- accept: acceptor, default: @default, complete: completion,
192
+ accept: @acceptor, default: @default, complete: @completion,
206
193
  display_name: @display_name, desc: @desc, long_desc: @long_desc)
207
194
  end
208
195
  end
data/lib/toys/dsl/tool.rb CHANGED
@@ -57,7 +57,7 @@ module Toys
57
57
  module Tool
58
58
  ## @private
59
59
  def method_added(_meth)
60
- DSL::Tool.current_tool(self, true)&.check_definition_state
60
+ DSL::Tool.current_tool(self, true)&.check_definition_state(is_method: true)
61
61
  end
62
62
 
63
63
  ##
@@ -273,7 +273,7 @@ module Toys
273
273
  #
274
274
  def completion(name, spec = nil, **options, &block)
275
275
  cur_tool = DSL::Tool.current_tool(self, false)
276
- cur_tool&.add_completion(name, spec, options, &block)
276
+ cur_tool&.add_completion(name, spec, **options, &block)
277
277
  self
278
278
  end
279
279
 
@@ -297,6 +297,16 @@ module Toys
297
297
  # end
298
298
  # end
299
299
  #
300
+ # The following example defines a tool that runs one of its subtools.
301
+ #
302
+ # tool "test", runs: ["test", "unit"] do
303
+ # tool "unit" do
304
+ # def run
305
+ # puts "Running unit tests"
306
+ # end
307
+ # end
308
+ # end
309
+ #
300
310
  # @param words [String,Array<String>] The name of the subtool
301
311
  # @param if_defined [:combine,:reset,:ignore] What to do if a definition
302
312
  # already exists for this tool. Possible values are `:combine` (the
@@ -304,10 +314,14 @@ module Toys
304
314
  # existing definition, `:reset` indicating the earlier definition
305
315
  # should be reset and the new definition applied instead, or
306
316
  # `:ignore` indicating the new definition should be ignored.
317
+ # @param delegate_to [String,Array<String>] Optional. This tool should
318
+ # delegate to another tool, specified by the full path. This path may
319
+ # be given as an array of strings, or a single string possibly
320
+ # delimited by path separators.
307
321
  # @param block [Proc] Defines the subtool.
308
322
  # @return [self]
309
323
  #
310
- def tool(words, if_defined: :combine, &block)
324
+ def tool(words, if_defined: :combine, delegate_to: nil, &block)
311
325
  subtool_words = @__words
312
326
  next_remaining = @__remaining_words
313
327
  Array(words).each do |word|
@@ -326,17 +340,19 @@ module Toys
326
340
  end
327
341
  subtool_class = subtool.tool_class
328
342
  DSL::Tool.prepare(subtool_class, next_remaining, source_info) do
329
- subtool_class.class_eval(&block)
343
+ subtool_class.delegate_to(delegate_to) if delegate_to
344
+ subtool_class.class_eval(&block) if block
330
345
  end
331
346
  self
332
347
  end
333
348
  alias name tool
334
349
 
335
350
  ##
336
- # Create an alias in the current namespace.
351
+ # Create an alias, representing an "alternate name" for a tool.
337
352
  #
338
- # An alias is an alternate name for a tool. The referenced tool must be
339
- # in the same namespace.
353
+ # This is functionally equivalent to creating a subtool with the
354
+ # `delegate_to` option, except that `alias_tool` takes a _relative_ name
355
+ # for the delegate.
340
356
  #
341
357
  # ## Example
342
358
  #
@@ -351,11 +367,44 @@ module Toys
351
367
  # alias_tool "t", "test"
352
368
  #
353
369
  # @param word [String] The name of the alias
354
- # @param target [String] The target of the alias
370
+ # @param target [String,Array<String>] Relative path to the target of the
371
+ # alias. This path may be given as an array of strings, or a single
372
+ # string possibly delimited by path separators.
355
373
  # @return [self]
356
374
  #
357
375
  def alias_tool(word, target)
358
- @__loader.make_alias(@__words + [word.to_s], @__words + [target.to_s], @__priority)
376
+ tool(word, delegate_to: @__words + @__loader.split_path(target))
377
+ self
378
+ end
379
+
380
+ ##
381
+ # Causes the current tool to delegate to another tool. When run, it
382
+ # simply invokes the target tool with the same arguments.
383
+ #
384
+ # ## Example
385
+ #
386
+ # This example defines a tool that runs one of its subtools. Running the
387
+ # `test` tool will have the same effect (and recognize the same args) as
388
+ # the subtool `test unit`.
389
+ #
390
+ # tool "test" do
391
+ # tool "unit" do
392
+ # flag :faster
393
+ # def run
394
+ # puts "running tests..."
395
+ # end
396
+ # end
397
+ # delegate_to "test:unit"
398
+ # end
399
+ #
400
+ # @param target [String,Array<String>] The full path to the delegate
401
+ # tool. This path may be given as an array of strings, or a single
402
+ # string possibly delimited by path separators.
403
+ # @return [self]
404
+ #
405
+ def delegate_to(target)
406
+ cur_tool = DSL::Tool.current_tool(self, true)
407
+ cur_tool.delegate_to(@__loader.split_path(target))
359
408
  self
360
409
  end
361
410
 
@@ -403,7 +452,7 @@ module Toys
403
452
  # @param args [Object...] Template arguments
404
453
  # @return [self]
405
454
  #
406
- def expand(template_class, *args)
455
+ def expand(template_class, *args, **kwargs)
407
456
  cur_tool = DSL::Tool.current_tool(self, false)
408
457
  name = template_class.to_s
409
458
  if template_class.is_a?(::String)
@@ -414,7 +463,15 @@ module Toys
414
463
  if template_class.nil?
415
464
  raise ToolDefinitionError, "Template not found: #{name.inspect}"
416
465
  end
417
- template = template_class.new(*args)
466
+ # Due to a bug in Ruby < 2.7, passing an empty **kwargs splat to
467
+ # initialize will fail if there are no formal keyword args.
468
+ formals = template_class.instance_method(:initialize).parameters
469
+ template =
470
+ if kwargs.empty? && formals.all? { |(type, _name)| type != :key && type != :keyrest }
471
+ template_class.new(*args)
472
+ else
473
+ template_class.new(*args, **kwargs)
474
+ end
418
475
  yield template if block_given?
419
476
  class_exec(template, &template_class.expansion)
420
477
  self
@@ -1121,8 +1178,6 @@ module Toys
1121
1178
  # end
1122
1179
  # end
1123
1180
  #
1124
- # @return [self]
1125
- #
1126
1181
  # @overload static(key, value)
1127
1182
  # Set a single value by key.
1128
1183
  # @param key [String,Symbol] The key to use to retrieve the value from
@@ -1162,8 +1217,6 @@ module Toys
1162
1217
  # end
1163
1218
  # end
1164
1219
  #
1165
- # @return [self]
1166
- #
1167
1220
  # @overload set(key, value)
1168
1221
  # Set a single value by key.
1169
1222
  # @param key [String,Symbol] The key to use to retrieve the value from
@@ -1295,7 +1348,7 @@ module Toys
1295
1348
  def complete_tool_args(spec = nil, **options, &block)
1296
1349
  cur_tool = DSL::Tool.current_tool(self, true)
1297
1350
  return self if cur_tool.nil?
1298
- cur_tool.completion = cur_tool.scalar_completion(spec, options, &block)
1351
+ cur_tool.completion = Completion.scalarize_spec(spec, options, block)
1299
1352
  self
1300
1353
  end
1301
1354
 
@@ -1505,6 +1558,16 @@ module Toys
1505
1558
  DSL::Tool.current_tool(self, false)&.context_directory || source_info.context_directory
1506
1559
  end
1507
1560
 
1561
+ ##
1562
+ # Return the current tool object. This object can be queried to determine
1563
+ # such information as the name, but it should not be altered.
1564
+ #
1565
+ # @return [Toys::Tool]
1566
+ #
1567
+ def current_tool
1568
+ DSL::Tool.current_tool(self, false)
1569
+ end
1570
+
1508
1571
  ##
1509
1572
  # Set a custom context directory for this tool.
1510
1573
  #
@@ -1545,10 +1608,6 @@ module Toys
1545
1608
  else
1546
1609
  loader.get_tool(words, priority)
1547
1610
  end
1548
- if cur_tool.is_a?(Alias)
1549
- raise ToolDefinitionError,
1550
- "Cannot configure #{words.join(' ').inspect} because it is an alias"
1551
- end
1552
1611
  tool_class.instance_variable_set(memoize_var, cur_tool)
1553
1612
  end
1554
1613
  if cur_tool && activate
data/lib/toys/errors.rb CHANGED
@@ -87,7 +87,7 @@ module Toys
87
87
 
88
88
  class << self
89
89
  ## @private
90
- def capture_path(banner, path, opts = {})
90
+ def capture_path(banner, path, **opts)
91
91
  yield
92
92
  rescue ContextualError => e
93
93
  add_fields_if_missing(e, opts)
@@ -107,13 +107,13 @@ module Toys
107
107
  end
108
108
 
109
109
  ## @private
110
- def capture(banner, opts = {})
110
+ def capture(banner, **opts)
111
111
  yield
112
112
  rescue ContextualError => e
113
113
  add_fields_if_missing(e, opts)
114
114
  raise e
115
115
  rescue ::ScriptError, ::StandardError => e
116
- raise ContextualError.new(e, banner, opts)
116
+ raise ContextualError.new(e, banner, **opts)
117
117
  end
118
118
 
119
119
  private
data/lib/toys/flag.rb CHANGED
@@ -62,7 +62,7 @@ module Toys
62
62
  @long_desc = WrappableString.make_array(long_desc)
63
63
  @default = default
64
64
  @flag_completion = create_flag_completion(flag_completion)
65
- @value_completion = Completion.create(value_completion)
65
+ @value_completion = Completion.create(value_completion, **{})
66
66
  create_default_flag if @flag_syntax.empty?
67
67
  remove_used_flags(used_flags, report_collisions)
68
68
  canonicalize
@@ -373,14 +373,16 @@ module Toys
373
373
  end
374
374
 
375
375
  def create_flag_completion(spec)
376
- case spec
377
- when nil, :default
378
- DefaultCompletion.new(self)
379
- when ::Hash
380
- DefaultCompletion.new(self, spec)
381
- else
382
- Completion.create(spec)
383
- end
376
+ spec =
377
+ case spec
378
+ when nil, :default
379
+ {"": DefaultCompletion, flag: self}
380
+ when ::Hash
381
+ spec[:""].nil? ? spec.merge({"": DefaultCompletion, flag: self}) : spec
382
+ else
383
+ spec
384
+ end
385
+ Completion.create(spec, **{})
384
386
  end
385
387
 
386
388
  def create_default_flag
@@ -749,7 +751,7 @@ module Toys
749
751
  # @param include_long [Boolean] Whether to include long flags.
750
752
  # @param include_negative [Boolean] Whether to include `--no-*` forms.
751
753
  #
752
- def initialize(flag, include_short: true, include_long: true, include_negative: true)
754
+ def initialize(flag:, include_short: true, include_long: true, include_negative: true)
753
755
  @flag = flag
754
756
  @include_short = include_short
755
757
  @include_long = include_long
data/lib/toys/loader.rb CHANGED
@@ -134,8 +134,7 @@ module Toys
134
134
 
135
135
  ##
136
136
  # Given a list of command line arguments, find the appropriate tool to
137
- # handle the command, loading it from the configuration if necessary, and
138
- # following aliases.
137
+ # handle the command, loading it from the configuration if necessary.
139
138
  # This always returns a tool. If the specific tool path is not defined and
140
139
  # cannot be found in any configuration, it finds the nearest namespace that
141
140
  # *would* contain that tool, up to the root tool.
@@ -148,24 +147,35 @@ module Toys
148
147
  #
149
148
  def lookup(args)
150
149
  orig_prefix, args = find_orig_prefix(args)
151
- cur_prefix = orig_prefix
150
+ prefix = orig_prefix
152
151
  loop do
153
- load_for_prefix(cur_prefix)
154
- prefix = orig_prefix
155
- loop do
156
- tool = get_active_tool(prefix, [])
157
- if tool
158
- finish_definitions_in_tree(tool.full_name)
159
- return [tool, args.slice(prefix.length..-1)]
160
- end
161
- break if prefix.empty? || prefix.length <= cur_prefix.length
162
- prefix = prefix.slice(0..-2)
163
- end
164
- raise "Unexpected error" if cur_prefix.empty?
165
- cur_prefix = cur_prefix.slice(0..-2)
152
+ tool = lookup_specific(prefix)
153
+ return [tool, args.slice(prefix.length..-1)] if tool
154
+ prefix = prefix.slice(0..-2)
166
155
  end
167
156
  end
168
157
 
158
+ ##
159
+ # Given a tool name, looks up the specific tool, loading it from the
160
+ # configuration if necessary.
161
+ #
162
+ # If there is an active tool, returns it; otherwise, returns the highest
163
+ # priority tool that has been defined. If no tool has been defined with
164
+ # the given name, returns `nil`.
165
+ #
166
+ # @param words [Array<String>] The tool name
167
+ # @return [Toys::Tool] if the tool was found
168
+ # @return [nil] if no such tool exists
169
+ #
170
+ def lookup_specific(words)
171
+ words = split_path(words.first) if words.size == 1
172
+ load_for_prefix(words)
173
+ tool_data = get_tool_data(words)
174
+ tool = tool_data.active_definition || tool_data.top_definition
175
+ finish_definitions_in_tree(words) if tool
176
+ tool
177
+ end
178
+
169
179
  ##
170
180
  # Returns a list of subtools for the given path, loading from the
171
181
  # configuration if necessary.
@@ -175,8 +185,7 @@ module Toys
175
185
  # rather than just the immediate children (the default)
176
186
  # @param include_hidden [Boolean] If true, include hidden subtools,
177
187
  # e.g. names beginning with underscores.
178
- # @return [Array<Toys::Tool,Toys::Alias>] An array of subtools, which may
179
- # be tools or aliases.
188
+ # @return [Array<Toys::Tool>] An array of subtools.
180
189
  #
181
190
  def list_subtools(words, recursive: false, include_hidden: false)
182
191
  load_for_prefix(words)
@@ -214,6 +223,19 @@ module Toys
214
223
  false
215
224
  end
216
225
 
226
+ ##
227
+ # Splits the given path using the delimiters configured in this Loader.
228
+ # You may pass in either an array of strings, or a single string possibly
229
+ # delimited by path separators. Always returns an array of strings.
230
+ #
231
+ # @param str [String,Array<String>] The path to split.
232
+ # @return [Array<String>]
233
+ #
234
+ def split_path(str)
235
+ return str if str.is_a?(::Array)
236
+ @extra_delimiters ? str.split(@extra_delimiters) : [str]
237
+ end
238
+
217
239
  ##
218
240
  # Returns the active tool specified by the given words, with the given
219
241
  # priority, without doing any loading. If the given priority matches the
@@ -225,7 +247,6 @@ module Toys
225
247
  # @param priority [Integer] The priority of the request.
226
248
  #
227
249
  # @return [Toys::Tool] The tool found.
228
- # @return [Toys::Alias] The alias found.
229
250
  # @return [nil] if the given priority is insufficient.
230
251
  #
231
252
  # @private
@@ -239,53 +260,49 @@ module Toys
239
260
  end
240
261
 
241
262
  ##
242
- # Sets the given name as an alias to the given target.
243
- #
244
- # @param words [Array<String>] The alias name
245
- # @param target [Array<String>] The alias target name
246
- # @param priority [Integer] The priority of the request
263
+ # Returns true if the given tool name currently exists in the loader.
264
+ # Does not load the tool if not found.
247
265
  #
248
- # @return [Toys::Alias] The alias created
266
+ # @param words [Array<String>] The name of the tool.
267
+ # @return [Boolean]
249
268
  #
250
269
  # @private
251
270
  #
252
- def make_alias(words, target, priority)
253
- tool_data = get_tool_data(words)
254
- if tool_data.definitions.key?(priority)
255
- raise ToolDefinitionError,
256
- "Cannot make #{words.inspect} an alias because it is already defined"
257
- end
258
- alias_def = Alias.new(self, words, target, priority)
259
- tool_data.definitions[priority] = alias_def
260
- activate_tool(words, priority)
261
- alias_def
271
+ def tool_defined?(words)
272
+ @tool_data.key?(words)
262
273
  end
263
274
 
264
275
  ##
265
- # Returns true if the given tool name currently exists in the loader.
266
- # Does not load the tool if not found.
276
+ # Loads the subtree under the given prefix.
267
277
  #
268
- # @param words [Array<String>] The name of the tool.
269
- # @return [Boolean]
278
+ # @param prefix [Array<String>] The name prefix.
279
+ # @return [self]
270
280
  #
271
281
  # @private
272
282
  #
273
- def tool_defined?(words)
274
- @tool_data.key?(words)
283
+ def load_for_prefix(prefix)
284
+ cur_worklist = @worklist
285
+ @worklist = []
286
+ cur_worklist.each do |source, words, priority|
287
+ remaining_words = calc_remaining_words(prefix, words)
288
+ if source.source_proc
289
+ load_proc(source, words, remaining_words, priority)
290
+ elsif source.source_path
291
+ load_validated_path(source, words, remaining_words, priority)
292
+ end
293
+ end
294
+ self
275
295
  end
276
296
 
277
297
  ##
278
298
  # Get or create the tool definition for the given name and priority.
279
- # May return either a tool or alias definition.
299
+ #
300
+ # @return [Toys::Tool]
280
301
  #
281
302
  # @private
282
303
  #
283
304
  def get_tool(words, priority)
284
305
  parent = words.empty? ? nil : get_tool(words.slice(0..-2), priority)
285
- if parent.is_a?(Alias)
286
- raise ToolDefinitionError,
287
- "Cannot create children of #{parent.display_name.inspect} because it is an alias"
288
- end
289
306
  tool_data = get_tool_data(words)
290
307
  if tool_data.top_priority.nil? || tool_data.top_priority < priority
291
308
  tool_data.top_priority = priority
@@ -377,54 +394,44 @@ module Toys
377
394
  @tool_data[words] ||= ToolData.new({}, nil, nil)
378
395
  end
379
396
 
380
- ##
381
- # Returns the current effective tool given a name. Resolves any aliases.
382
- #
383
- # If there is an active tool, returns it; otherwise, returns the highest
384
- # priority tool that has been defined. If no tool has been defined with
385
- # the given name, returns `nil`.
386
- #
387
- def get_active_tool(words, looked_up = [])
388
- tool_data = get_tool_data(words)
389
- result = tool_data.active_definition
390
- case result
391
- when Alias
392
- resolve_alias(result, looked_up)
393
- when Tool
394
- result
395
- else
396
- tool_data.top_definition
397
+ def resolve_middleware(input)
398
+ input = Array(input).dup
399
+ middleware = input.shift
400
+ if middleware.is_a?(::String) || middleware.is_a?(::Symbol)
401
+ middleware = @middleware_lookup.lookup(middleware)
402
+ if middleware.nil?
403
+ raise ::ArgumentError, "Unknown middleware name #{input.first.inspect}"
404
+ end
397
405
  end
398
- end
399
-
400
- ##
401
- # Resolves the given alias
402
- #
403
- def resolve_alias(alias_tool, looked_up = [])
404
- words = alias_tool.target_name
405
- if looked_up.include?(words)
406
- raise ToolDefinitionError, "Circular alias references: #{looked_up.inspect}"
406
+ if middleware.is_a?(::Class)
407
+ middleware = build_middleware(middleware, input)
407
408
  end
408
- looked_up << words
409
- get_active_tool(words, looked_up)
409
+ unless input.empty?
410
+ raise ::ArgumentError, "Unrecognized middleware arguments: #{input.inspect}"
411
+ end
412
+ middleware
410
413
  end
411
414
 
412
- def resolve_middleware(input)
413
- input = Array(input)
414
- cls = input.first
415
- args = input[1..-1]
416
- if cls.is_a?(::String) || cls.is_a?(::Symbol)
417
- cls = @middleware_lookup.lookup(cls)
418
- if cls.nil?
419
- raise ::ArgumentError, "Unrecognized middleware name #{input.first.inspect}"
420
- end
415
+ def build_middleware(middleware_class, input)
416
+ args = input.first
417
+ if args.is_a?(::Array)
418
+ input.shift
419
+ else
420
+ args = []
421
+ end
422
+ kwargs = input.first
423
+ if kwargs.is_a?(::Hash)
424
+ input.shift
425
+ else
426
+ kwargs = {}
421
427
  end
422
- if cls.is_a?(::Class)
423
- cls.new(*args)
424
- elsif !args.empty?
425
- raise ::ArgumentError, "Unrecognized middleware object of class #{cls.class}"
428
+ # Due to a bug in Ruby < 2.7, passing an empty **kwargs splat to
429
+ # initialize will fail if there are no formal keyword args.
430
+ formals = middleware_class.instance_method(:initialize).parameters
431
+ if kwargs.empty? && formals.all? { |(type, _name)| type != :key && type != :keyrest }
432
+ middleware_class.new(*args)
426
433
  else
427
- cls
434
+ middleware_class.new(*args, **kwargs)
428
435
  end
429
436
  end
430
437
 
@@ -442,19 +449,6 @@ module Toys
442
449
  end
443
450
  end
444
451
 
445
- def load_for_prefix(prefix)
446
- cur_worklist = @worklist
447
- @worklist = []
448
- cur_worklist.each do |source, words, priority|
449
- remaining_words = calc_remaining_words(prefix, words)
450
- if source.source_proc
451
- load_proc(source, words, remaining_words, priority)
452
- elsif source.source_path
453
- load_validated_path(source, words, remaining_words, priority)
454
- end
455
- end
456
- end
457
-
458
452
  def load_proc(source, words, remaining_words, priority)
459
453
  if remaining_words
460
454
  tool_class = get_tool(words, priority).tool_class
@@ -548,11 +542,6 @@ module Toys
548
542
 
549
543
  def tool_hidden?(tool, next_tool)
550
544
  return true if tool.full_name.any? { |n| n.start_with?("_") }
551
- if tool.is_a?(Alias)
552
- original_tool = resolve_alias(tool)
553
- return true if original_tool.nil?
554
- return tool_hidden?(original_tool, nil)
555
- end
556
545
  !tool.runnable? && next_tool && next_tool.full_name.slice(0..-2) == tool.full_name
557
546
  end
558
547