toys-core 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,7 +37,7 @@ module Toys
37
37
  @type = type
38
38
  @acceptor = Acceptor.create(acceptor)
39
39
  @default = default
40
- @completion = Completion.create(completion)
40
+ @completion = Completion.create(completion, **{})
41
41
  @desc = WrappableString.make(desc)
42
42
  @long_desc = WrappableString.make_array(long_desc)
43
43
  @display_name = display_name || key.to_s.tr("-", "_").gsub(/\W/, "").upcase
@@ -40,6 +40,12 @@ module Toys
40
40
  #
41
41
  DEFAULT_TOOL_DESC = "(No tool description available)"
42
42
 
43
+ ##
44
+ # The default description for delegating tools.
45
+ # @return [String]
46
+ #
47
+ DEFAULT_DELEGATE_DESC = '(Delegates to "%<target>s")'
48
+
43
49
  ##
44
50
  # The default description for namespaces.
45
51
  # @return [String]
@@ -58,7 +64,7 @@ module Toys
58
64
  #
59
65
  DEFAULT_ROOT_LONG_DESC = [
60
66
  "This command line tool was built using the toys-core gem. See" \
61
- " https://www.rubydoc.info/gems/toys-core for more info.",
67
+ " https://dazuma.github.io/toys/gems/toys-core for more info.",
62
68
  "To replace this message, set the description and long description" \
63
69
  " of the root tool, or configure the SetDefaultDescriptions" \
64
70
  " middleware.",
@@ -70,33 +76,39 @@ module Toys
70
76
  # @param default_tool_desc [String,nil] The default short description for
71
77
  # runnable tools, or `nil` not to set one. Defaults to
72
78
  # {DEFAULT_TOOL_DESC}.
73
- # @param default_tool_long_desc [String,nil] The default long description
74
- # for runnable tools, or `nil` not to set one. Defaults to `nil`.
79
+ # @param default_tool_long_desc [Array<String>,nil] The default long
80
+ # description for runnable tools, or `nil` not to set one. Defaults
81
+ # to `nil`.
75
82
  # @param default_namespace_desc [String,nil] The default short
76
83
  # description for non-runnable tools, or `nil` not to set one.
77
84
  # Defaults to {DEFAULT_TOOL_DESC}.
78
- # @param default_namespace_long_desc [String,nil] The default long
85
+ # @param default_namespace_long_desc [Array<String>,nil] The default long
79
86
  # description for non-runnable tools, or `nil` not to set one.
80
87
  # Defaults to `nil`.
81
88
  # @param default_root_desc [String,nil] The default short description for
82
89
  # the root tool, or `nil` not to set one. Defaults to
83
90
  # {DEFAULT_ROOT_DESC}.
84
- # @param default_root_long_desc [String,nil] The default long description
85
- # for the root tool, or `nil` not to set one. Defaults to
91
+ # @param default_root_long_desc [Array<String>,nil] The default long
92
+ # description for the root tool, or `nil` not to set one. Defaults to
86
93
  # {DEFAULT_ROOT_LONG_DESC}.
94
+ # @param default_delegate_desc [String,nil] The default short description
95
+ # for delegate tools, or `nil` not to set one. May include an sprintf
96
+ # field for the `target` name. Defaults to {DEFAULT_DELEGATE_DESC}.
87
97
  #
88
98
  def initialize(default_tool_desc: DEFAULT_TOOL_DESC,
89
99
  default_tool_long_desc: nil,
90
100
  default_namespace_desc: DEFAULT_NAMESPACE_DESC,
91
101
  default_namespace_long_desc: nil,
92
102
  default_root_desc: DEFAULT_ROOT_DESC,
93
- default_root_long_desc: DEFAULT_ROOT_LONG_DESC)
103
+ default_root_long_desc: DEFAULT_ROOT_LONG_DESC,
104
+ default_delegate_desc: DEFAULT_DELEGATE_DESC)
94
105
  @default_tool_desc = default_tool_desc
95
106
  @default_tool_long_desc = default_tool_long_desc
96
107
  @default_namespace_desc = default_namespace_desc
