toys-core 0.11.5 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +5 -2
  5. data/docs/guide.md +1 -1
  6. data/lib/toys/acceptor.rb +13 -4
  7. data/lib/toys/arg_parser.rb +7 -7
  8. data/lib/toys/cli.rb +170 -120
  9. data/lib/toys/compat.rb +71 -23
  10. data/lib/toys/completion.rb +18 -6
  11. data/lib/toys/context.rb +24 -15
  12. data/lib/toys/core.rb +6 -2
  13. data/lib/toys/dsl/base.rb +87 -0
  14. data/lib/toys/dsl/flag.rb +26 -20
  15. data/lib/toys/dsl/flag_group.rb +18 -14
  16. data/lib/toys/dsl/internal.rb +206 -0
  17. data/lib/toys/dsl/positional_arg.rb +26 -16
  18. data/lib/toys/dsl/tool.rb +180 -218
  19. data/lib/toys/errors.rb +64 -8
  20. data/lib/toys/flag.rb +662 -656
  21. data/lib/toys/flag_group.rb +24 -10
  22. data/lib/toys/input_file.rb +13 -7
  23. data/lib/toys/loader.rb +293 -140
  24. data/lib/toys/middleware.rb +46 -22
  25. data/lib/toys/mixin.rb +10 -8
  26. data/lib/toys/positional_arg.rb +21 -20
  27. data/lib/toys/settings.rb +914 -0
  28. data/lib/toys/source_info.rb +147 -35
  29. data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
  30. data/lib/toys/standard_middleware/apply_config.rb +6 -4
  31. data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
  32. data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
  33. data/lib/toys/standard_middleware/show_help.rb +19 -5
  34. data/lib/toys/standard_middleware/show_root_version.rb +2 -0
  35. data/lib/toys/standard_mixins/bundler.rb +24 -15
  36. data/lib/toys/standard_mixins/exec.rb +43 -34
  37. data/lib/toys/standard_mixins/fileutils.rb +3 -1
  38. data/lib/toys/standard_mixins/gems.rb +21 -17
  39. data/lib/toys/standard_mixins/git_cache.rb +46 -0
  40. data/lib/toys/standard_mixins/highline.rb +8 -8
  41. data/lib/toys/standard_mixins/terminal.rb +5 -5
  42. data/lib/toys/standard_mixins/xdg.rb +56 -0
  43. data/lib/toys/template.rb +11 -9
  44. data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
  45. data/lib/toys/utils/completion_engine.rb +7 -2
  46. data/lib/toys/utils/exec.rb +162 -132
  47. data/lib/toys/utils/gems.rb +85 -60
  48. data/lib/toys/utils/git_cache.rb +813 -0
  49. data/lib/toys/utils/help_text.rb +117 -37
  50. data/lib/toys/utils/terminal.rb +11 -3
  51. data/lib/toys/utils/xdg.rb +293 -0
  52. data/lib/toys/wrappable_string.rb +9 -2
  53. data/lib/toys-core.rb +18 -6
  54. metadata +14 -7
data/lib/toys/dsl/tool.rb CHANGED
@@ -10,7 +10,7 @@ module Toys
10
10
  # how to execute the tool, and requesting mixin modules and other services.
11
11
  # It also lets you define subtools, nested arbitrarily deep, using blocks.
12
12
  #
13
- # ## Simple example
13
+ # ### Simple example
14
14
  #
15
15
  # Create a file called `.toys.rb` in the current directory, with the
16
16
  # following contents:
@@ -34,11 +34,6 @@ module Toys
34
34
  # toys greet rubyists
35
35
  #
36
36
  module Tool
37
- ## @private
38
- def method_added(_meth)
39
- DSL::Tool.current_tool(self, true)&.check_definition_state(is_method: true)
40
- end
41
-
42
37
  ##
43
38
  # Create a named acceptor that can be referenced by name from any flag or
44
39
  # positional argument in this tool or its subtools.
@@ -83,7 +78,7 @@ module Toys
83
78
  # an exception (descended from `StandardError`) to indicate that the
84
79
  # string parameter is invalid.
85
80
  #
86
- # ## Example
81
+ # ### Example
87
82
  #
88
83
  # The following example creates an acceptor named "hex" that is defined
89
84
  # via a regular expression. It then uses it to validate values passed to
@@ -105,7 +100,7 @@ module Toys
105
100
  # @return [self]
106
101
  #
107
102
  def acceptor(name, spec = nil, type_desc: nil, &block)
108
- cur_tool = DSL::Tool.current_tool(self, false)
103
+ cur_tool = DSL::Internal.current_tool(self, false)
109
104
  cur_tool&.add_acceptor(name, spec, type_desc: type_desc || name.to_s, &block)
110
105
  self
111
106
  end
@@ -122,7 +117,7 @@ module Toys
122
117
  # block. Alternatively, you can create a module separately and pass it
123
118
  # directly to this directive.
124
119
  #
125
- # ## Example
120
+ # ### Example
126
121
  #
127
122
  # The following example creates a named mixin and uses it in a tool.
128
123
  #
@@ -149,7 +144,7 @@ module Toys
149
144
  # @return [self]
150
145
  #
151
146
  def mixin(name, mixin_module = nil, &block)
152
- cur_tool = DSL::Tool.current_tool(self, false)
147
+ cur_tool = DSL::Internal.current_tool(self, false)
153
148
  cur_tool&.add_mixin(name, mixin_module, &block)
154
149
  self
155
150
  end
@@ -175,7 +170,7 @@ module Toys
175
170
  # directly. See {Toys::Template} for details on creating a template
176
171
  # class.
177
172
  #
178
- # ## Example
173
+ # ### Example
179
174
  #
180
175
  # The following example creates and uses a simple template.
181
176
  #
@@ -204,7 +199,7 @@ module Toys
204
199
  # @return [self]
205
200
  #
206
201
  def template(name, template_class = nil, &block)
207
- cur_tool = DSL::Tool.current_tool(self, false)
202
+ cur_tool = DSL::Internal.current_tool(self, false)
208
203
  return self if cur_tool.nil?
