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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +13 -10
- data/docs/guide.md +1 -1
- data/lib/toys-core.rb +1 -2
- data/lib/toys/acceptor.rb +35 -10
- data/lib/toys/arg_parser.rb +6 -6
- data/lib/toys/cli.rb +10 -6
- data/lib/toys/compat.rb +8 -0
- data/lib/toys/completion.rb +24 -6
- data/lib/toys/context.rb +8 -0
- data/lib/toys/{core_version.rb → core.rb} +11 -3
- data/lib/toys/dsl/flag.rb +7 -20
- data/lib/toys/dsl/positional_arg.rb +7 -20
- data/lib/toys/dsl/tool.rb +79 -20
- data/lib/toys/errors.rb +3 -3
- data/lib/toys/flag.rb +12 -10
- data/lib/toys/loader.rb +95 -106
- data/lib/toys/positional_arg.rb +1 -1
- data/lib/toys/standard_middleware/set_default_descriptions.rb +22 -7
- data/lib/toys/standard_middleware/show_help.rb +3 -3
- data/lib/toys/standard_mixins/exec.rb +33 -16
- data/lib/toys/standard_mixins/gems.rb +1 -1
- data/lib/toys/standard_mixins/terminal.rb +1 -1
- data/lib/toys/tool.rb +93 -24
- data/lib/toys/utils/help_text.rb +51 -38
- data/lib/toys/utils/terminal.rb +2 -2
- metadata +8 -9
- data/lib/toys/alias.rb +0 -106
data/lib/toys/positional_arg.rb
CHANGED
@@ -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://
|
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
|
74
|
-
# for runnable tools, or `nil` not to set one. Defaults
|
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
|
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
|
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
|
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?(:
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
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(
|
57
|
+
@__gems ||= Utils::Gems.new(**@__gems_opts)
|
58
58
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
59
59
|
end
|
60
60
|
|
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
|
-
|
991
|
+
spec = resolve_completion_name(spec)
|
992
|
+
spec =
|
983
993
|
case spec
|
984
994
|
when nil, :default
|
985
|
-
DefaultCompletion
|
995
|
+
DefaultCompletion
|
986
996
|
when ::Hash
|
987
|
-
|
997
|
+
spec[:""].nil? ? spec.merge({"": DefaultCompletion}) : spec
|
988
998
|
else
|
989
|
-
|
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)
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -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(
|
103
|
-
|
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(
|
134
|
-
|
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
|
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
|
-
|
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,
|
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)
|
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 =
|
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
|
|