97
108
  @default_namespace_long_desc = default_namespace_long_desc
98
109
  @default_root_desc = default_root_desc
99
110
  @default_root_long_desc = default_root_long_desc
111
+ @default_delegate_desc = default_delegate_desc
100
112
  end
101
113
 
102
114
  ##
@@ -137,6 +149,9 @@ module Toys
137
149
  def generate_tool_desc(tool, data)
138
150
  if tool.root?
139
151
  @default_root_desc
152
+ elsif tool.delegate_target
153
+ params = {target: tool.delegate_target.join(" ")}
154
+ format(@default_delegate_desc, params)
140
155
  elsif !tool.runnable? && data[:loader].has_subtools?(tool.full_name)
141
156
  @default_namespace_desc
142
157
  else
@@ -216,7 +216,7 @@ module Toys
216
216
  @show_source_path = show_source_path
217
217
  @stream = stream
218
218
  @styled_output = styled_output
219
- @use_less = use_less
219
+ @use_less = use_less && !Compat::IS_JRUBY
220
220
  end
221
221
 
222
222
  ##
@@ -294,7 +294,7 @@ module Toys
294
294
  include_hidden: context[SHOW_ALL_SUBTOOLS_KEY], show_source_path: @show_source_path,
295
295
  wrap_width: terminal.width
296
296
  )
297
- if RUBY_PLATFORM != "java" && less_path
297
+ if less_path
298
298
  require "toys/utils/exec"
299
299
  Utils::Exec.new.exec([less_path, "-R"], in: [:string, str])
300
300
  else
@@ -333,7 +333,7 @@ module Toys
333
333
  dict = loader.list_subtools(tool_name).map(&:simple_name)
334
334
  suggestions = Compat.suggestions(next_word, dict)
335
335
  tool_name = (tool_name + [next_word]).join(" ")
336
- message = "Tool not found: \"#{tool_name}\"."
336
+ message = "Tool not found: \"#{tool_name}\""
337
337
  unless suggestions.empty?
338
338
  suggestions_str = suggestions.join("\n ")
339
339
  message = "#{message}\nDid you mean... #{suggestions_str}"
@@ -82,6 +82,10 @@ module Toys
82
82
  #
83
83
  # include :exec, exit_on_nonzero_status: true
84
84
  #
85
+ # **:e** can be used as a shortcut for **:exit_on_nonzero_status**
86
+ #
87
+ # include :exec, e: true
88
+ #
85
89
  module Exec
86
90
  include Mixin
87
91
 
@@ -343,25 +347,38 @@ module Toys
343
347
 
344
348
  ## @private
345
349
  def self._setup_exec_opts(opts, context)
346
- if opts.key?(:exit_on_nonzero_status)
347
- result_callback =
348
- if opts[:exit_on_nonzero_status]
349
- proc { |r| context.exit(r.exit_code) if r.error? }
350
- end
351
- opts = opts.merge(result_callback: result_callback)
352
- opts.delete(:exit_on_nonzero_status)
353
- elsif opts.key?(:result_callback)
354
- orig_callback = opts[:result_callback]
355
- result_callback =
356
- if orig_callback.is_a?(::Symbol)
357
- context.method(orig_callback)
358
- elsif orig_callback.respond_to?(:call)
359
- proc { |r| orig_callback.call(r, context) }
360
- end
361
- opts = opts.merge(result_callback: result_callback)
350
+ if opts.key?(:result_callback)
351
+ opts = _setup_result_callback_option(opts, context)
352
+ end
353
+ if opts.key?(:exit_on_nonzero_status) || opts.key?(:e)
354
+ opts = _setup_e_option(opts, context)
362
355
  end
363
356
  opts
364
357
  end