209
204
  cur_tool.add_template(name, template_class, &block)
210
205
  self
@@ -228,7 +223,7 @@ module Toys
228
223
  # * The symbol `:file_system` which indicates that paths in the file
229
224
  # system should serve as completion candidates.
230
225
  #
231
- # ## Example
226
+ # ### Example
232
227
  #
233
228
  # The following example defines a completion that uses only the immediate
234
229
  # files in the current directory as candidates. (This is different from
@@ -252,7 +247,7 @@ module Toys
252
247
  # @return [self]
253
248
  #
254
249
  def completion(name, spec = nil, **options, &block)
255
- cur_tool = DSL::Tool.current_tool(self, false)
250
+ cur_tool = DSL::Internal.current_tool(self, false)
256
251
  return self if cur_tool.nil?
257
252
  cur_tool.add_completion(name, spec, **options, &block)
258
253
  self
@@ -261,7 +256,7 @@ module Toys
261
256
  ##
262
257
  # Create a subtool. You must provide a block defining the subtool.
263
258
  #
264
- # ## Example
259
+ # ### Example
265
260
  #
266
261
  # The following example defines a tool and two subtools within it.
267
262
  #
@@ -280,7 +275,7 @@ module Toys
280
275
  #
281
276
  # The following example defines a tool that runs one of its subtools.
282
277
  #
283
- # tool "test", runs: ["test", "unit"] do
278
+ # tool "test", delegate_to: ["test", "unit"] do
284
279
  # tool "unit" do
285
280
  # def run
286
281
  # puts "Running unit tests"
@@ -316,7 +311,7 @@ module Toys
316
311
  when :ignore
317
312
  return self
318
313
  when :reset
319
- subtool.reset_definition(@__loader)
314
+ subtool.reset_definition
320
315
  end
321
316
  end
322
317
  if delegate_to
@@ -328,7 +323,6 @@ module Toys
328
323
  end
329
324
  self
330
325
  end
331
- alias name tool
332
326
 
333
327
  ##
334
328
  # Create an alias, representing an "alternate name" for a tool.
@@ -337,7 +331,7 @@ module Toys
337
331
  # `delegate_to` option, except that `alias_tool` takes a _relative_ name
338
332
  # for the delegate.
339
333
  #
340
- # ## Example
334
+ # ### Example
341
335
  #
342
336
  # This example defines a tool and an alias pointing to it. Both the tool
343
337
  # name `test` and the alias `t` will then refer to the same tool.
@@ -364,7 +358,7 @@ module Toys
364
358
  # Causes the current tool to delegate to another tool. When run, it
365
359
  # simply invokes the target tool with the same arguments.
366
360
  #
367
- # ## Example
361
+ # ### Example
368
362
  #
369
363
  # This example defines a tool that runs one of its subtools. Running the
370
364
  # `test` tool will have the same effect (and recognize the same args) as
@@ -386,7 +380,7 @@ module Toys
386
380
  # @return [self]
387
381
  #
388
382
  def delegate_to(target)
389
- cur_tool = DSL::Tool.current_tool(self, true)
383
+ cur_tool = DSL::Internal.current_tool(self, true)
390
384
  return self if cur_tool.nil?
391
385
  cur_tool.delegate_to(@__loader.split_path(target))
392
386
  self
@@ -397,20 +391,64 @@ module Toys
397
391
  # at the current location.
398
392
  #
399
393
  # @param path [String] The file or directory to load.
394
+ # @param as [String] Load into the given tool/namespace. If omitted,
395
+ # configuration will be loaded into the current namespace.
396
+ #
400
397
  # @return [self]
401
398
  #
402
- def load(path)
399
+ def load(path, as: nil)
400
+ if as
401
+ tool(as) do
402
+ load(path)
403
+ end
404
+ return self
405
+ end
403
406
  @__loader.load_path(source_info, path, @__words, @__remaining_words, @__priority)
404
407
  self
405
408
  end
406
409
 
410
+ ##
411
+ # Load configuration from a public git repository, as if its contents
412
+ # were inserted at the current location.
413
+ #
414
+ # @param remote [String] The URL of the git repository. Defaults to the
415
+ # current repository if already loading from git.
416
+ # @param path [String] The path within the repo to the file or directory
417
+ # to load. Defaults to the root of the repo.
418
+ # @param commit [String] The commit branch, tag, or sha. Defaults to the
419
+ # current commit if already loading from git, or to `HEAD`.
420
+ # @param as [String] Load into the given tool/namespace. If omitted,
421
+ # configuration will be loaded into the current namespace.
422
+ # @param update [Boolean] Force-fetch from the remote (unless the commit
423
+ # is a SHA). This will ensure that symbolic commits, such as branch
424
+ # names, are up to date. Default is false.
425
+ #
426
+ # @return [self]
427
+ #
428
+ def load_git(remote: nil, path: nil, commit: nil, as: nil, update: false)
429
+ if as
430
+ tool(as) do
431
+ load_git(remote: remote, path: path, commit: commit)
432
+ end
433
+ return self
434
+ end
435
+ remote ||= source_info.git_remote
436
+ raise ToolDefinitionError, "Git remote not specified" unless remote
437
+ path ||= ""
438
+ commit ||= source_info.git_commit || "HEAD"
439
+ @__loader.load_git(source_info, remote, path, commit,
440
+ @__words, @__remaining_words, @__priority,
441
+ update: update)
442
+ self
443
+ end
444
+
407
445
  ##
408
446
  # Expand the given template in the current location.
409
447
  #
410
448
  # The template may be specified as a class or a well-known template name.
411
449
  # You may also provide arguments to pass to the template.
412
450
  #
413
- # ## Example
451
+ # ### Example
414
452
  #
415
453
  # The following example creates and uses a simple template.
416
454
  #
@@ -437,12 +475,13 @@ module Toys
437
475
  # @return [self]
438
476
  #
439
477
  def expand(template_class, *args, **kwargs)
440
- cur_tool = DSL::Tool.current_tool(self, false)
478
+ cur_tool = DSL::Internal.current_tool(self, false)
441
479
  return self if cur_tool.nil?
