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.
@@ -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