358
+
359
+ ## @private
360
+ def self._setup_e_option(opts, context)
361
+ result_callback =
362
+ if opts[:exit_on_nonzero_status] || opts[:e]
363
+ proc { |r| context.exit(r.exit_code) if r.error? }
364
+ end
365
+ opts = opts.merge(result_callback: result_callback)
366
+ opts.delete(:exit_on_nonzero_status)
367
+ opts.delete(:e)
368
+ opts
369
+ end
370
+
371
+ ## @private
372
+ def self._setup_result_callback_option(opts, context)
373
+ orig_callback = opts[:result_callback]
374
+ result_callback =
375
+ if orig_callback.is_a?(::Symbol)
376
+ context.method(orig_callback)
377
+ elsif orig_callback.respond_to?(:call)
378
+ proc { |r| orig_callback.call(r, context) }
379
+ end
380
+ opts.merge(result_callback: result_callback)
381
+ end
365
382
  end
366
383
  end
367
384
  end
@@ -54,7 +54,7 @@ module Toys
54
54
  def self.gems
55
55
  require "toys/utils/gems"
56
56
  # rubocop:disable Naming/MemoizedInstanceVariableName
57
- @__gems ||= Utils::Gems.new(@__gems_opts)
57
+ @__gems ||= Utils::Gems.new(**@__gems_opts)
58
58
  # rubocop:enable Naming/MemoizedInstanceVariableName
59
59
  end
60
60
 
@@ -58,7 +58,7 @@ module Toys
58
58
 
59
59
  on_initialize do |opts = {}|
60
60
  require "toys/utils/terminal"
61
- self[KEY] = Utils::Terminal.new(opts)
61
+ self[KEY] = Utils::Terminal.new(**opts)
62
62
  end
63
63
 
64
64
  ##
data/lib/toys/tool.rb CHANGED
@@ -87,6 +87,7 @@ module Toys
87
87
 
88
88
  @interrupt_handler = nil
89
89
  @usage_error_handler = nil
90
+ @delegate_target = nil
90
91
 
91
92
  @completion = DefaultCompletion.new
92
93
  end
@@ -259,6 +260,14 @@ module Toys
259
260
  #
260
261
  attr_reader :usage_error_handler
261
262
 
263
+ ##
264
+ # The full name of the delegate target, if any.
265
+ #
266
+ # @return [Array<String>] if this tool delegates
267
+ # @return [nil] if this tool does not delegate
268
+ #
269
+ attr_reader :delegate_target
270
+
262
271
  ##
263
272
  # The local name of this tool, i.e. the last element of the full name.
264
273
  #
@@ -587,7 +596,7 @@ module Toys
587
596
  "A completion named #{name.inspect} has already been defined in tool" \
588
597
  " #{display_name.inspect}."
589
598
  end
590
- @completions[name] = Toys::Completion.create(completion, options, &block)
599
+ @completions[name] = Toys::Completion.create(completion, **options, &block)
591
600
  self
592
601
  end
593
602
 
@@ -916,7 +925,7 @@ module Toys
916
925
  # @param proc [Proc] The runnable block
917
926
  #
918
927
  def run_handler=(proc)
919
- check_definition_state
928
+ check_definition_state(is_method: true)
920
929
  @tool_class.to_run(&proc)
921
930
  end
922
931
 
@@ -926,7 +935,7 @@ module Toys
926
935
  # @param handler [Proc,Symbol] The interrupt handler
927
936
  #
928
937
  def interrupt_handler=(handler)
929
- check_definition_state
938
+ check_definition_state(is_method: true)
930
939
  if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
931
940
  raise ToolDefinitionError, "Interrupt handler must be a proc or symbol"
932
941
  end
@@ -939,7 +948,7 @@ module Toys
939
948
  # @param handler [Proc,Symbol] The usage error handler
940
949
  #
941
950
  def usage_error_handler=(handler)
942
- check_definition_state
951
+ check_definition_state(is_method: true)
943
952
  if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
944
953
  raise ToolDefinitionError, "Usage error handler must be a proc or symbol"
945
954
  end
@@ -979,15 +988,17 @@ module Toys
979
988
  # @param spec [Object]
980
989
  #
981
990
  def completion=(spec)
982
- @completion =
991
+ spec = resolve_completion_name(spec)
992
+ spec =
983
993
  case spec
984
994
  when nil, :default