442
480
  name = template_class.to_s
443
- if template_class.is_a?(::String)
481
+ case template_class
482
+ when ::String
444
483
  template_class = cur_tool.lookup_template(template_class)
445
- elsif template_class.is_a?(::Symbol)
484
+ when ::Symbol
446
485
  template_class = @__loader.resolve_standard_template(name)
447
486
  end
448
487
  if template_class.nil?
@@ -472,7 +511,7 @@ module Toys
472
511
  # across the strings in the array. In this case, whitespace is not
473
512
  # compacted.
474
513
  #
475
- # ## Examples
514
+ # ### Examples
476
515
  #
477
516
  # If you pass in a sentence as a simple string, it may be word wrapped
478
517
  # when displayed:
@@ -488,7 +527,7 @@ module Toys
488
527
  # @return [self]
489
528
  #
490
529
  def desc(str)
491
- cur_tool = DSL::Tool.current_tool(self, true)
530
+ cur_tool = DSL::Internal.current_tool(self, true)
492
531
  return self if cur_tool.nil?
493
532
  cur_tool.desc = str
494
533
  self
@@ -506,7 +545,7 @@ module Toys
506
545
  # word-wrapped when displayed. To insert a blank line, include an empty
507
546
  # string as one of the descriptions.
508
547
  #
509
- # ## Example
548
+ # ### Example
510
549
  #
511
550
  # long_desc "This initial paragraph might get word wrapped.",
512
551
  # "This next paragraph is followed by a blank line.",
@@ -524,7 +563,7 @@ module Toys
524
563
  # @return [self]
525
564
  #
526
565
  def long_desc(*strs, file: nil, data: nil)
527
- cur_tool = DSL::Tool.current_tool(self, true)
566
+ cur_tool = DSL::Internal.current_tool(self, true)
528
567
  return self if cur_tool.nil?
529
568
  if file
530
569
  unless source_info.source_path
@@ -535,7 +574,7 @@ module Toys
535
574
  elsif data
536
575
  file = source_info.find_data(data, type: :file)
537
576
  end
538
- strs += DSL::Tool.load_long_desc_file(file) if file
577
+ strs += DSL::Internal.load_long_desc_file(file) if file
539
578
  cur_tool.append_long_desc(strs)
540
579
  self
541
580
  end
@@ -545,7 +584,7 @@ module Toys
545
584
  # belong to the group. The flags in the group are listed together in
546
585
  # help screens.
547
586
  #
548
- # ## Example
587
+ # ### Example
549
588
  #
550
589
  # The following example creates a flag group in which all flags are
551
590
  # optional.
@@ -562,11 +601,11 @@ module Toys
562
601
  # `:optional`, `:exactly_one`, `:at_most_one`, `:at_least_one`.
563
602
  # Default is `:optional`.
564
603
  # @param desc [String,Array<String>,Toys::WrappableString] Short
565
- # description for the group. See {Toys::Tool#desc=} for a description
566
- # of allowed formats. Defaults to `"Flags"`.
604
+ # description for the group. See {Toys::DSL::Tool#desc} for a
605
+ # description of allowed formats. Defaults to `"Flags"`.
567
606
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
568
607
  # Long description for the flag group. See
569
- # {Toys::Tool#long_desc=} for a description of allowed formats.
608
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
570
609
  # Defaults to the empty array.
571
610
  # @param name [String,Symbol,nil] The name of the group, or nil for no
572
611
  # name.
@@ -581,7 +620,7 @@ module Toys
581
620
  #
582
621
  def flag_group(type: :optional, desc: nil, long_desc: nil, name: nil,
583
622
  report_collisions: true, prepend: false, &block)
584
- cur_tool = DSL::Tool.current_tool(self, true)
623
+ cur_tool = DSL::Internal.current_tool(self, true)
585
624
  return self if cur_tool.nil?
586
625
  cur_tool.add_flag_group(type: type, desc: desc, long_desc: long_desc, name: name,
587
626
  report_collisions: report_collisions, prepend: prepend)
@@ -596,7 +635,7 @@ module Toys
596
635
  # defined in the block belong to the group. All flags in this group are
597
636
  # required.
598
637
  #
599
- # ## Example
638
+ # ### Example
600
639
  #
601
640
  # The following example creates a group of required flags.
602
641
  #
@@ -609,11 +648,11 @@ module Toys
609
648
  # end
610
649
  #
611
650
  # @param desc [String,Array<String>,Toys::WrappableString] Short
612
- # description for the group. See {Toys::Tool#desc=} for a description
613
- # of allowed formats. Defaults to `"Flags"`.
651
+ # description for the group. See {Toys::DSL::Tool#desc} for a
652
+ # description of allowed formats. Defaults to `"Flags"`.
614
653
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
615
654
  # Long description for the flag group. See
616
- # {Toys::Tool#long_desc=} for a description of allowed formats.
655
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
617
656
  # Defaults to the empty array.
618
657
  # @param name [String,Symbol,nil] The name of the group, or nil for no
619
658
  # name.
@@ -637,7 +676,7 @@ module Toys
637
676
  # defined in the block belong to the group. At most one flag in this
638
677
  # group must be provided on the command line.
639
678
  #
640
- # ## Example
679
+ # ### Example
641
680
  #
642
681
  # The following example creates a group of flags in which either one or
643
682
  # none may be set, but not more than one.
@@ -652,11 +691,11 @@ module Toys
652
691
  # end
653
692
  #
654
693
  # @param desc [String,Array<String>,Toys::WrappableString] Short
655
- # description for the group. See {Toys::Tool#desc=} for a description
656
- # of allowed formats. Defaults to `"Flags"`.
694
+ # description for the group. See {Toys::DSL::Tool#desc} for a
695
+ # description of allowed formats. Defaults to `"Flags"`.
657
696
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
658
697
  # Long description for the flag group. See
659
- # {Toys::Tool#long_desc=} for a description of allowed formats.
698
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
660
699
  # Defaults to the empty array.
661
700
  # @param name [String,Symbol,nil] The name of the group, or nil for no
662
701
  # name.
