toys-core 0.12.2 → 0.13.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +4 -1
  5. data/docs/guide.md +1 -1
  6. data/lib/toys/acceptor.rb +10 -1
  7. data/lib/toys/arg_parser.rb +1 -0
  8. data/lib/toys/cli.rb +127 -107
  9. data/lib/toys/compat.rb +54 -3
  10. data/lib/toys/completion.rb +15 -5
  11. data/lib/toys/context.rb +22 -20
  12. data/lib/toys/core.rb +6 -2
  13. data/lib/toys/dsl/base.rb +2 -0
  14. data/lib/toys/dsl/flag.rb +23 -17
  15. data/lib/toys/dsl/flag_group.rb +11 -7
  16. data/lib/toys/dsl/positional_arg.rb +23 -13
  17. data/lib/toys/dsl/tool.rb +10 -6
  18. data/lib/toys/errors.rb +63 -8
  19. data/lib/toys/flag.rb +660 -651
  20. data/lib/toys/flag_group.rb +19 -6
  21. data/lib/toys/input_file.rb +9 -3
  22. data/lib/toys/loader.rb +129 -115
  23. data/lib/toys/middleware.rb +45 -21
  24. data/lib/toys/mixin.rb +8 -6
  25. data/lib/toys/positional_arg.rb +18 -17
  26. data/lib/toys/settings.rb +81 -67
  27. data/lib/toys/source_info.rb +33 -24
  28. data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
  29. data/lib/toys/standard_middleware/apply_config.rb +1 -0
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +1 -0
  32. data/lib/toys/standard_middleware/show_help.rb +2 -0
  33. data/lib/toys/standard_middleware/show_root_version.rb +2 -0
  34. data/lib/toys/standard_mixins/bundler.rb +22 -14
  35. data/lib/toys/standard_mixins/exec.rb +31 -20
  36. data/lib/toys/standard_mixins/fileutils.rb +3 -1
  37. data/lib/toys/standard_mixins/gems.rb +21 -17
  38. data/lib/toys/standard_mixins/git_cache.rb +5 -7
  39. data/lib/toys/standard_mixins/highline.rb +8 -8
  40. data/lib/toys/standard_mixins/terminal.rb +5 -5
  41. data/lib/toys/standard_mixins/xdg.rb +5 -5
  42. data/lib/toys/template.rb +9 -7
  43. data/lib/toys/tool_definition.rb +209 -202
  44. data/lib/toys/utils/completion_engine.rb +7 -2
  45. data/lib/toys/utils/exec.rb +158 -127
  46. data/lib/toys/utils/gems.rb +81 -57
  47. data/lib/toys/utils/git_cache.rb +674 -45
  48. data/lib/toys/utils/help_text.rb +27 -3
  49. data/lib/toys/utils/terminal.rb +10 -2
  50. data/lib/toys/wrappable_string.rb +9 -2
  51. data/lib/toys-core.rb +14 -5
  52. metadata +4 -4
@@ -56,6 +56,7 @@ module Toys
56
56
  # Create a flag group.
57
57
  # This argument list is subject to change. Use {Toys::FlagGroup.create}
58
58
  # instead for a more stable interface.
59
+ #
59
60
  # @private
60
61
  #
61
62
  def initialize(name, desc, long_desc)
@@ -168,12 +169,16 @@ module Toys
168
169
  self
169
170
  end
170
171
 
171
- ## @private
172
+ ##
173
+ # @private
174
+ #
172
175
  def <<(flag)
173
176
  flags << flag
174
177
  end
175
178
 
176
- ## @private
179
+ ##
180
+ # @private
181
+ #
177
182
  def validation_errors(_seen)
178
183
  []
179
184
  end
@@ -183,7 +188,9 @@ module Toys
183
188
  # A FlagGroup containing all required flags
184
189
  #
185
190
  class Required < Base
186
- ## @private
191
+ ##
192
+ # @private
193
+ #
187
194
  def validation_errors(seen)
188
195
  results = []
189
196
  flags.each do |flag|
@@ -206,7 +213,9 @@ module Toys
206
213
  # A FlagGroup in which exactly one flag must be set
207
214
  #
208
215
  class ExactlyOne < Base