985
- DefaultCompletion.new
995
+ DefaultCompletion
986
996
  when ::Hash
987
- DefaultCompletion.new(spec)
997
+ spec[:""].nil? ? spec.merge({"": DefaultCompletion}) : spec
988
998
  else
989
- Completion.create(spec)
999
+ spec
990
1000
  end
1001
+ @completion = Completion.create(spec, **{})
991
1002
  end
992
1003
 
993
1004
  ##
@@ -1005,6 +1016,35 @@ module Toys
1005
1016
  lookup_custom_context_directory || source_info&.context_directory
1006
1017
  end
1007
1018
 
1019
+ ##
1020
+ # Causes this tool to delegate to another tool.
1021
+ #
1022
+ # @param target [Array<String>] The full path to the delegate tool.
1023
+ # @return [self]
1024
+ #
1025
+ def delegate_to(target)
1026
+ if @delegate_target
1027
+ raise ToolDefinitionError,
1028
+ "Cannot delegate tool #{display_name.inspect} because" \
1029
+ " it already delegates to \"#{@delegate_target.join(' ')}\"."
1030
+ end
1031
+ if includes_arguments?
1032
+ raise ToolDefinitionError,
1033
+ "Cannot delegate tool #{display_name.inspect} because" \
1034
+ " arguments have already been defined."
1035
+ end
1036
+ if runnable?
1037
+ raise ToolDefinitionError,
1038
+ "Cannot delegate tool #{display_name.inspect} because" \
1039
+ " the run method has already been defined."
1040
+ end
1041
+ disable_argument_parsing
1042
+ self.run_handler = make_delegation_run_handler(target)
1043
+ self.completion = DefaultCompletion.new(delegation_target: target)
1044
+ @delegate_target = target
1045
+ self
1046
+ end
1047
+
1008
1048
  ##
1009
1049
  # Lookup the custom context directory in this tool and its ancestors.
1010
1050
  # @private
@@ -1013,20 +1053,6 @@ module Toys
1013
1053
  custom_context_directory || @parent&.lookup_custom_context_directory
1014
1054
  end
1015
1055
 
1016
- ## @private
1017
- def scalar_acceptor(spec = nil, type_desc: nil, &block)
1018
- Acceptor.create(resolve_acceptor_name(spec), type_desc: type_desc, &block)
1019
- end
1020
-
1021
- ## @private
1022
- def scalar_completion(spec = nil, **options, &block)
1023
- if spec.nil? && block.nil? || spec == :default
1024
- options
1025
- else
1026
- Completion.create(resolve_completion_name(spec), options, &block)
1027
- end
1028
- end
1029
-
1030
1056
  ##
1031
1057
  # Mark this tool as having at least one module included.
1032
1058
  # @private
@@ -1074,7 +1100,7 @@ module Toys
1074
1100
  # or from the DSL only.
1075
1101
  # @private
1076
1102
  #
1077
- def check_definition_state(is_arg: false)
1103
+ def check_definition_state(is_arg: false, is_method: false)
1078
1104
  if @definition_finished
1079
1105
  raise ToolDefinitionError,
1080
1106
  "Defintion of tool #{display_name.inspect} is already finished"
@@ -1083,6 +1109,10 @@ module Toys
1083
1109
  raise ToolDefinitionError,
1084
1110
  "Tool #{display_name.inspect} has disabled argument parsing"
1085
1111
  end
1112
+ if (is_arg || is_method) && delegate_target
1113
+ raise ToolDefinitionError,
1114
+ "Tool #{display_name.inspect} is already delegating to another tool"
1115
+ end
1086
1116
  self
1087
1117
  end
1088
1118
 
@@ -1099,14 +1129,18 @@ module Toys
1099
1129
  # @param complete_args [Boolean] Whether to complete positional args
1100
1130
  # @param complete_flags [Boolean] Whether to complete flag names
1101
1131
  # @param complete_flag_values [Boolean] Whether to complete flag values
1132
+ # @param delegation_target [Array<String>,nil] Delegation target, or
1133
+ # `nil` if none.
1102
1134
  #