@@ -681,7 +720,7 @@ module Toys
681
720
  # defined in the block belong to the group. At least one flag in this
682
721
  # group must be provided on the command line.
683
722
  #
684
- # ## Example
723
+ # ### Example
685
724
  #
686
725
  # The following example creates a group of flags in which one or more
687
726
  # may be set.
@@ -696,11 +735,11 @@ module Toys
696
735
  # end
697
736
  #
698
737
  # @param desc [String,Array<String>,Toys::WrappableString] Short
699
- # description for the group. See {Toys::Tool#desc=} for a description
700
- # of allowed formats. Defaults to `"Flags"`.
738
+ # description for the group. See {Toys::DSL::Tool#desc} for a
739
+ # description of allowed formats. Defaults to `"Flags"`.
701
740
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
702
741
  # Long description for the flag group. See
703
- # {Toys::Tool#long_desc=} for a description of allowed formats.
742
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
704
743
  # Defaults to the empty array.
705
744
  # @param name [String,Symbol,nil] The name of the group, or nil for no
706
745
  # name.
@@ -725,7 +764,7 @@ module Toys
725
764
  # defined in the block belong to the group. Exactly one flag in this
726
765
  # group must be provided on the command line.
727
766
  #
728
- # ## Example
767
+ # ### Example
729
768
  #
730
769
  # The following example creates a group of flags in which exactly one
731
770
  # must be set.
@@ -740,11 +779,11 @@ module Toys
740
779
  # end
741
780
  #
742
781
  # @param desc [String,Array<String>,Toys::WrappableString] Short
743
- # description for the group. See {Toys::Tool#desc=} for a description
744
- # of allowed formats. Defaults to `"Flags"`.
782
+ # description for the group. See {Toys::DSL::Tool#desc} for a
783
+ # description of allowed formats. Defaults to `"Flags"`.
745
784
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
746
785
  # Long description for the flag group. See
747
- # {Toys::Tool#long_desc=} for a description of allowed formats.
786
+ # {Toys::DSL::Tool#long_desc} for a description of allowed formats.
748
787
  # Defaults to the empty array.
749
788
  # @param name [String,Symbol,nil] The name of the group, or nil for no
750
789
  # name.
@@ -778,7 +817,7 @@ module Toys
778
817
  # set in a block passed to this method. If you provide a block, you can
779
818
  # use directives in {Toys::DSL::Flag} within the block.
780
819
  #
781
- # ## Flag syntax
820
+ # ### Flag syntax
782
821
  #
783
822
  # The flags themselves should be provided in OptionParser form. Following
784
823
  # are examples of valid syntax.
@@ -833,7 +872,7 @@ module Toys
833
872
  # or off. This effectively creates two flags, `--abc` which sets the
834
873
  # value to `true`, and `--no-abc` which sets the falue to `false`.
835
874
  #
836
- # ## Default flag syntax
875
+ # ### Default flag syntax
837
876
  #
838
877
  # If no flag syntax strings are provided, a default syntax will be
839
878
  # inferred based on the key and other options.
@@ -858,7 +897,7 @@ module Toys
858
897
  # flag :number, accept: Integer
859
898
  # flag :number, "--number=VAL", accept: Integer
860
899
  #
861
- # ## More examples
900
+ # ### More examples
862
901
  #
863
902
  # A flag that sets its value to the number of times it appears on the
864
903
  # command line:
@@ -937,7 +976,7 @@ module Toys
937
976
  report_collisions: true, group: nil,
938
977
  desc: nil, long_desc: nil, display_name: nil,
939
978
  &block)
940
- cur_tool = DSL::Tool.current_tool(self, true)
979
+ cur_tool = DSL::Internal.current_tool(self, true)
941
980
  return self if cur_tool.nil?
942
981
  flag_dsl = DSL::Flag.new(
943
982
  flags.flatten, accept, default, handler, complete_flags, complete_values,
@@ -945,14 +984,14 @@ module Toys
945
984
  )
946
985
  flag_dsl.instance_exec(flag_dsl, &block) if block
947
986
  flag_dsl._add_to(cur_tool, key)
948
- DSL::Tool.maybe_add_getter(self, key)
987
+ DSL::Internal.maybe_add_getter(self, key)
949
988
  self
950
989
  end
951
990
 
952
991
  ##
953
- # Add a required positional argument to the current tool. You must specify
954
- # a key which the script may use to obtain the argument value from the
955
- # context.
992
+ # Add a required positional argument to the current tool. You must
993
+ # specify a key which the script may use to obtain the argument value
994
+ # from the context.
956
995
  #
957
996
  # If the given key is a symbol representing a valid method name, then a
958
997
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -963,7 +1002,7 @@ module Toys
963
1002
  # set in a block passed to this method. If you provide a block, you can
964
1003
  # use directives in {Toys::DSL::PositionalArg} within the block.
965
1004
  #
966
- # ## Example
1005
+ # ### Example
967
1006
  #
968
1007
  # This tool "moves" something from a source to destination, and takes two
969
1008
  # required arguments:
@@ -987,8 +1026,8 @@ module Toys
987
1026
  # values of this arg. This is the empty completion by default. To
988
1027
  # customize completion, set this to the name of a previously defined
989
1028
  # completion, or any spec recognized by {Toys::Completion.create}.
990
- # @param display_name [String] A name to use for display (in help text and
991
- # error reports). Defaults to the key in upper case.
1029
+ # @param display_name [String] A name to use for display (in help text
1030
+ # and error reports). Defaults to the key in upper case.
992
1031
  # @param desc [String,Array<String>,Toys::WrappableString] Short
993
1032
  # description for the flag. See {Toys::DSL::Tool#desc} for a
994
1033
  # description of the allowed formats. Defaults to the empty string.
@@ -1006,21 +1045,21 @@ module Toys
1006
1045
  accept: nil, complete: nil, display_name: nil,
1007
1046
  desc: nil, long_desc: nil,
1008
1047
  &block)
1009
- cur_tool = DSL::Tool.current_tool(self, true)
1048
+ cur_tool = DSL::Internal.current_tool(self, true)
1010
1049
  return self if cur_tool.nil?