209
- ## @private
216
+ ##
217
+ # @private
218
+ #
210
219
  def validation_errors(seen)
211
220
  seen_names = []
212
221
  flags.each do |flag|
@@ -229,7 +238,9 @@ module Toys
229
238
  # A FlagGroup in which at most one flag must be set
230
239
  #
231
240
  class AtMostOne < Base
232
- ## @private
241
+ ##
242
+ # @private
243
+ #
233
244
  def validation_errors(seen)
234
245
  seen_names = []
235
246
  flags.each do |flag|
@@ -249,7 +260,9 @@ module Toys
249
260
  # A FlagGroup in which at least one flag must be set
250
261
  #
251
262
  class AtLeastOne < Base
252
- ## @private
263
+ ##
264
+ # @private
265
+ #
253
266
  def validation_errors(seen)
254
267
  flags.each do |flag|
255
268
  return [] if seen.include?(flag.key)
@@ -5,12 +5,16 @@
5
5
  # is parsed, a module is created under this parent for that file's constants.
6
6
  #
7
7
  module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
8
- ## @private
8
+ ##
9
+ # @private
10
+ #
9
11
  def self.__binding
10
12
  binding
11
13
  end
12
14
 
13
- ## @private
15
+ ##
16
+ # @private
17
+ #
14
18
  def self.evaluate(tool_class, words, priority, remaining_words, source, loader)
15
19
  namespace = ::Module.new
16
20
  namespace.module_eval do
@@ -33,7 +37,9 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
33
37
  end
34
38
  end
35
39
 
36
- ## @private
40
+ ##
41
+ # @private
42
+ #
37
43
  def self.build_eval_string(module_name, string)