1103
1135
  def initialize(complete_subtools: true, include_hidden_subtools: false,
1104
- complete_args: true, complete_flags: true, complete_flag_values: true)
1136
+ complete_args: true, complete_flags: true, complete_flag_values: true,
1137
+ delegation_target: nil)
1105
1138
  @complete_subtools = complete_subtools
1106
1139
  @include_hidden_subtools = include_hidden_subtools
1107
1140
  @complete_flags = complete_flags
1108
1141
  @complete_args = complete_args
1109
1142
  @complete_flag_values = complete_flag_values
1143
+ @delegation_target = delegation_target
1110
1144
  end
1111
1145
 
1112
1146
  ##
@@ -1149,6 +1183,13 @@ module Toys
1149
1183
  @complete_flag_values
1150
1184
  end
1151
1185
 
1186
+ ##
1187
+ # Delegation target, or nil for none.
1188
+ # @return [Array<String>] if there is a delegation target
1189
+ # @return [nil] if there is no delegation target
1190
+ #
1191
+ attr_accessor :delegation_target
1192
+
1152
1193
  ##
1153
1194
  # Returns candidates for the current completion.
1154
1195
  #
@@ -1162,6 +1203,13 @@ module Toys
1162
1203
  candidates = subtool_or_arg_candidates(context)
1163
1204
  candidates += plain_flag_candidates(context)
1164
1205
  candidates += flag_value_candidates(context)
1206
+ if delegation_target
1207
+ delegate_tool = context.cli.loader.lookup_specific(delegation_target)
1208
+ if delegate_tool
1209
+ context = context.with(previous_words: delegation_target)
1210
+ candidates += delegate_tool.completion.call(context)
1211
+ end
1212
+ end
1165
1213
  candidates
1166
1214
  end
1167
1215
 
@@ -1260,6 +1308,27 @@ module Toys
1260
1308
  proc { middleware.config(self, loader, &next_config) }
1261
1309
  end
1262
1310
 
1311
+ def make_delegation_run_handler(target)
1312
+ lambda do
1313
+ path = [target.join(" ").inspect]
1314
+ walk_context = self
1315
+ until walk_context.nil?
1316
+ name = walk_context[::Toys::Context::Key::TOOL_NAME]
1317
+ path << name.join(" ").inspect
1318
+ if name == target
1319
+ raise "Delegation loop: #{path.join(' <- ')}"
1320
+ end
1321
+ walk_context = walk_context[::Toys::Context::Key::DELEGATED_FROM]
1322
+ end
1323
+ cli = self[::Toys::Context::Key::CLI]
1324
+ cli.loader.load_for_prefix(target)
1325
+ unless cli.loader.tool_defined?(target)
1326
+ raise "Delegate target not found: \"#{target.join(' ')}\""
1327
+ end
1328
+ exit(cli.run(target + self[::Toys::Context::Key::ARGS], delegated_from: self))
1329
+ end
1330
+ end
1331
+
1263
1332
  def resolve_acceptor_name(name)
1264
1333
  return name unless name.is_a?(::String)
1265
1334
  accept = lookup_acceptor(name)
@@ -53,8 +53,14 @@ module Toys
53
53
  # @return [Toys::Utils::HelpText]
54
54
  #
55
55
  def self.from_context(context)
56
+ orig_context = context
57
+ while (from = context[Context::Key::DELEGATED_FROM])
58
+ context = from
59
+ end
60
+ delegate_target = orig_context == context ? nil : orig_context[Context::Key::TOOL_NAME]
56
61
  cli = context[Context::Key::CLI]
57
- new(context[Context::Key::TOOL], cli.loader, cli.executable_name)
62
+ new(context[Context::Key::TOOL], cli.loader, cli.executable_name,
63
+ delegate_target: delegate_target)
58
64
  end
59
65
 
60
66
  ##
@@ -64,13 +70,16 @@ module Toys
64
70
  # @param loader [Toys::Loader] A loader that can provide subcommands.
65
71
  # @param executable_name [String] The name of the executable.
66
72
  # e.g. `"toys"`.