1011
1050
  arg_dsl = DSL::PositionalArg.new(accept, nil, complete, display_name, desc, long_desc)
1012
1051
  arg_dsl.instance_exec(arg_dsl, &block) if block
1013
1052
  arg_dsl._add_required_to(cur_tool, key)
1014
- DSL::Tool.maybe_add_getter(self, key)
1053
+ DSL::Internal.maybe_add_getter(self, key)
1015
1054
  self
1016
1055
  end
1017
1056
  alias required required_arg
1018
1057
 
1019
1058
  ##
1020
- # Add an optional positional argument to the current tool. You must specify
1021
- # a key which the script may use to obtain the argument value from the
1022
- # context. If an optional argument is not given on the command line, the
1023
- # value is set to the given default.
1059
+ # Add an optional positional argument to the current tool. You must
1060
+ # specify a key which the script may use to obtain the argument value
1061
+ # from the context. If an optional argument is not given on the command
1062
+ # line, the value is set to the given default.
1024
1063
  #
1025
1064
  # If the given key is a symbol representing a valid method name, then a
1026
1065
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -1031,7 +1070,7 @@ module Toys
1031
1070
  # set in a block passed to this method. If you provide a block, you can
1032
1071
  # use directives in {Toys::DSL::PositionalArg} within the block.
1033
1072
  #
1034
- # ## Example
1073
+ # ### Example
1035
1074
  #
1036
1075
  # This tool creates a "link" to a given target. The link location is
1037
1076
  # optional; if it is not given, it is inferred from the target.
@@ -1048,8 +1087,8 @@ module Toys
1048
1087
  # @param key [String,Symbol] The key to use to retrieve the value from
1049
1088
  # the execution context.
1050
1089
  # @param default [Object] The default value. This is the value that will
1051
- # be set in the context if this argument is not provided on the command
1052
- # line. Defaults to `nil`.
1090
+ # be set in the context if this argument is not provided on the
1091
+ # command line. Defaults to `nil`.
1053
1092
  # @param accept [Object] An acceptor that validates and/or converts the
1054
1093
  # value. You may provide either the name of an acceptor you have
1055
1094
  # defined, one of the default acceptors provided by OptionParser, or
@@ -1059,8 +1098,8 @@ module Toys
1059
1098
  # values of this arg. This is the empty completion by default. To
1060
1099
  # customize completion, set this to the name of a previously defined
1061
1100
  # completion, or any spec recognized by {Toys::Completion.create}.
1062
- # @param display_name [String] A name to use for display (in help text and
1063
- # error reports). Defaults to the key in upper case.
1101
+ # @param display_name [String] A name to use for display (in help text
1102
+ # and error reports). Defaults to the key in upper case.
1064
1103
  # @param desc [String,Array<String>,Toys::WrappableString] Short
1065
1104
  # description for the flag. See {Toys::DSL::Tool#desc} for a
1066
1105
  # description of the allowed formats. Defaults to the empty string.
@@ -1078,20 +1117,20 @@ module Toys
1078
1117
  default: nil, accept: nil, complete: nil, display_name: nil,
1079
1118
  desc: nil, long_desc: nil,
1080
1119
  &block)
1081
- cur_tool = DSL::Tool.current_tool(self, true)
1120
+ cur_tool = DSL::Internal.current_tool(self, true)
1082
1121
  return self if cur_tool.nil?
1083
1122
  arg_dsl = DSL::PositionalArg.new(accept, default, complete, display_name, desc, long_desc)
1084
1123
  arg_dsl.instance_exec(arg_dsl, &block) if block
1085
1124
  arg_dsl._add_optional_to(cur_tool, key)
1086
- DSL::Tool.maybe_add_getter(self, key)
1125
+ DSL::Internal.maybe_add_getter(self, key)
1087
1126
  self
1088
1127
  end
1089
1128
  alias optional optional_arg
1090
1129
 
1091
1130
  ##
1092
- # Specify what should be done with unmatched positional arguments. You must
1093
- # specify a key which the script may use to obtain the remaining args from
1094
- # the context.
1131
+ # Specify what should be done with unmatched positional arguments. You
1132
+ # must specify a key which the script may use to obtain the remaining
1133
+ # args from the context.
1095
1134
  #
1096
1135
  # If the given key is a symbol representing a valid method name, then a
1097
1136
  # helper method is automatically added to retrieve the value. Otherwise,
@@ -1102,7 +1141,7 @@ module Toys
1102
1141
  # set in a block passed to this method. If you provide a block, you can
1103
1142
  # use directives in {Toys::DSL::PositionalArg} within the block.
1104
1143
  #
1105
- # ## Example
1144
+ # ### Example
1106
1145
  #
1107
1146
  # This tool displays a "list" of the given directories. If no directories
1108
1147
  # ar given, lists the current directory.
@@ -1131,8 +1170,8 @@ module Toys
1131
1170
  # values of this arg. This is the empty completion by default. To
1132
1171
  # customize completion, set this to the name of a previously defined
1133
1172
  # completion, or any spec recognized by {Toys::Completion.create}.
1134
- # @param display_name [String] A name to use for display (in help text and
1135
- # error reports). Defaults to the key in upper case.
1173
+ # @param display_name [String] A name to use for display (in help text
1174
+ # and error reports). Defaults to the key in upper case.
1136
1175
  # @param desc [String,Array<String>,Toys::WrappableString] Short
1137
1176
  # description for the flag. See {Toys::DSL::Tool#desc} for a
1138
1177
  # description of the allowed formats. Defaults to the empty string.
@@ -1150,12 +1189,12 @@ module Toys
1150
1189
  default: [], accept: nil, complete: nil, display_name: nil,
1151
1190
  desc: nil, long_desc: nil,
1152
1191
  &block)
1153
- cur_tool = DSL::Tool.current_tool(self, true)
1192
+ cur_tool = DSL::Internal.current_tool(self, true)
1154
1193
  return self if cur_tool.nil?
1155
1194
  arg_dsl = DSL::PositionalArg.new(accept, default, complete, display_name, desc, long_desc)
1156
1195
  arg_dsl.instance_exec(arg_dsl, &block) if block
