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.
- 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
@@ -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
|
-
@
|
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
|
-
@
|
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.
|
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
|
351
|
+
# Create an alias, representing an "alternate name" for a tool.
|
337
352
|
#
|
338
|
-
#
|
339
|
-
#
|
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]
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
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
|
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
|
-
|
150
|
+
prefix = orig_prefix
|
152
151
|
loop do
|
153
|
-
|
154
|
-
prefix
|
155
|
-
|
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
|
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
|
-
#
|
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
|
-
# @
|
266
|
+
# @param words [Array<String>] The name of the tool.
|
267
|
+
# @return [Boolean]
|
249
268
|
#
|
250
269
|
# @private
|
251
270
|
#
|
252
|
-
def
|
253
|
-
tool_data
|
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
|
-
#
|
266
|
-
# Does not load the tool if not found.
|
276
|
+
# Loads the subtree under the given prefix.
|
267
277
|
#
|
268
|
-
# @param
|
269
|
-
# @return [
|
278
|
+
# @param prefix [Array<String>] The name prefix.
|
279
|
+
# @return [self]
|
270
280
|
#
|
271
281
|
# @private
|
272
282
|
#
|
273
|
-
def
|
274
|
-
@
|
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
|
-
#
|
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
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
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
|
-
|
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
|
-
|
409
|
-
|
409
|
+
unless input.empty?
|
410
|
+
raise ::ArgumentError, "Unrecognized middleware arguments: #{input.inspect}"
|
411
|
+
end
|
412
|
+
middleware
|
410
413
|
end
|
411
414
|
|
412
|
-
def
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
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
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
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
|
-
|
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
|
|