73
+ # @param delegate_target [Array<String>,nil] The full name of a tool this
74
+ # tool will delegate to. Default is `nil` for no delegation.
67
75
  #
68
76
  # @return [Toys::Utils::HelpText]
69
77
  #
70
- def initialize(tool, loader, executable_name)
78
+ def initialize(tool, loader, executable_name, delegate_target: nil)
71
79
  @tool = tool
72
80
  @loader = loader
73
81
  @executable_name = executable_name
82
+ @delegate_target = delegate_target
74
83
  end
75
84
 
76
85
  ##
@@ -99,8 +108,10 @@ module Toys
99
108
  left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
100
109
  indent ||= DEFAULT_INDENT
101
110
  subtools = find_subtools(recursive, nil, include_hidden)
102
- assembler = UsageStringAssembler.new(@tool, @executable_name, subtools,
103
- indent, left_column_width, wrap_width)
111
+ assembler = UsageStringAssembler.new(
112
+ @tool, @executable_name, @delegate_target, subtools,
113
+ indent, left_column_width, wrap_width
114
+ )
104
115
  assembler.result
105
116
  end
106
117
 
@@ -130,8 +141,10 @@ module Toys
130
141
  indent ||= DEFAULT_INDENT
131
142
  indent2 ||= DEFAULT_INDENT
132
143
  subtools = find_subtools(recursive, search, include_hidden)
133
- assembler = HelpStringAssembler.new(@tool, @executable_name, subtools, search,
134
- show_source_path, indent, indent2, wrap_width, styled)
144
+ assembler = HelpStringAssembler.new(
145
+ @tool, @executable_name, @delegate_target, subtools, search, show_source_path,
146
+ indent, indent2, wrap_width, styled
147
+ )
135
148
  assembler.result
136
149
  end
137
150
 
@@ -174,10 +187,11 @@ module Toys
174
187
 
175
188
  ## @private
176
189
  class UsageStringAssembler