1157
1196
  arg_dsl._set_remaining_on(cur_tool, key)
1158
- DSL::Tool.maybe_add_getter(self, key)
1197
+ DSL::Internal.maybe_add_getter(self, key)
1159
1198
  self
1160
1199
  end
1161
1200
  alias remaining remaining_args
@@ -1168,7 +1207,7 @@ module Toys
1168
1207
  # if the key is a string or does not represent a valid method name, the
1169
1208
  # tool can retrieve the value by calling {Toys::Context#get}.
1170
1209
  #
1171
- # ## Example
1210
+ # ### Example
1172
1211
  #
1173
1212
  # tool "hello" do
1174
1213
  # static :greeting, "Hi there"
@@ -1190,16 +1229,16 @@ module Toys
1190
1229
  # @return [self]
1191
1230
  #
1192
1231
  def static(key, value = nil)
1193
- cur_tool = DSL::Tool.current_tool(self, true)
1232
+ cur_tool = DSL::Internal.current_tool(self, true)
1194
1233
  return self if cur_tool.nil?
1195
1234
  if key.is_a?(::Hash)
1196
1235
  cur_tool.default_data.merge!(key)
1197
1236
  key.each_key do |k|
1198
- DSL::Tool.maybe_add_getter(self, k)
1237
+ DSL::Internal.maybe_add_getter(self, k)
1199
1238
  end
1200
1239
  else
1201
1240
  cur_tool.default_data[key] = value
1202
- DSL::Tool.maybe_add_getter(self, key)
1241
+ DSL::Internal.maybe_add_getter(self, key)
1203
1242
  end
1204
1243
  self
1205
1244
  end
@@ -1207,7 +1246,7 @@ module Toys
1207
1246
  ##
1208
1247
  # Set a option values statically without creating helper methods.
1209
1248
  #
1210
- # ## Example
1249
+ # ### Example
1211
1250
  #
1212
1251
  # tool "hello" do
1213
1252
  # set :greeting, "Hi there"
@@ -1229,7 +1268,7 @@ module Toys
1229
1268
  # @return [self]
1230
1269
  #
1231
1270
  def set(key, value = nil)
1232
- cur_tool = DSL::Tool.current_tool(self, true)
1271
+ cur_tool = DSL::Internal.current_tool(self, true)
1233
1272
  return self if cur_tool.nil?
1234
1273
  if key.is_a?(::Hash)
1235
1274
  cur_tool.default_data.merge!(key)
@@ -1251,7 +1290,7 @@ module Toys
1251
1290
  # @return [self]
1252
1291
  #
1253
1292
  def enforce_flags_before_args(state = true)
1254
- DSL::Tool.current_tool(self, true)&.enforce_flags_before_args(state)
1293
+ DSL::Internal.current_tool(self, true)&.enforce_flags_before_args(state)
1255
1294
  self
1256
1295
  end
1257
1296
 
@@ -1267,7 +1306,7 @@ module Toys
1267
1306
  # @return [self]
1268
1307
  #
1269
1308
  def require_exact_flag_match(state = true)
1270
- DSL::Tool.current_tool(self, true)&.require_exact_flag_match(state)
1309
+ DSL::Internal.current_tool(self, true)&.require_exact_flag_match(state)
1271
1310
  self
1272
1311
  end
1273
1312
 
@@ -1282,7 +1321,7 @@ module Toys
1282
1321
  # @return [self]
1283
1322
  #
1284
1323
  def disable_argument_parsing
1285
- DSL::Tool.current_tool(self, true)&.disable_argument_parsing
1324
+ DSL::Internal.current_tool(self, true)&.disable_argument_parsing
1286
1325
  self
1287
1326
  end
1288
1327
 
@@ -1291,7 +1330,7 @@ module Toys
1291
1330
  # subsequent flag definition. This can be used to prevent middleware from
1292
1331
  # defining a particular flag.
1293
1332
  #
1294
- # ## Example
1333
+ # ### Example
1295
1334
  #
1296
1335
  # This tool does not support the `-v` and `-q` short forms for the two
1297
1336
  # verbosity flags (although it still supports the long forms `--verbose`
@@ -1308,7 +1347,7 @@ module Toys
1308
1347
  # @return [self]
1309
1348
  #
1310
1349
  def disable_flag(*flags)
1311
- DSL::Tool.current_tool(self, true)&.disable_flag(*flags)
1350
+ DSL::Internal.current_tool(self, true)&.disable_flag(*flags)
1312
1351
  self
1313
1352
  end
1314
1353
 
@@ -1319,12 +1358,13 @@ module Toys
1319
1358
  # * The string name of a completion defined in this tool or any of its
1320
1359
  # its ancestors.
1321
1360
  # * A hash of options to pass to the constructor of
1322
- # {Toys::Tool::DefaultCompletion}.
1361
+ # {Toys::ToolDefinition::DefaultCompletion}.
1323
1362
  # * `nil` or `:default` to select the standard completion strategy
1324
- # (which is {Toys::Tool::DefaultCompletion} with no extra options).
1363
+ # (which is {Toys::ToolDefinition::DefaultCompletion} with no extra
1364
+ # options).
1325
1365
  # * Any other specification recognized by {Toys::Completion.create}.
1326
1366
  #
1327
- # ## Example
1367
+ # ### Example
1328
1368
  #
1329
1369
  # The namespace "foo" supports completion only of subtool names. It does
1330
1370
  # not complete the standard flags (like --help).
@@ -1345,7 +1385,7 @@ module Toys
1345
1385
  # @return [self]
1346
1386
  #
1347
1387
  def complete_tool_args(spec = nil, **options, &block)
1348
- cur_tool = DSL::Tool.current_tool(self, true)
1388
+ cur_tool = DSL::Internal.current_tool(self, true)
1349
1389
  return self if cur_tool.nil?
1350
1390
  cur_tool.completion = Completion.scalarize_spec(spec, options, block)
1351
1391
  self
@@ -1360,7 +1400,7 @@ module Toys
1360
1400
  # in the lexical scope. However, it is often more convenient to use