38
44
  index = string.index(/^\s*[^#\s]/)
39
45
  return nil if index.nil?
data/lib/toys/loader.rb CHANGED
@@ -8,9 +8,6 @@ module Toys
8
8
  # appropriate tool given a set of command line arguments.
9
9
  #
10
10
  class Loader
11
- # @private
12
- BASE_PRIORITY = -999_999
13
-
14
11
  ##
15
12
  # Create a Loader
16
13
  #
@@ -44,19 +41,17 @@ module Toys
44
41
  # @param template_lookup [Toys::ModuleLookup] A lookup for
45
42
  # well-known template classes. Defaults to an empty lookup.
46
43
  #
47
- def initialize(
48
- index_file_name: nil,
49
- preload_dir_name: nil,
50
- preload_file_name: nil,
51
- data_dir_name: nil,
52
- lib_dir_name: nil,
53
- middleware_stack: [],
54
- extra_delimiters: "",
55
- mixin_lookup: nil,
56
- middleware_lookup: nil,
57
- template_lookup: nil,
58
- git_cache: nil
59
- )
44
+ def initialize(index_file_name: nil,
45
+ preload_dir_name: nil,
46
+ preload_file_name: nil,
47
+ data_dir_name: nil,
48
+ lib_dir_name: nil,
49
+ middleware_stack: [],
50
+ extra_delimiters: "",
51
+ mixin_lookup: nil,
52
+ middleware_lookup: nil,
53
+ template_lookup: nil,
54
+ git_cache: nil)
60
55
  if index_file_name && ::File.extname(index_file_name) != ".rb"
61
56
  raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
62
57
  end
@@ -74,12 +69,12 @@ module Toys
74
69
  @tool_data = {}
75
70
  @roots_by_priority = {}
76
71
  @max_priority = @min_priority = 0
77
- @stop_priority = BASE_PRIORITY
72
+ @stop_priority = -999_999
78
73
  @min_loaded_priority = 999_999
79
74
  @middleware_stack = Middleware.stack(middleware_stack)
80
75
  @delimiter_handler = DelimiterHandler.new(extra_delimiters)
81
76
  @git_cache = git_cache
82
- get_tool([], BASE_PRIORITY)
77
+ get_tool([], -999_999)
83
78
  end
84
79
 
85
80
  ##
@@ -213,7 +208,7 @@ module Toys
213
208
  @mutex.synchronize do
214
209
  raise "Cannot add a git source after tool loading has started" if @loading_started
215
210
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
216
- path = git_cache.find(git_remote, path: git_path, commit: git_commit, update: update)
211
+ path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
217
212
  source = SourceInfo.create_git_root(git_remote, git_path, git_commit, path, priority,
218
213
  context_directory: context_directory,
219
214
  data_dir_name: @data_dir_name,
@@ -453,7 +448,7 @@ module Toys
453
448
  #
454
449
  def load_git(parent_source, git_remote, git_path, git_commit, words, remaining_words, priority,
455
450
  update: false)
456
- path = git_cache.find(git_remote, path: git_path, commit: git_commit, update: update)
451
+ path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
457
452
  source = parent_source.git_child(git_remote, git_path, git_commit, path)
458
453
  @mutex.synchronize do
459
454
  load_validated_path(source, words, remaining_words, priority)
@@ -499,6 +494,120 @@ module Toys
499
494
  end
500
495
  end
501
496
 
497
+ ##
498
+ # Tool data
499
+ #
500
+ # @private
501
+ #
502
+ class ToolData
503
+ ##
504
+ # @private
505
+ #
506
+ def initialize(words)
507
+ @words = validate_words(words)
508
+ @definitions = {}
509
+ @top_priority = @active_priority = nil
510
+ @mutex = ::Monitor.new
511
+ end
512
+
513
+ ##
514
+ # @private
515
+ #
516
+ def cur_definition
517
+ @mutex.synchronize { active_definition || top_definition }
518
+ end
519
+
520
+ ##
521
+ # @private
522
+ #
523
+ def empty?
524
+ @definitions.empty?
525
+ end
526
+
527
+ ##
528
+ # @private
529
+ #
530
+ def get_tool(priority, loader, tool_class = nil)
531
+ @mutex.synchronize do
532
+ if @top_priority.nil? || @top_priority < priority
533
+ @top_priority = priority
534
+ end
535
+ if tool_class && @definitions.include?(priority)
536
+ raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
537
+ end
538
+ @definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
539
+ end
540
+ end
541
+
542
+ ##
543
+ # @private
544
+ #
545
+ def activate_tool(priority, loader)
546
+ @mutex.synchronize do
547
+ return active_definition if @active_priority == priority
548
+ return nil if @active_priority && @active_priority > priority
549
+ @active_priority = priority
550
+ get_tool(priority, loader)
551
+ end
552
+ end
553
+
554
+ private
555
+
556
+ def validate_words(words)
557
+ words.each do |word|
558
+ if /[[:cntrl:] #"$&'()*;<>\[\\\]\^`{|}]/.match(word)
559
+ raise ToolDefinitionError, "Illegal characters in name #{word.inspect}"
560
+ end
561
+ end
562
+ end
563
+
564
+ def top_definition
565
+ @top_priority ? @definitions[@top_priority] : nil
566
+ end
567
+
568
+ def active_definition
569
+ @active_priority ? @definitions[@active_priority] : nil
570
+ end
571
+ end
572
+
573
+ ##
574
+ # An object that handles name delimiting.
575
+ #
576
+ # @private
577
+ #
578
+ class DelimiterHandler
579
+ ##
580
+ # @private
581
+ #
582
+ def initialize(extra_delimiters)
583
+ unless %r{^[[:space:]./:]*$}.match?(extra_delimiters)
584
+ raise ::ArgumentError, "Illegal delimiters in #{extra_delimiters.inspect}"
585
+ end
586
+ chars = ::Regexp.escape(extra_delimiters.chars.uniq.join)
587
+ @delimiters = ::Regexp.new("[[:space:]#{chars}]")
588
+ end
589
+
590
+ ##
591
+ # @private
592
+ #
593
+ def split_path(str)
594
+ str.split(@delimiters)
595
+ end
596
+
597
+ ##
598
+ # @private
599
+ #
600
+ def find_orig_prefix(args)
601
+ first_split = (args.first || "").split(@delimiters)
602
+ if first_split.size > 1
603
+ args = first_split + args.slice(1..-1)
604
+ return [first_split, args]
605
+ end
606
+ orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
607
+ [orig_prefix, args]
608
+ end
609
+ end
610
+
502
611
  private
503
612
 
504
613
  def all_cur_definitions
@@ -648,100 +757,5 @@ module Toys
648
757
  index += 1
649
758
  end
650
759
  end
651
-
652
- ##
653
- # Tool data
654
- #
655
- # @private
656
- #
657
- class ToolData
658
- # @private
659
- def initialize(words)
660
- @words = validate_words(words)
661
- @definitions = {}
662
- @top_priority = @active_priority = nil
663
- @mutex = ::Monitor.new
664
- end
665
-
666
- # @private
667
- def cur_definition
668
- @mutex.synchronize { active_definition || top_definition }
669
- end
670
-
671
- # @private
672
- def empty?
673
- @definitions.empty?
674
- end
675
-
676
- # @private
677
- def get_tool(priority, loader, tool_class = nil)
678
- @mutex.synchronize do
679
- if @top_priority.nil? || @top_priority < priority
680
- @top_priority = priority
681
- end
682
- if tool_class && @definitions.include?(priority)
683
- raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
684
- end
685
- @definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
686
- end
687
- end
688
-
689
- # @private
690
- def activate_tool(priority, loader)
691
- @mutex.synchronize do
692
- return active_definition if @active_priority == priority
693
- return nil if @active_priority && @active_priority > priority
694
- @active_priority = priority
695
- get_tool(priority, loader)
696
- end
697
- end
698
-
699
- private
700
-
701
- def validate_words(words)
702
- words.each do |word|
703
- if /[[:cntrl:] #"$&'()*;<>\[\\\]\^`{|}]/.match(word)
704
- raise ToolDefinitionError, "Illegal characters in name #{word.inspect}"
705
- end
706
- end
707
- end
708
-
709
- def top_definition
710
- @top_priority ? @definitions[@top_priority] : nil
711
- end
712
-
713
- def active_definition
714
- @active_priority ? @definitions[@active_priority] : nil
715
- end
716
- end
717
-
718
- ##
719
- # An object that handles name delimiting.
720
- #
721
- # @private
722
- #
723
- class DelimiterHandler
724
- def initialize(extra_delimiters)
725
- unless %r{^[[:space:]./:]*$}.match?(extra_delimiters)
726
- raise ::ArgumentError, "Illegal delimiters in #{extra_delimiters.inspect}"
727
- end
728
- chars = ::Regexp.escape(extra_delimiters.chars.uniq.join)
729
- @delimiters = ::Regexp.new("[[:space:]#{chars}]")
730
- end
731
-
732
- def split_path(str)
733
- str.split(@delimiters)
734
- end
735
-
736
- def find_orig_prefix(args)
737
- first_split = (args.first || "").split(@delimiters)
738
- if first_split.size > 1
739
- args = first_split + args.slice(1..-1)
740
- return [first_split, args]
741
- end
742
- orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
743
- [orig_prefix, args]
744
- end
745
- end
746
760
  end
747
761
  end
@@ -140,7 +140,9 @@ module Toys
140
140
  end
141
141
  end
142
142
 
143
- ## @private
143
+ ##
144
+ # @private
145
+ #
144
146
  def spec_from_array(array)
145
147
  middleware = array.first
146
148
  if !middleware.is_a?(::String) && !middleware.is_a?(::Symbol) && !middleware.is_a?(::Class)
@@ -238,16 +240,12 @@ module Toys
238
240
  #
239
241
  attr_reader :block
240
242
 
241
- ## @private
242
- def initialize(object, name, args, kwargs, block)
243
- @object = object
244
- @name = name
245
- @args = args
246
- @kwargs = kwargs
247
- @block = block
248
- end
249
-
250
- ## @private
243
+ ##
244
+ # Equality check
245
+ #
246
+ # @param other [Object]
247
+ # @return [Boolean]
248
+ #
251
249
  def ==(other)
252
250
  other.is_a?(Spec) &&
253
251
  object.eql?(other.object) &&
@@ -258,10 +256,25 @@ module Toys
258
256
  end
259
257
  alias eql? ==
260
258
 
261
- ## @private
259
+ ##
260
+ # Return the hash code
261
+ #
262
+ # @return [Integer]
263
+ #
262
264
  def hash
263
265
  [object, name, args, kwargs, block].hash
264
266
  end
267
+
268
+ ##
269
+ # @private
270
+ #
271
+ def initialize(object, name, args, kwargs, block)
272
+ @object = object
273
+ @name = name
274
+ @args = args
275
+ @kwargs = kwargs
276
+ @block = block
277
+ end
265
278
  end
266
279
 
267
280
  ##
@@ -319,14 +332,12 @@ module Toys
319
332
  (@pre_specs + @default_specs + @post_specs).map { |spec| spec.build(middleware_lookup) }
320
333
  end
321
334
 
322
- ## @private
323
- def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
324
- @pre_specs = pre_specs || []
325
- @post_specs = post_specs || []
326
- @default_specs = default_specs || []
327
- end
328
-
329
- ## @private
335
+ ##
336
+ # Equality check
337
+ #
338
+ # @param other [Object]
339
+ # @return [Boolean]
340
+ #
330
341
  def ==(other)
331
342
  other.is_a?(Stack) &&
332
343
  pre_specs.eql?(other.pre_specs) &&
@@ -335,10 +346,23 @@ module Toys
335
346
  end
336
347
  alias eql? ==
337
348
 
338
- ## @private
349
+ ##
350
+ # Return the hash code
351
+ #
352
+ # @return [Integer]
353
+ #
339
354
  def hash
340
355
  [@pre_specs, @default_specs, @post_specs].hash
341
356
  end
357
+
358
+ ##
359
+ # @private
360
+ #
361
+ def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
362
+ @pre_specs = pre_specs || []
363
+ @post_specs = post_specs || []
364
+ @default_specs = default_specs || []
365
+ end
342
366
  end
343
367
  end
344
368
  end
data/lib/toys/mixin.rb CHANGED
@@ -92,12 +92,6 @@ module Toys
92
92
  mixin_mod
93
93
  end
94
94
 
95
- ## @private
96
- def self.included(mod)
97
- return if mod.respond_to?(:on_initialize)
98
- mod.extend(ModuleMethods)
99
- end
100
-
101
95
  ##
102
96
  # Methods that will be added to a mixin module object.
103
97
  #
@@ -148,5 +142,13 @@ module Toys
148
142
  #
149
143
  attr_accessor :inclusion
150
144
  end
145
+
146
+ ##
147
+ # @private
148
+ #
149
+ def self.included(mod)
150
+ return if mod.respond_to?(:on_initialize)
151
+ mod.extend(ModuleMethods)
152
+ end
151
153
  end
152
154
  end
@@ -5,23 +5,6 @@ module Toys
5
5
  # Representation of a formal positional argument
6
6
  #
7
7
  class PositionalArg
8
- ##
9
- # Create a PositionalArg definition.
10
- # This argument list is subject to change. Use {Toys::PositionalArg.create}
11
- # instead for a more stable interface.
12
- # @private
13
- #
14
- def initialize(key, type, acceptor, default, completion, desc, long_desc, display_name)
15
- @key = key
16
- @type = type
17
- @acceptor = Acceptor.create(acceptor)
18
- @default = default
19
- @completion = Completion.create(completion, **{})
20
- @desc = WrappableString.make(desc)
21
- @long_desc = WrappableString.make_array(long_desc)
22
- @display_name = display_name || key.to_s.tr("-", "_").gsub(/\W/, "").upcase
23
- end
24
-
25
8
  ##
26
9
  # Create a PositionalArg definition.
27
10
  #
@@ -159,5 +142,23 @@ module Toys
159
142
  @long_desc.concat(WrappableString.make_array(long_desc))
160
143
  self
161
144
  end
145
+
146
+ ##
147
+ # Create a PositionalArg definition.
148
+ # This argument list is subject to change. Use {Toys::PositionalArg.create}
149
+ # instead for a more stable interface.
150
+ #
151
+ # @private
152
+ #
153
+ def initialize(key, type, acceptor, default, completion, desc, long_desc, display_name)
154
+ @key = key
155
+ @type = type
156
+ @acceptor = Acceptor.create(acceptor)
157
+ @default = default
158
+ @completion = Completion.create(completion, **{})
159
+ @desc = WrappableString.make(desc)
160
+ @long_desc = WrappableString.make_array(long_desc)
161
+ @display_name = display_name || key.to_s.tr("-", "_").gsub(/\W/, "").upcase
162
+ end
162
163
  end
163
164
  end