toys-core 0.13.1 → 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +18 -16
- data/docs/guide.md +111 -6
- data/lib/toys/acceptor.rb +64 -58
- data/lib/toys/arg_parser.rb +4 -4
- data/lib/toys/cli.rb +3 -3
- data/lib/toys/completion.rb +5 -1
- data/lib/toys/context.rb +4 -3
- data/lib/toys/core.rb +1 -1
- data/lib/toys/errors.rb +14 -3
- data/lib/toys/flag.rb +10 -2
- data/lib/toys/input_file.rb +8 -8
- data/lib/toys/loader.rb +160 -92
- data/lib/toys/middleware.rb +24 -5
- data/lib/toys/settings.rb +2 -2
- data/lib/toys/source_info.rb +9 -9
- data/lib/toys/standard_middleware/show_help.rb +4 -16
- data/lib/toys/standard_mixins/exec.rb +86 -22
- data/lib/toys/standard_mixins/pager.rb +57 -0
- data/lib/toys/standard_mixins/xdg.rb +7 -7
- data/lib/toys/tool_definition.rb +10 -8
- data/lib/toys/utils/exec.rb +218 -37
- data/lib/toys/utils/help_text.rb +4 -1
- data/lib/toys/utils/pager.rb +138 -0
- data/lib/toys/wrappable_string.rb +12 -0
- metadata +6 -4
data/lib/toys/flag.rb
CHANGED
@@ -92,6 +92,7 @@ module Toys
|
|
92
92
|
# The type of flag (`:boolean` or `:value`)
|
93
93
|
# @return [:boolean] if this is a boolean flag (i.e. no value)
|
94
94
|
# @return [:value] if this flag takes a value (even if optional)
|
95
|
+
# @return [nil] if this flag is indeterminate
|
95
96
|
#
|
96
97
|
attr_reader :flag_type
|
97
98
|
|
@@ -128,7 +129,9 @@ module Toys
|
|
128
129
|
attr_reader :canonical_str
|
129
130
|
|
130
131
|
##
|
131
|
-
#
|
132
|
+
# This method is accessible for testing only.
|
133
|
+
#
|
134
|
+
# @private This interface is internal and subject to change without warning.
|
132
135
|
#
|
133
136
|
def configure_canonical(canonical_flag_type, canonical_value_type,
|
134
137
|
canonical_value_label, canonical_value_delim)
|
@@ -683,6 +686,7 @@ module Toys
|
|
683
686
|
@group = group
|
684
687
|
@key = key
|
685
688
|
@flag_syntax = Array(flags).map { |s| Syntax.new(s) }
|
689
|
+
@explicit_acceptor = !acceptor.nil?
|
686
690
|
@acceptor = Acceptor.create(acceptor)
|
687
691
|
@handler = resolve_handler(handler)
|
688
692
|
@desc = WrappableString.make(desc)
|
@@ -737,7 +741,7 @@ module Toys
|
|
737
741
|
end
|
738
742
|
if flag_str
|
739
743
|
needs_val = @value_completion != Completion::EMPTY ||
|
740
|
-
|
744
|
+
@explicit_acceptor ||
|
741
745
|
![nil, true, false].include?(@default)
|
742
746
|
flag_str = "#{flag_str} VALUE" if needs_val
|
743
747
|
@flag_syntax << Syntax.new(flag_str)
|
@@ -771,6 +775,10 @@ module Toys
|
|
771
775
|
analyze_flag_syntax(flag)
|
772
776
|
end
|
773
777
|
@flag_type ||= :boolean
|
778
|
+
if @flag_type == :boolean && @explicit_acceptor
|
779
|
+
raise ToolDefinitionError,
|
780
|
+
"Flag #{key.inspect} cannot have an acceptor because it does not take a value."
|
781
|
+
end
|
774
782
|
@value_type ||= :required if @flag_type == :value
|
775
783
|
flag_syntax.each do |flag|
|
776
784
|
flag.configure_canonical(@flag_type, @value_type, @value_label, @value_delim)
|
data/lib/toys/input_file.rb
CHANGED
@@ -6,14 +6,7 @@
|
|
6
6
|
#
|
7
7
|
module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
8
8
|
##
|
9
|
-
# @private
|
10
|
-
#
|
11
|
-
def self.__binding
|
12
|
-
binding
|
13
|
-
end
|
14
|
-
|
15
|
-
##
|
16
|
-
# @private
|
9
|
+
# @private This interface is internal and subject to change without warning.
|
17
10
|
#
|
18
11
|
def self.evaluate(tool_class, words, priority, remaining_words, source, loader)
|
19
12
|
namespace = ::Module.new
|
@@ -37,6 +30,13 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
37
30
|
end
|
38
31
|
end
|
39
32
|
|
33
|
+
##
|
34
|
+
# @private
|
35
|
+
#
|
36
|
+
def self.__binding
|
37
|
+
binding
|
38
|
+
end
|
39
|
+
|
40
40
|
##
|
41
41
|
# @private
|
42
42
|
#
|
data/lib/toys/loader.rb
CHANGED
@@ -205,10 +205,11 @@ module Toys
|
|
205
205
|
high_priority: false,
|
206
206
|
update: false,
|
207
207
|
context_directory: nil)
|
208
|
+
git_cache = @git_cache || Loader.default_git_cache
|
209
|
+
path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
|
208
210
|
@mutex.synchronize do
|
209
211
|
raise "Cannot add a git source after tool loading has started" if @loading_started
|
210
212
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
211
|
-
path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
|
212
213
|
source = SourceInfo.create_git_root(git_remote, git_path, git_commit, path, priority,
|
213
214
|
context_directory: context_directory,
|
214
215
|
data_dir_name: @data_dir_name,
|
@@ -257,43 +258,48 @@ module Toys
|
|
257
258
|
def lookup_specific(words)
|
258
259
|
words = @delimiter_handler.split_path(words.first) if words.size == 1
|
259
260
|
load_for_prefix(words)
|
260
|
-
tool = get_tool_data(words, false)&.cur_definition
|
261
|
+
tool = @mutex.synchronize { get_tool_data(words, false)&.cur_definition }
|
261
262
|
finish_definitions_in_tree(words) if tool
|
262
263
|
tool
|
263
264
|
end
|
264
265
|
|
265
266
|
##
|
266
267
|
# Returns a list of subtools for the given path, loading from the
|
267
|
-
# configuration if necessary.
|
268
|
+
# configuration if necessary. The list will be sorted by name.
|
268
269
|
#
|
269
270
|
# @param words [Array<String>] The name of the parent tool
|
270
271
|
# @param recursive [Boolean] If true, return all subtools recursively
|
271
272
|
# rather than just the immediate children (the default)
|
272
273
|
# @param include_hidden [Boolean] If true, include hidden subtools,
|
273
|
-
# e.
|
274
|
+
# i.e. names beginning with underscores. Defaults to false.
|
275
|
+
# @param include_namespaces [Boolean] If true, include namespaces,
|
276
|
+
# i.e. tools that are not runnable but have descendents that would have
|
277
|
+
# been listed by the current filters. Defaults to false.
|
278
|
+
# @param include_non_runnable [Boolean] If true, include tools that have
|
279
|
+
# no children and are not runnable. Defaults to false.
|
274
280
|
# @return [Array<Toys::ToolDefinition>] An array of subtools.
|
275
281
|
#
|
276
|
-
def list_subtools(words,
|
282
|
+
def list_subtools(words,
|
283
|
+
recursive: false,
|
284
|
+
include_hidden: false,
|
285
|
+
include_namespaces: false,
|
286
|
+
include_non_runnable: false)
|
277
287
|
load_for_prefix(words)
|
278
|
-
found_tools = []
|
279
288
|
len = words.length
|
280
|
-
all_cur_definitions.
|
289
|
+
found_tools = all_cur_definitions.find_all do |tool|
|
281
290
|
name = tool.full_name
|
282
|
-
|
283
|
-
|
284
|
-
next if name.length <= len || name.slice(0, len) != words
|
285
|
-
else
|
286
|
-
next unless name.slice(0..-2) == words
|
287
|
-
end
|
288
|
-
found_tools << tool
|
291
|
+
name.length > len && name.slice(0, len) == words &&
|
292
|
+
(include_hidden || name[len..-1].none? { |word| word.start_with?("_") })
|
289
293
|
end
|
290
|
-
|
291
|
-
|
294
|
+
found_tools.sort_by!(&:full_name)
|
295
|
+
found_tools = filter_non_runnable_tools(found_tools, include_namespaces, include_non_runnable)
|
296
|
+
found_tools.select! { |tool| tool.full_name.length == len + 1 } unless recursive
|
297
|
+
found_tools
|
292
298
|
end
|
293
299
|
|
294
300
|
##
|
295
|
-
# Returns true if the given path has at least one subtool
|
296
|
-
# configuration if necessary.
|
301
|
+
# Returns true if the given path has at least one subtool, even if they are
|
302
|
+
# hidden or non-runnable. Loads from the configuration if necessary.
|
297
303
|
#
|
298
304
|
# @param words [Array<String>] The name of the parent tool
|
299
305
|
# @return [Boolean]
|
@@ -301,13 +307,10 @@ module Toys
|
|
301
307
|
def has_subtools?(words) # rubocop:disable Naming/PredicateName
|
302
308
|
load_for_prefix(words)
|
303
309
|
len = words.length
|
304
|
-
all_cur_definitions.
|
310
|
+
all_cur_definitions.any? do |tool|
|
305
311
|
name = tool.full_name
|
306
|
-
|
307
|
-
return true
|
308
|
-
end
|
312
|
+
name.length > len && name.slice(0, len) == words
|
309
313
|
end
|
310
|
-
false
|
311
314
|
end
|
312
315
|
|
313
316
|
##
|
@@ -323,13 +326,17 @@ module Toys
|
|
323
326
|
@delimiter_handler.split_path(str.to_s)
|
324
327
|
end
|
325
328
|
|
329
|
+
#### INTERNAL METHODS ####
|
330
|
+
|
326
331
|
##
|
327
332
|
# Get or create the tool definition for the given name and priority.
|
328
333
|
#
|
329
|
-
# @private
|
334
|
+
# @private This interface is internal and subject to change without warning.
|
330
335
|
#
|
331
336
|
def get_tool(words, priority, tool_class = nil)
|
332
|
-
|
337
|
+
@mutex.synchronize do
|
338
|
+
get_tool_data(words, true).get_tool(priority, self, tool_class)
|
339
|
+
end
|
333
340
|
end
|
334
341
|
|
335
342
|
##
|
@@ -339,17 +346,19 @@ module Toys
|
|
339
346
|
# the active priority, returns `nil`. If the given priority is higher than
|
340
347
|
# the active priority, returns and activates a new tool.
|
341
348
|
#
|
342
|
-
# @private
|
349
|
+
# @private This interface is internal and subject to change without warning.
|
343
350
|
#
|
344
351
|
def activate_tool(words, priority)
|
345
|
-
|
352
|
+
@mutex.synchronize do
|
353
|
+
get_tool_data(words, true).activate_tool(priority, self)
|
354
|
+
end
|
346
355
|
end
|
347
356
|
|
348
357
|
##
|
349
358
|
# Returns true if the given tool name currently exists in the loader.
|
350
359
|
# Does not load the tool if not found.
|
351
360
|
#
|
352
|
-
# @private
|
361
|
+
# @private This interface is internal and subject to change without warning.
|
353
362
|
#
|
354
363
|
def tool_defined?(words)
|
355
364
|
@tool_data.key?(words)
|
@@ -359,7 +368,7 @@ module Toys
|
|
359
368
|
# Build a new tool.
|
360
369
|
# Called only from ToolData.
|
361
370
|
#
|
362
|
-
# @private
|
371
|
+
# @private This interface is internal and subject to change without warning.
|
363
372
|
#
|
364
373
|
def build_tool(words, priority, tool_class = nil)
|
365
374
|
parent = words.empty? ? nil : get_tool(words.slice(0..-2), priority)
|
@@ -372,7 +381,7 @@ module Toys
|
|
372
381
|
# Stop search at the given priority. Returns true if successful.
|
373
382
|
# Called only from the DSL.
|
374
383
|
#
|
375
|
-
# @private
|
384
|
+
# @private This interface is internal and subject to change without warning.
|
376
385
|
#
|
377
386
|
def stop_loading_at_priority(priority)
|
378
387
|
@mutex.synchronize do
|
@@ -385,7 +394,7 @@ module Toys
|
|
385
394
|
##
|
386
395
|
# Loads the subtree under the given prefix.
|
387
396
|
#
|
388
|
-
# @private
|
397
|
+
# @private This interface is internal and subject to change without warning.
|
389
398
|
#
|
390
399
|
def load_for_prefix(prefix)
|
391
400
|
@mutex.synchronize do
|
@@ -408,7 +417,7 @@ module Toys
|
|
408
417
|
##
|
409
418
|
# Attempt to get a well-known mixin module for the given symbolic name.
|
410
419
|
#
|
411
|
-
# @private
|
420
|
+
# @private This interface is internal and subject to change without warning.
|
412
421
|
#
|
413
422
|
def resolve_standard_mixin(name)
|
414
423
|
@mixin_lookup.lookup(name)
|
@@ -417,7 +426,7 @@ module Toys
|
|
417
426
|
##
|
418
427
|
# Attempt to get a well-known template class for the given symbolic name.
|
419
428
|
#
|
420
|
-
# @private
|
429
|
+
# @private This interface is internal and subject to change without warning.
|
421
430
|
#
|
422
431
|
def resolve_standard_template(name)
|
423
432
|
@template_lookup.lookup(name)
|
@@ -427,7 +436,7 @@ module Toys
|
|
427
436
|
# Load configuration from the given path. This is called from the `load`
|
428
437
|
# directive in the DSL.
|
429
438
|
#
|
430
|
-
# @private
|
439
|
+
# @private This interface is internal and subject to change without warning.
|
431
440
|
#
|
432
441
|
def load_path(parent_source, path, words, remaining_words, priority)
|
433
442
|
if parent_source.git_remote
|
@@ -444,10 +453,11 @@ module Toys
|
|
444
453
|
# Load configuration from the given git remote. This is called from the
|
445
454
|
# `load_git` directive in the DSL.
|
446
455
|
#
|
447
|
-
# @private
|
456
|
+
# @private This interface is internal and subject to change without warning.
|
448
457
|
#
|
449
458
|
def load_git(parent_source, git_remote, git_path, git_commit, words, remaining_words, priority,
|
450
459
|
update: false)
|
460
|
+
git_cache = @git_cache || Loader.default_git_cache
|
451
461
|
path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
|
452
462
|
source = parent_source.git_child(git_remote, git_path, git_commit, path)
|
453
463
|
@mutex.synchronize do
|
@@ -458,7 +468,7 @@ module Toys
|
|
458
468
|
##
|
459
469
|
# Load a subtool block. Called from the `tool` directive in the DSL.
|
460
470
|
#
|
461
|
-
# @private
|
471
|
+
# @private This interface is internal and subject to change without warning.
|
462
472
|
#
|
463
473
|
def load_block(parent_source, block, words, remaining_words, priority)
|
464
474
|
source = parent_source.proc_child(block)
|
@@ -467,22 +477,27 @@ module Toys
|
|
467
477
|
end
|
468
478
|
end
|
469
479
|
|
480
|
+
@git_cache_mutex = ::Monitor.new
|
481
|
+
@default_git_cache = nil
|
482
|
+
|
470
483
|
##
|
471
|
-
# Get a GitCache.
|
484
|
+
# Get a global default GitCache.
|
472
485
|
#
|
473
|
-
# @private
|
486
|
+
# @private This interface is internal and subject to change without warning.
|
474
487
|
#
|
475
|
-
def
|
476
|
-
@
|
477
|
-
|
478
|
-
|
488
|
+
def self.default_git_cache
|
489
|
+
@git_cache_mutex.synchronize do
|
490
|
+
@default_git_cache ||= begin
|
491
|
+
require "toys/utils/git_cache"
|
492
|
+
Utils::GitCache.new
|
493
|
+
end
|
479
494
|
end
|
480
495
|
end
|
481
496
|
|
482
497
|
##
|
483
498
|
# Determine the next setting for remaining_words, given a word.
|
484
499
|
#
|
485
|
-
# @private
|
500
|
+
# @private This interface is internal and subject to change without warning.
|
486
501
|
#
|
487
502
|
def self.next_remaining_words(remaining_words, word)
|
488
503
|
if remaining_words.nil?
|
@@ -495,26 +510,35 @@ module Toys
|
|
495
510
|
end
|
496
511
|
|
497
512
|
##
|
498
|
-
#
|
513
|
+
# An internal object managing the various definitions for a specific tool
|
514
|
+
# tool name and their priorities, and tracking which, if any, has been
|
515
|
+
# activated.
|
516
|
+
#
|
517
|
+
# This class is not thread-safe by itself. The caller must protect access
|
518
|
+
# with a mutex.
|
499
519
|
#
|
500
520
|
# @private
|
501
521
|
#
|
502
522
|
class ToolData
|
503
523
|
##
|
524
|
+
# Create an empty tool data with no definitions.
|
525
|
+
#
|
504
526
|
# @private
|
505
527
|
#
|
506
528
|
def initialize(words)
|
507
529
|
@words = validate_words(words)
|
508
530
|
@definitions = {}
|
509
531
|
@top_priority = @active_priority = nil
|
510
|
-
@mutex = ::Monitor.new
|
511
532
|
end
|
512
533
|
|
513
534
|
##
|
535
|
+
# Return the current "best" definition, which is either the active
|
536
|
+
# definition, or, if none, the current highest-priority definition.
|
537
|
+
#
|
514
538
|
# @private
|
515
539
|
#
|
516
540
|
def cur_definition
|
517
|
-
|
541
|
+
active_definition || top_definition
|
518
542
|
end
|
519
543
|
|
520
544
|
##
|
@@ -525,30 +549,37 @@ module Toys
|
|
525
549
|
end
|
526
550
|
|
527
551
|
##
|
552
|
+
# Ensure there is a tool definition of the given priority, creating it if
|
553
|
+
# needed, and return it. A tool class may be provided, but only if the
|
554
|
+
# tool definition has not yet been created.
|
555
|
+
#
|
528
556
|
# @private
|
529
557
|
#
|
530
558
|
def get_tool(priority, loader, tool_class = nil)
|
531
|
-
@
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
|
537
|
-
end
|
538
|
-
@definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
|
559
|
+
if @top_priority.nil? || @top_priority < priority
|
560
|
+
@top_priority = priority
|
561
|
+
end
|
562
|
+
if tool_class && @definitions.include?(priority)
|
563
|
+
raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
|
539
564
|
end
|
565
|
+
@definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
|
540
566
|
end
|
541
567
|
|
542
568
|
##
|
569
|
+
# Attempt to activate the tool with the given priority, and return it.
|
570
|
+
# If the given priority tool is already active, returns it.
|
571
|
+
# If a lower priority tool is already active, activates the given higher
|
572
|
+
# priority tool and returns it.
|
573
|
+
# If a higher priority tool is already active, does nothing and returns
|
574
|
+
# nil.
|
575
|
+
#
|
543
576
|
# @private
|
544
577
|
#
|
545
578
|
def activate_tool(priority, loader)
|
546
|
-
@
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
get_tool(priority, loader)
|
551
|
-
end
|
579
|
+
return active_definition if @active_priority == priority
|
580
|
+
return nil if @active_priority && @active_priority > priority
|
581
|
+
@active_priority = priority
|
582
|
+
get_tool(priority, loader)
|
552
583
|
end
|
553
584
|
|
554
585
|
private
|
@@ -610,10 +641,15 @@ module Toys
|
|
610
641
|
|
611
642
|
private
|
612
643
|
|
644
|
+
##
|
645
|
+
# Return a snapshot of all the current tool definitions that have been
|
646
|
+
# loaded. No additional loading is done. The returned array is not in any
|
647
|
+
# particular order.
|
648
|
+
#
|
613
649
|
def all_cur_definitions
|
614
650
|
result = []
|
615
651
|
@mutex.synchronize do
|
616
|
-
@tool_data.
|
652
|
+
@tool_data.each_value do |td|
|
617
653
|
tool = td.cur_definition
|
618
654
|
result << tool unless tool.nil?
|
619
655
|
end
|
@@ -621,10 +657,12 @@ module Toys
|
|
621
657
|
result
|
622
658
|
end
|
623
659
|
|
660
|
+
##
|
661
|
+
# Get or create the ToolData for the given name.
|
662
|
+
# Caller must own the mutex.
|
663
|
+
#
|
624
664
|
def get_tool_data(words, create)
|
625
|
-
@
|
626
|
-
create ? (@tool_data[words] ||= ToolData.new(words)) : @tool_data[words]
|
627
|
-
end
|
665
|
+
create ? (@tool_data[words] ||= ToolData.new(words)) : @tool_data[words]
|
628
666
|
end
|
629
667
|
|
630
668
|
##
|
@@ -641,6 +679,10 @@ module Toys
|
|
641
679
|
end
|
642
680
|
end
|
643
681
|
|
682
|
+
##
|
683
|
+
# Loads from a proc source.
|
684
|
+
# Caller must own the mutex.
|
685
|
+
#
|
644
686
|
def load_proc(source, words, remaining_words, priority)
|
645
687
|
if remaining_words
|
646
688
|
update_min_loaded_priority(priority)
|
@@ -655,6 +697,10 @@ module Toys
|
|
655
697
|
end
|
656
698
|
end
|
657
699
|
|
700
|
+
##
|
701
|
+
# Load from a file path source that is known to exist.
|
702
|
+
# Caller must own the mutex.
|
703
|
+
#
|
658
704
|
def load_validated_path(source, words, remaining_words, priority)
|
659
705
|
if remaining_words
|
660
706
|
load_relevant_path(source, words, remaining_words, priority)
|
@@ -663,6 +709,11 @@ module Toys
|
|
663
709
|
end
|
664
710
|
end
|
665
711
|
|
712
|
+
##
|
713
|
+
# Load from a file path source that is known to exist and is known to be
|
714
|
+
# relevant to the current load request.
|
715
|
+
# Caller must own the mutex.
|
716
|
+
#
|
666
717
|
def load_relevant_path(source, words, remaining_words, priority)
|
667
718
|
if source.source_type == :file
|
668
719
|
update_min_loaded_priority(priority)
|
@@ -677,12 +728,20 @@ module Toys
|
|
677
728
|
end
|
678
729
|
end
|
679
730
|
|
731
|
+
##
|
732
|
+
# Load an index file in a directory source.
|
733
|
+
# Caller must own the mutex.
|
734
|
+
#
|
680
735
|
def load_index_in(source, words, remaining_words, priority)
|
681
736
|
return unless @index_file_name
|
682
737
|
index_source = source.relative_child(@index_file_name)
|
683
738
|
load_relevant_path(index_source, words, remaining_words, priority) if index_source
|
684
739
|
end
|
685
740
|
|
741
|
+
##
|
742
|
+
# Load non-index file in a directory source.
|
743
|
+
# Caller must own the mutex.
|
744
|
+
#
|
686
745
|
def load_child_in(source, child, words, remaining_words, priority)
|
687
746
|
return if child.start_with?(".") || child == @index_file_name ||
|
688
747
|
child == @preload_file_name || child == @preload_dir_name ||
|
@@ -695,10 +754,17 @@ module Toys
|
|
695
754
|
load_validated_path(child_source, next_words, next_remaining, priority)
|
696
755
|
end
|
697
756
|
|
757
|
+
##
|
758
|
+
# Update min_loaded_priority to the given value.
|
759
|
+
# Caller must own the mutex.
|
760
|
+
#
|
698
761
|
def update_min_loaded_priority(priority)
|
699
762
|
@min_loaded_priority = priority if @min_loaded_priority > priority
|
700
763
|
end
|
701
764
|
|
765
|
+
##
|
766
|
+
# Look for and require any preloads.
|
767
|
+
#
|
702
768
|
def do_preload(path)
|
703
769
|
if @preload_file_name
|
704
770
|
preload_file = ::File.join(path, @preload_file_name)
|
@@ -709,13 +775,16 @@ module Toys
|
|
709
775
|
if @preload_dir_name
|
710
776
|
preload_dir = ::File.join(path, @preload_dir_name)
|
711
777
|
if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
|
712
|
-
|
778
|
+
require_dir_contents(preload_dir)
|
713
779
|
end
|
714
780
|
end
|
715
781
|
end
|
716
782
|
|
717
|
-
|
718
|
-
|
783
|
+
##
|
784
|
+
# Require the contents of the given directory.
|
785
|
+
#
|
786
|
+
def require_dir_contents(preload_dir)
|
787
|
+
::Dir.entries(preload_dir).sort.each do |child|
|
719
788
|
next unless ::File.extname(child) == ".rb"
|
720
789
|
preload_file = ::File.join(preload_dir, child)
|
721
790
|
next if !::File.file?(preload_file) || !::File.readable?(preload_file)
|
@@ -723,31 +792,6 @@ module Toys
|
|
723
792
|
end
|
724
793
|
end
|
725
794
|
|
726
|
-
def sort_tools_by_name(tools)
|
727
|
-
tools.sort! do |a, b|
|
728
|
-
a = a.full_name
|
729
|
-
b = b.full_name
|
730
|
-
while !a.empty? && !b.empty? && a.first == b.first
|
731
|
-
a = a.slice(1..-1)
|
732
|
-
b = b.slice(1..-1)
|
733
|
-
end
|
734
|
-
a.first.to_s <=> b.first.to_s
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
def filter_hidden_subtools(tools)
|
739
|
-
result = []
|
740
|
-
tools.each_with_index do |tool, index|
|
741
|
-
result << tool unless tool_hidden?(tool, tools[index + 1])
|
742
|
-
end
|
743
|
-
result
|
744
|
-
end
|
745
|
-
|
746
|
-
def tool_hidden?(tool, next_tool)
|
747
|
-
return true if tool.full_name.any? { |n| n.start_with?("_") }
|
748
|
-
!tool.runnable? && next_tool && next_tool.full_name.slice(0..-2) == tool.full_name
|
749
|
-
end
|
750
|
-
|
751
795
|
def calc_remaining_words(words1, words2)
|
752
796
|
index = 0
|
753
797
|
lengths = [words1.length, words2.length]
|
@@ -757,5 +801,29 @@ module Toys
|
|
757
801
|
index += 1
|
758
802
|
end
|
759
803
|
end
|
804
|
+
|
805
|
+
##
|
806
|
+
# Given a sorted list of tools, filter out non-runnable tools, subject to
|
807
|
+
# the given settings.
|
808
|
+
#
|
809
|
+
def filter_non_runnable_tools(tools, include_namespaces, include_non_runnable)
|
810
|
+
return tools if include_namespaces && include_non_runnable
|
811
|
+
|
812
|
+
# This is a bit of a clever algorithm, sorry. We iterate over the sorted
|
813
|
+
# list of tools backwards (i.e. a reverse depth-first traversal) and
|
814
|
+
# apply the runnable and namespace filters.
|
815
|
+
# We determine whether a non-runnable tool is a namespace (i.e. has a
|
816
|
+
# runnable descendent) by tracking the state "kept_depth" representing
|
817
|
+
# the longest tool name length of a tool that we have kept and whose
|
818
|
+
# parent has yet to be traversed. Thus, when we traverse a non-runnable
|
819
|
+
# node, we can tell whether we have kept at least one child.
|
820
|
+
kept_depth = 0
|
821
|
+
tools.reverse_each.select do |tool|
|
822
|
+
cur_len = tool.full_name.length
|
823
|
+
keep = tool.runnable? || (kept_depth > cur_len ? include_namespaces : include_non_runnable)
|
824
|
+
kept_depth = cur_len if keep || kept_depth > cur_len
|
825
|
+
keep
|
826
|
+
end.reverse
|
827
|
+
end
|
760
828
|
end
|
761
829
|
end
|
data/lib/toys/middleware.rb
CHANGED
@@ -141,7 +141,9 @@ module Toys
|
|
141
141
|
end
|
142
142
|
|
143
143
|
##
|
144
|
-
#
|
144
|
+
# Create a spec from an array specification.
|
145
|
+
#
|
146
|
+
# @private This interface is internal and subject to change without warning.
|
145
147
|
#
|
146
148
|
def spec_from_array(array)
|
147
149
|
middleware = array.first
|
@@ -168,7 +170,7 @@ module Toys
|
|
168
170
|
end
|
169
171
|
|
170
172
|
##
|
171
|
-
# A base class that provides default
|
173
|
+
# A base class that provides default no-op implementation of the middleware
|
172
174
|
# interface. This base class may optionally be subclassed by a middleware
|
173
175
|
# implementation.
|
174
176
|
#
|
@@ -266,7 +268,9 @@ module Toys
|
|
266
268
|
end
|
267
269
|
|
268
270
|
##
|
269
|
-
#
|
271
|
+
# Internal constructor. Use {Toys::Middleware.spec} instead.
|
272
|
+
#
|
273
|
+
# @private This interface is internal and subject to change without warning.
|
270
274
|
#
|
271
275
|
def initialize(object, name, args, kwargs, block)
|
272
276
|
@object = object
|
@@ -278,7 +282,20 @@ module Toys
|
|
278
282
|
end
|
279
283
|
|
280
284
|
##
|
281
|
-
# A stack of middleware specs.
|
285
|
+
# A stack of middleware specs, which can be applied in order to a tool.
|
286
|
+
#
|
287
|
+
# A middleware stack is separated into three groups:
|
288
|
+
#
|
289
|
+
# * {#pre_specs}, which are applied first.
|
290
|
+
# * {#default_specs}, which are applied next. The default specs are set
|
291
|
+
# when the stack is created and are generally not modified.
|
292
|
+
# * {#post_specs}, which are applied third.
|
293
|
+
#
|
294
|
+
# When adding middleware to a stack, you should normally add them to the
|
295
|
+
# pre or post specs. By default, {Stack#add} appends to the pre specs,
|
296
|
+
# inserting new middleware just before the defaults.
|
297
|
+
#
|
298
|
+
# Use {Toys::Middleware.stack} to create a middleware stack.
|
282
299
|
#
|
283
300
|
class Stack
|
284
301
|
##
|
@@ -356,7 +373,9 @@ module Toys
|
|
356
373
|
end
|
357
374
|
|
358
375
|
##
|
359
|
-
#
|
376
|
+
# Internal constructor. Use {Toys::Middleware.stack} instead.
|
377
|
+
#
|
378
|
+
# @private This interface is internal and subject to change without warning.
|
360
379
|
#
|
361
380
|
def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
|
362
381
|
@pre_specs = pre_specs || []
|
data/lib/toys/settings.rb
CHANGED
@@ -334,7 +334,7 @@ module Toys
|
|
334
334
|
attr_reader :type_description
|
335
335
|
|
336
336
|
##
|
337
|
-
# @private
|
337
|
+
# @private This interface is internal and subject to change without warning.
|
338
338
|
#
|
339
339
|
def initialize(value, settings_class, field_name, type_description)
|
340
340
|
@value = value
|
@@ -835,7 +835,7 @@ module Toys
|
|
835
835
|
end
|
836
836
|
|
837
837
|
##
|
838
|
-
# @private
|
838
|
+
# @private This interface is internal and subject to change without warning.
|
839
839
|
#
|
840
840
|
# Returns the fields hash. This is shared between the settings class and
|
841
841
|
# all its instances.
|