177
- def initialize(tool, executable_name, subtools,
190
+ def initialize(tool, executable_name, delegate_target, subtools,
178
191
  indent, left_column_width, wrap_width)
179
192
  @tool = tool
180
193
  @executable_name = executable_name
194
+ @delegate_target = delegate_target
181
195
  @subtools = subtools
182
196
  @indent = indent
183
197
  @left_column_width = left_column_width
@@ -201,9 +215,8 @@ module Toys
201
215
 
202
216
  def add_synopsis_section
203
217
  synopses = []
204
- synopses << namespace_synopsis if !@subtools.empty? && !@tool.runnable?
205
- synopses << tool_synopsis
206
- synopses << namespace_synopsis if !@subtools.empty? && @tool.runnable?
218
+ synopses << namespace_synopsis unless @subtools.empty?
219
+ synopses << (@delegate_target ? delegate_synopsis : tool_synopsis)
207
220
  first = true
208
221
  synopses.each do |synopsis|
209
222
  @lines << (first ? "Usage: #{synopsis}" : " #{synopsis}")
@@ -220,8 +233,13 @@ module Toys
220
233
  synopsis.join(" ")
221
234
  end
222
235
 
236
+ def delegate_synopsis
237
+ target = @delegate_target.join(" ")
238
+ "#{@executable_name} #{@tool.display_name} [ARGUMENTS FOR \"#{target}\"...]"
239
+ end
240
+
223
241
  def namespace_synopsis
224
- ([@executable_name] + @tool.full_name + ["TOOL", "[ARGUMENTS...]"]).join(" ")
242
+ "#{@executable_name} #{@tool.display_name} TOOL [ARGUMENTS...]"
225
243
  end
226
244
 
227
245
  def add_flag_group_sections
@@ -264,13 +282,7 @@ module Toys
264
282
  @lines << "Tools:"
265
283
  @subtools.each do |subtool|
266
284
  tool_name = subtool.full_name.slice(name_len..-1).join(" ")
267
- desc =
268
- if subtool.is_a?(Alias)
269
- ["(Alias of #{subtool.display_target})"]
270
- else
271
- wrap_desc(subtool.desc)
272
- end
273
- add_right_column_desc(tool_name, desc)
285
+ add_right_column_desc(tool_name, wrap_desc(subtool.desc))
274
286
  end
275
287
  end
276
288
 
@@ -310,11 +322,12 @@ module Toys
310
322
 
311
323
  ## @private
312
324
  class HelpStringAssembler
313
- def initialize(tool, executable_name, subtools, search_term, show_source_path,
314
- indent, indent2, wrap_width, styled)
325
+ def initialize(tool, executable_name, delegate_target, subtools, search_term,
326
+ show_source_path, indent, indent2, wrap_width, styled)
315
327
  require "toys/utils/terminal"
316
328
  @tool = tool
317
329
  @executable_name = executable_name
330
+ @delegate_target = delegate_target
318
331
  @subtools = subtools
319
332
  @search_term = search_term
320
333
  @show_source_path = show_source_path
@@ -363,9 +376,8 @@ module Toys
363
376
  def add_synopsis_section
364
377
  @lines << ""
365
378
  @lines << bold("SYNOPSIS")
366
- add_synopsis_clause(namespace_synopsis) if !@subtools.empty? && !@tool.runnable?
367
- add_synopsis_clause(tool_synopsis)
368
- add_synopsis_clause(namespace_synopsis) if !@subtools.empty? && @tool.runnable?
379
+ add_synopsis_clause(namespace_synopsis) unless @subtools.empty?
380
+ add_synopsis_clause(@delegate_target ? delegate_synopsis : tool_synopsis)
369
381
  end
370
382
 
371
383
  def add_synopsis_clause(synopsis)
@@ -454,6 +466,13 @@ module Toys
454
466
  wrap_indent_indent2(WrappableString.new(synopsis))
455
467
  end
456
468
 
469
+ def delegate_synopsis
470
+ target = @delegate_target.join(" ")
471
+ args_clause = underline("ARGUMENTS FOR \"#{target}\"")
472
+ synopsis = [full_executable_name, "[#{args_clause}...]"]
473
+ wrap_indent_indent2(WrappableString.new(synopsis))
474
+ end
475
+
457
476
  def full_executable_name
458
477
  bold(([@executable_name] + @tool.full_name).join(" "))
459
478
  end
@@ -466,7 +485,13 @@ module Toys
466
485
  end
467
486
 
468
487
  def add_description_section
469
- desc = wrap_indent(@tool.long_desc)
488
+ desc = @tool.long_desc
489
+ if @delegate_target
490
+ delegate_clause =
491
+ "Passes all arguments to \"#{@delegate_target.join(' ')}\" if invoked directly."
492
+ desc = desc.empty? ? [delegate_clause] : desc + ["", delegate_clause]
493
+ end
494
+ desc = wrap_indent(desc)
470
495
  return if desc.empty?
471
496
  @lines << ""
472
497
  @lines << bold("DESCRIPTION")
@@ -532,13 +557,7 @@ module Toys
532
557
  name_len = @tool.full_name.length
533
558
  @subtools.each do |subtool|
534
559
  tool_name = subtool.full_name.slice(name_len..-1).join(" ")
535
- desc =
536
- if subtool.is_a?(Alias)
537
- "(Alias of #{subtool.display_target})"
538
- else
539
- subtool.desc
540
- end
541
- add_prefix_with_desc(bold(tool_name), desc)
560
+ add_prefix_with_desc(bold(tool_name), subtool.desc)
542
561
  end
543
562
  end
544
563
 
@@ -642,13 +661,7 @@ module Toys
642
661
  name_len = @tool.full_name.length
643
662
  @subtools.each do |subtool|
644
663
  tool_name = subtool.full_name.slice(name_len..-1).join(" ")
645
- desc =
646
- if subtool.is_a?(Alias)
647
- "(Alias of #{subtool.display_target})"
648
- else
649
- subtool.desc
650
- end
651
- add_prefix_with_desc(bold(tool_name), desc)
664
+ add_prefix_with_desc(bold(tool_name), subtool.desc)
652
665
  end
653
666
  end
654
667