toys-core 0.12.2 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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