1361
1401
  # {#static} to set the value in the context.)
1362
1402
  #
1363
- # ## Example
1403
+ # ### Example
1364
1404
  #
1365
1405
  # tool "foo" do
1366
1406
  # cur_time = Time.new
@@ -1373,7 +1413,9 @@ module Toys
1373
1413
  # @return [self]
1374
1414
  #
1375
1415
  def to_run(&block)
1376
- define_method(:run, &block)
1416
+ cur_tool = DSL::Internal.current_tool(self, true)
1417
+ return self if cur_tool.nil?
1418
+ cur_tool.run_handler = block
1377
1419
  self
1378
1420
  end
1379
1421
  alias on_run to_run
@@ -1385,7 +1427,7 @@ module Toys
1385
1427
  # either case, the block or method should take one argument, the
1386
1428
  # Interrupt exception that was raised.
1387
1429
  #
1388
- # ## Example
1430
+ # ### Example
1389
1431
  #
1390
1432
  # tool "foo" do
1391
1433
  # def run
@@ -1402,7 +1444,7 @@ module Toys
1402
1444
  # @return [self]
1403
1445
  #
1404
1446
  def on_interrupt(handler = nil, &block)
1405
- cur_tool = DSL::Tool.current_tool(self, true)
1447
+ cur_tool = DSL::Internal.current_tool(self, true)
1406
1448
  return self if cur_tool.nil?
1407
1449
  cur_tool.interrupt_handler = handler || block
1408
1450
  self
@@ -1415,7 +1457,7 @@ module Toys
1415
1457
  # either case, the block or method should take one argument, the array of
1416
1458
  # usage errors reported.
1417
1459
  #
1418
- # ## Example
1460
+ # ### Example
1419
1461
  #
1420
1462
  # This tool runs even if a usage error is encountered. You can find info
1421
1463
  # on the errors from {Toys::Context::Key::USAGE_ERRORS},
@@ -1434,7 +1476,7 @@ module Toys
1434
1476
  # @return [self]
1435
1477
  #
1436
1478
  def on_usage_error(handler = nil, &block)
1437
- cur_tool = DSL::Tool.current_tool(self, true)
1479
+ cur_tool = DSL::Internal.current_tool(self, true)
1438
1480
  return self if cur_tool.nil?
1439
1481
  cur_tool.usage_error_handler = handler || block
1440
1482
  self
@@ -1448,7 +1490,7 @@ module Toys
1448
1490
  # have defined in this tool or one of its ancestors, or the symbol name
1449
1491
  # of a well-known mixin.
1450
1492
  #
1451
- # ## Example
1493
+ # ### Example
1452
1494
  #
1453
1495
  # Include the well-known mixin `:terminal` and perform some terminal
1454
1496
  # magic.
@@ -1463,28 +1505,16 @@ module Toys
1463
1505
  # end
1464
1506
  # end
1465
1507
  #
1466
- # @param mod [Module,Symbol,String] Module or module name.
1508
+ # @param mixin [Module,Symbol,String] Module or module name.
1467
1509
  # @param args [Object...] Arguments to pass to the initializer
1468
1510
  # @param kwargs [keywords] Keyword arguments to pass to the initializer
1469
1511
  # @return [self]
1470
1512
  #
1471
- def include(mod, *args, **kwargs)
1472
- cur_tool = DSL::Tool.current_tool(self, true)
1513
+ def include(mixin, *args, **kwargs)
1514
+ cur_tool = DSL::Internal.current_tool(self, true)
1473
1515
  return self if cur_tool.nil?
1474
- mod = DSL::Tool.resolve_mixin(mod, cur_tool, @__loader)
1475
- if included_modules.include?(mod)
1476
- raise ToolDefinitionError, "Mixin already included: #{mod.name}"
1477
- end
1478
- cur_tool.mark_includes_modules
1479
- super(mod)
1480
- if mod.respond_to?(:initializer)
1481
- callback = mod.initializer
1482
- cur_tool.add_initializer(callback, *args, **kwargs) if callback
1483
- end
1484
- if mod.respond_to?(:inclusion)
1485
- callback = mod.inclusion
1486
- class_exec(*args, **kwargs, &callback) if callback
1487
- end
1516
+ mod = DSL::Internal.resolve_mixin(mixin, cur_tool, @__loader)
1517
+ cur_tool.include_mixin(mod, *args, **kwargs)
1488
1518
  self
1489
1519
  end
1490
1520
 
@@ -1501,9 +1531,9 @@ module Toys
1501
1531
  # @return [nil] if the current tool is not active.
1502
1532
  #
1503
1533
  def include?(mod)
1504
- cur_tool = DSL::Tool.current_tool(self, false)
1534
+ cur_tool = DSL::Internal.current_tool(self, false)
1505
1535
  return if cur_tool.nil?
1506
- super(DSL::Tool.resolve_mixin(mod, cur_tool, @__loader))
1536
+ super(DSL::Internal.resolve_mixin(mod, cur_tool, @__loader))
1507
1537
  end
1508
1538
 
1509
1539
  ##
@@ -1523,7 +1553,7 @@ module Toys
1523
1553
  # in a directory called `.data` inside a Toys directory. This directive
1524
1554
  # locates a data file during tool definition.
1525
1555
  #
1526
- # ## Example
1556
+ # ### Example
1527
1557
  #
1528
1558
  # This tool reads its description from a text file in the `.data`
1529
1559
  # directory.
@@ -1557,17 +1587,17 @@ module Toys
1557
1587
  # @return [nil] if there is no context.
1558
1588
  #
1559
1589
  def context_directory
1560
- DSL::Tool.current_tool(self, false)&.context_directory || source_info.context_directory
1590
+ DSL::Internal.current_tool(self, false)&.context_directory || source_info.context_directory
1561
1591
  end
1562
1592
 
1563
1593
  ##
1564
- # Return the current tool object. This object can be queried to determine
1594
+ # Return the current tool config. This object can be queried to determine
1565
1595
  # such information as the name, but it should not be altered.
1566
1596
  #
1567
- # @return [Toys::Tool]
1597
+ # @return [Toys::ToolDefinition]
1568
1598
  #
