toys-core 0.13.1 → 0.14.1
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 +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.
|