1569
1599
  def current_tool
1570
- DSL::Tool.current_tool(self, false)
1600
+ DSL::Internal.current_tool(self, false)
1571
1601
  end
1572
1602
 
1573
1603
  ##
@@ -1577,7 +1607,7 @@ module Toys
1577
1607
  # @return [self]
1578
1608
  #
1579
1609
  def set_context_directory(dir) # rubocop:disable Naming/AccessorMethodName
1580
- cur_tool = DSL::Tool.current_tool(self, false)
1610
+ cur_tool = DSL::Internal.current_tool(self, false)
1581
1611
  return self if cur_tool.nil?
1582
1612
  cur_tool.custom_context_directory = dir
1583
1613
  self
@@ -1591,7 +1621,7 @@ module Toys
1591
1621
  # The block is applied only to subtools defined *after* the block
1592
1622
  # appears. Subtools defined before the block appears are not affected.
1593
1623
  #
1594
- # ## Example
1624
+ # ### Example
1595
1625
  #
1596
1626
  # It is common for tools to use the `:exec` mixin to invoke external
1597
1627
  # programs. This example automatically includes the exec mixin in all
@@ -1616,7 +1646,7 @@ module Toys
1616
1646
  # end
1617
1647
  #
1618
1648
  def subtool_apply(&block)
1619
- cur_tool = DSL::Tool.current_tool(self, false)
1649
+ cur_tool = DSL::Internal.current_tool(self, false)
1620
1650
  return self if cur_tool.nil?
1621
1651
  cur_tool.subtool_middleware_stack.add(:apply_config,
1622
1652
  parent_source: source_info, &block)
@@ -1640,6 +1670,15 @@ module Toys
1640
1670
  end
1641
1671
  end
1642
1672
 
1673
+ ##
1674
+ # Get the settings for this tool.
1675
+ #
1676
+ # @return [Toys::ToolDefinition::Settings] Tool-specific settings.
1677
+ #
1678
+ def settings
1679
+ DSL::Internal.current_tool(self, false)&.settings
1680
+ end
1681
+
1643
1682
  ##
1644
1683
  # Determines whether the current Toys version satisfies the given
1645
1684
  # requirements.
@@ -1673,91 +1712,14 @@ module Toys
1673
1712
  self
1674
1713
  end
1675
1714
 
1676
- ## @private
1677
- def self.new_class(words, priority, loader)
1678
- tool_class = ::Class.new(::Toys::Context)
1679
- tool_class.extend(DSL::Tool)
1680
- tool_class.instance_variable_set(:@__words, words)
1681
- tool_class.instance_variable_set(:@__priority, priority)
1682
- tool_class.instance_variable_set(:@__loader, loader)
1683
- tool_class.instance_variable_set(:@__remaining_words, nil)
1684
- tool_class.instance_variable_set(:@__source, [])
1685
- tool_class
1686
- end
1687
-
1688
- ## @private
1689
- def self.current_tool(tool_class, activate)
1690
- memoize_var = activate ? :@__active_tool : :@__cur_tool
1691
- if tool_class.instance_variable_defined?(memoize_var)
1692
- tool_class.instance_variable_get(memoize_var)
1693
- else
1694
- loader = tool_class.instance_variable_get(:@__loader)
1695
- words = tool_class.instance_variable_get(:@__words)
1696
- priority = tool_class.instance_variable_get(:@__priority)
1697
- cur_tool =
1698
- if activate
1699
- loader.activate_tool(words, priority)
1700
- else
1701
- loader.get_tool(words, priority)
1702
- end
1703
- if cur_tool && activate
1704
- source = tool_class.instance_variable_get(:@__source).last
1705
- cur_tool.lock_source(source)
1706
- end
1707
- tool_class.instance_variable_set(memoize_var, cur_tool)
1708
- end
1709
- end
1710
-
1711
- ## @private
1712
- def self.prepare(tool_class, remaining_words, source)
1713
- tool_class.instance_variable_set(:@__remaining_words, remaining_words)
1714
- tool_class.instance_variable_get(:@__source).push(source)
1715
- yield
1716
- ensure
1717
- tool_class.instance_variable_get(:@__source).pop
1718
- end
1719
-
1720
- ## @private
1721
- def self.maybe_add_getter(tool_class, key)
1722
- if key.is_a?(::Symbol) && key.to_s =~ /^[_a-zA-Z]\w*[!\?]?$/ && key != :run
1723
- unless tool_class.public_method_defined?(key)
1724
- tool_class.class_eval do
1725
- define_method(key) do
1726
- self[key]
1727
- end
1728
- end
1729
- end
1730
- end
1731
- end
1732
-
1733
- ## @private
1734
- def self.resolve_mixin(mod, cur_tool, loader)
1735
- name = mod.to_s
1736
- if mod.is_a?(::String)
1737
- mod = cur_tool.lookup_mixin(mod)
1738
- elsif mod.is_a?(::Symbol)
1739
- mod = loader.resolve_standard_mixin(name)
1740
- end
1741
- unless mod.is_a?(::Module)
1742
- raise ToolDefinitionError, "Module not found: #{name.inspect}"
1743
- end
1744
- mod
1745
- end
1746
-
1747
- ## @private
1748
- def self.load_long_desc_file(path)
1749
- if ::File.extname(path) == ".txt"
1750
- begin
1751
- ::File.readlines(path).map do |line|
1752
- line = line.chomp
1753
- line =~ /^\s/ ? [line] : line
1754
- end
1755
- rescue ::SystemCallError => e
1756
- raise Toys::ToolDefinitionError, e.to_s
1757
- end
1758
- else
1759
- raise Toys::ToolDefinitionError, "Cannot load long desc from file type: #{path}"
1760
- end
1715
+ ##
1716
+ # Notify the tool definition when a method is defined in this tool class.
1717
+ #
1718
+ # @private
1719
+ #
1720
+ def method_added(_meth)
1721
+ super
1722
+ DSL::Internal.current_tool(self, true)&.check_definition_state(is_method: true)
1761
1723
  end
1762
1724
  end
1763
1725
  end