toys-core 0.11.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +1 -1
  4. data/lib/toys-core.rb +4 -1
  5. data/lib/toys/acceptor.rb +3 -3
  6. data/lib/toys/arg_parser.rb +6 -7
  7. data/lib/toys/cli.rb +44 -14
  8. data/lib/toys/compat.rb +19 -22
  9. data/lib/toys/completion.rb +3 -1
  10. data/lib/toys/context.rb +2 -2
  11. data/lib/toys/core.rb +1 -1
  12. data/lib/toys/dsl/base.rb +85 -0
  13. data/lib/toys/dsl/flag.rb +3 -3
  14. data/lib/toys/dsl/flag_group.rb +7 -7
  15. data/lib/toys/dsl/internal.rb +206 -0
  16. data/lib/toys/dsl/positional_arg.rb +3 -3
  17. data/lib/toys/dsl/tool.rb +174 -216
  18. data/lib/toys/errors.rb +1 -0
  19. data/lib/toys/flag.rb +15 -18
  20. data/lib/toys/flag_group.rb +5 -4
  21. data/lib/toys/input_file.rb +4 -4
  22. data/lib/toys/loader.rb +189 -50
  23. data/lib/toys/middleware.rb +1 -1
  24. data/lib/toys/mixin.rb +2 -2
  25. data/lib/toys/positional_arg.rb +3 -3
  26. data/lib/toys/settings.rb +900 -0
  27. data/lib/toys/source_info.rb +121 -18
  28. data/lib/toys/standard_middleware/apply_config.rb +5 -4
  29. data/lib/toys/standard_middleware/set_default_descriptions.rb +18 -18
  30. data/lib/toys/standard_middleware/show_help.rb +17 -5
  31. data/lib/toys/standard_mixins/exec.rb +12 -14
  32. data/lib/toys/standard_mixins/git_cache.rb +48 -0
  33. data/lib/toys/standard_mixins/xdg.rb +56 -0
  34. data/lib/toys/template.rb +2 -2
  35. data/lib/toys/{tool.rb → tool_definition.rb} +100 -41
  36. data/lib/toys/utils/exec.rb +4 -5
  37. data/lib/toys/utils/gems.rb +8 -7
  38. data/lib/toys/utils/git_cache.rb +184 -0
  39. data/lib/toys/utils/help_text.rb +90 -34
  40. data/lib/toys/utils/terminal.rb +1 -1
  41. data/lib/toys/utils/xdg.rb +293 -0
  42. metadata +14 -7
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Toys
6
+ module StandardMixins
7
+ ##
8
+ # A mixin that provides a git cache.
9
+ #
10
+ # This mixin provides an instance of {Toys::Utils::GitCache}, providing
11
+ # cached access to files from a remote git repo.
12
+ #
13
+ # Example usage:
14
+ #
15
+ # include :git_cache
16
+ #
17
+ # def run
18
+ # # Pull and cache the HEAD commit from the Toys repo.
19
+ # dir = git_cache.find("https://github.com/dazuma/toys.git")
20
+ # # Display the contents of the readme file.
21
+ # puts File.read(File.join(dir, "README.md"))
22
+ # end
23
+ #
24
+ module GitCache
25
+ include Mixin
26
+
27
+ ##
28
+ # Context key for the GitCache object.
29
+ # @return [Object]
30
+ #
31
+ KEY = ::Object.new.freeze
32
+
33
+ on_initialize do
34
+ require "toys/utils/git_cache"
35
+ self[KEY] = Utils::GitCache.new
36
+ end
37
+
38
+ ##
39
+ # Access the builtin GitCache.
40
+ #
41
+ # @return [Toys::Utils::GitCache]
42
+ #
43
+ def git_cache
44
+ self[KEY]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Toys
6
+ module StandardMixins
7
+ ##
8
+ # A mixin that provides tools for working with the XDG Base Directory
9
+ # Specification.
10
+ #
11
+ # This mixin provides an instance of {Toys::Utils::XDG}, which includes
12
+ # utility methods that locate base directories and search paths for
13
+ # application state, configuration, caches, and other data, according to
14
+ # the [XDG Base Directory Spec version
15
+ # 0.8](https://specifications.freedesktop.org/basedir-spec/0.8/).
16
+ #
17
+ # Example usage:
18
+ #
19
+ # include :xdg
20
+ #
21
+ # def run
22
+ # # Get config file paths, in order from most to least inportant
23
+ # config_files = xdg.lookup_config("my-config.toml")
24
+ # config_files.each { |path| read_my_config(path) }
25
+ # end
26
+ #
27
+ module XDG
28
+ include Mixin
29
+
30
+ ##
31
+ # Context key for the XDG object.
32
+ # @return [Object]
33
+ #
34
+ KEY = ::Object.new.freeze
35
+
36
+ on_initialize do
37
+ require "toys/utils/xdg"
38
+ self[KEY] = Utils::XDG.new
39
+ end
40
+
41
+ ##
42
+ # Access XDG utility methods.
43
+ #
44
+ # @return [Toys::Utils::XDG]
45
+ #
46
+ def xdg
47
+ self[KEY]
48
+ end
49
+ end
50
+
51
+ ##
52
+ # An alternate name for the {XDG} module
53
+ #
54
+ Xdg = XDG
55
+ end
56
+ end
data/lib/toys/template.rb CHANGED
@@ -10,7 +10,7 @@ module Toys
10
10
  # Templates will often support configuration; for example the minitest
11
11
  # template lets you configure the paths to the test files.
12
12
  #
13
- # ## Usage
13
+ # ### Usage
14
14
  #
15
15
  # To create a template, define a class and include this module.
16
16
  # The class defines the "configuration" of the template. If your template
@@ -25,7 +25,7 @@ module Toys
25
25
  # this block are "inserted" into the user's configuration. The template
26
26
  # object is passed to the block so you have access to the template options.
27
27
  #
28
- # ## Example
28
+ # ### Example
29
29
  #
30
30
  # This is a simple template that generates a "hello" tool. The tool simply
31
31
  # prints a `"Hello, #{name}!"` greeting. The name is set as a template
@@ -4,22 +4,25 @@ require "set"
4
4
 
5
5
  module Toys
6
6
  ##
7
- # A Tool describes a single command that can be invoked using Toys.
7
+ # A ToolDefinition describes a single command that can be invoked using Toys.
8
8
  # It has a name, a series of one or more words that you use to identify
9
9
  # the tool on the command line. It also has a set of formal flags and
10
10
  # command line arguments supported, and a block that gets run when the
11
11
  # tool is executed.
12
12
  #
13
- class Tool
13
+ class ToolDefinition
14
14
  ##
15
15
  # Create a new tool.
16
16
  # Should be created only from the DSL via the Loader.
17
17
  # @private
18
18
  #
19
- def initialize(loader, parent, full_name, priority, middleware_stack, middleware_lookup)
19
+ def initialize(parent, full_name, priority, source_root, middleware_stack, middleware_lookup,
20
+ tool_class = nil)
20
21
  @parent = parent
22
+ @settings = Settings.new(parent: parent&.settings)
21
23
  @full_name = full_name.dup.freeze
22
24
  @priority = priority
25
+ @source_root = source_root
23
26
  @built_middleware = middleware_stack.build(middleware_lookup)
24
27
  @subtool_middleware_stack = middleware_stack.dup
25
28
 
@@ -28,7 +31,9 @@ module Toys
28
31
  @templates = {}
29
32
  @completions = {}
30
33
 
31
- reset_definition(loader)
34
+ @precreated_class = tool_class
35
+
36
+ reset_definition
32
37
  end
33
38
 
34
39
  ##
@@ -37,8 +42,8 @@ module Toys
37
42
  # Should be called only from the DSL.
38
43
  # @private
39
44
  #
40
- def reset_definition(loader)
41
- @tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
45
+ def reset_definition
46
+ @tool_class = @precreated_class || create_class
42
47
 
43
48
  @source_info = nil
44
49
  @definition_finished = false
@@ -72,6 +77,13 @@ module Toys
72
77
  @completion = DefaultCompletion.new
73
78
  end
74
79
 
80
+ ##
81
+ # Settings for this tool
82
+ #
83
+ # @return [Toys::Tool::Settings]
84
+ #
85
+ attr_reader :settings
86
+
75
87
  ##
76
88
  # The name of the tool as an array of strings.
77
89
  # This array may not be modified.
@@ -87,6 +99,13 @@ module Toys
87
99
  #
88
100
  attr_reader :priority
89
101
 
102
+ ##
103
+ # The root source info defining this tool, or nil if there is no source.
104
+ #
105
+ # @return [Toys::SourceInfo,nil]
106
+ #
107
+ attr_reader :source_root
108
+
90
109
  ##
91
110
  # The tool class.
92
111
  #
@@ -217,14 +236,14 @@ module Toys
217
236
  #
218
237
  # When reading, this may return an instance of one of the subclasses of
219
238
  # {Toys::Completion::Base}, or a Proc that duck-types it. Generally, this
220
- # defaults to a {Toys::Tool::DefaultCompletion}, providing a standard
221
- # algorithm that finds appropriate completions from flags, positional
222
- # arguments, and subtools.
239
+ # defaults to a {Toys::ToolDefinition::DefaultCompletion}, providing a
240
+ # standard algorithm that finds appropriate completions from flags,
241
+ # positional arguments, and subtools.
223
242
  #
224
243
  # When setting, you may pass any of the following:
225
244
  # * `nil` or `:default` which sets the value to a default instance.
226
- # * A Hash of options to pass to the {Toys::Tool::DefaultCompletion}
227
- # constructor.
245
+ # * A Hash of options to pass to the
246
+ # {Toys::ToolDefinition::DefaultCompletion} constructor.
228
247
  # * Any other form recognized by {Toys::Completion.create}.
229
248
  #
230
249
  # @return [Toys::Completion::Base,Proc]
@@ -408,7 +427,7 @@ module Toys
408
427
  # Get the named acceptor from this tool or its ancestors.
409
428
  #
410
429
  # @param name [String] The acceptor name.
411
- # @return [Tool::Acceptor::Base] The acceptor.
430
+ # @return [Toys::Acceptor::Base] The acceptor.
412
431
  # @return [nil] if no acceptor of the given name is found.
413
432
  #
414
433
  def lookup_acceptor(name)
@@ -441,7 +460,7 @@ module Toys
441
460
  # Get the named completion from this tool or its ancestors.
442
461
  #
443
462
  # @param name [String] The completion name
444
- # @return [Tool::Completion::Base,Proc] The completion proc.
463
+ # @return [Toys::Completion::Base,Proc] The completion proc.
445
464
  # @return [nil] if no completion of the given name is found.
446
465
  #
447
466
  def lookup_completion(name)
@@ -451,11 +470,31 @@ module Toys
451
470
  ##
452
471
  # Include the given mixin in the tool class.
453
472
  #
454
- # @param name [String,Symbol,Module] The mixin name or module
473
+ # The mixin must be given as a module. You can use {#lookup_mixin} or
474
+ # {Loader#resolve_standard_mixin} to resolve named mixins.
475
+ #
476
+ # @param mod [Module] The mixin module
455
477
  # @return [self]
456
478
  #
457
- def include_mixin(name)
458
- tool_class.include(name)
479
+ def include_mixin(mod, *args, **kwargs)
480
+ check_definition_state
481
+ if tool_class.included_modules.include?(mod)
482
+ raise ToolDefinitionError, "Mixin already included: #{mod.name}"
483
+ end
484
+ @includes_modules = true
485
+ if tool_class.respond_to?(:super_include)
486
+ tool_class.super_include(mod)
487
+ else
488
+ tool_class.include(mod)
489
+ end
490
+ if mod.respond_to?(:initializer)
491
+ callback = mod.initializer
492
+ add_initializer(callback, *args, **kwargs) if callback
493
+ end
494
+ if mod.respond_to?(:inclusion)
495
+ callback = mod.inclusion
496
+ tool_class.class_exec(*args, **kwargs, &callback) if callback
497
+ end
459
498
  self
460
499
  end
461
500
 
@@ -565,7 +604,7 @@ module Toys
565
604
  # completion.
566
605
  #
567
606
  # @param name [String] The name of the completion.
568
- # @param completion [Proc,Tool::Completion::Base,Object] The completion to
607
+ # @param completion [Proc,Toys::Completion::Base,Object] The completion to
569
608
  # add. You can provide either a completion object, or a spec understood
570
609
  # by {Toys::Completion.create}.
571
610
  # @param options [Hash] Additional options to pass to the completion.
@@ -669,11 +708,12 @@ module Toys
669
708
  #
670
709
  # @param type [Symbol] The type of group. Default is `:optional`.
671
710
  # @param desc [String,Array<String>,Toys::WrappableString] Short
672
- # description for the group. See {Toys::Tool#desc=} for a description
673
- # of allowed formats. Defaults to `"Flags"`.
711
+ # description for the group. See {Toys::ToolDefinition#desc} for a
712
+ # description of allowed formats. Defaults to `"Flags"`.
674
713
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
675
- # Long description for the flag group. See {Toys::Tool#long_desc=} for
676
- # a description of allowed formats. Defaults to the empty array.
714
+ # Long description for the flag group. See
715
+ # {Toys::ToolDefinition#long_desc} for a description of allowed
716
+ # formats. Defaults to the empty array.
677
717
  # @param name [String,Symbol,nil] The name of the group, or nil for no
678
718
  # name.
679
719
  # @param report_collisions [Boolean] If `true`, raise an exception if a
@@ -741,11 +781,11 @@ module Toys
741
781
  # this flag. You may provide a group name, a FlagGroup object, or
742
782
  # `nil` which denotes the default group.
743
783
  # @param desc [String,Array<String>,Toys::WrappableString] Short
744
- # description for the flag. See {Toys::Tool#desc=} for a description of
745
- # allowed formats. Defaults to the empty string.
784
+ # description for the flag. See {Toys::ToolDefinition#desc} for a
785
+ # description of allowed formats. Defaults to the empty string.
746
786
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
747
- # Long description for the flag. See {Toys::Tool#long_desc=} for a
748
- # description of allowed formats. Defaults to the empty array.
787
+ # Long description for the flag. See {Toys::ToolDefinition#long_desc}
788
+ # for a description of allowed formats. Defaults to the empty array.
749
789
  # @param display_name [String] A display name for this flag, used in help
750
790
  # text and error messages.
751
791
  # @return [self]
@@ -808,11 +848,11 @@ module Toys
808
848
  # @param display_name [String] A name to use for display (in help text and
809
849
  # error reports). Defaults to the key in upper case.
810
850
  # @param desc [String,Array<String>,Toys::WrappableString] Short
811
- # description for the arg. See {Toys::Tool#desc=} for a description of
812
- # allowed formats. Defaults to the empty string.
851
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
852
+ # description of allowed formats. Defaults to the empty string.
813
853
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
814
- # Long description for the arg. See {Toys::Tool#long_desc=} for a
815
- # description of allowed formats. Defaults to the empty array.
854
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
855
+ # for a description of allowed formats. Defaults to the empty array.
816
856
  # @return [self]
817
857
  #
818
858
  def add_required_arg(key, accept: nil, complete: nil, display_name: nil,
@@ -846,11 +886,11 @@ module Toys
846
886
  # @param display_name [String] A name to use for display (in help text and
847
887
  # error reports). Defaults to the key in upper case.
848
888
  # @param desc [String,Array<String>,Toys::WrappableString] Short
849
- # description for the arg. See {Toys::Tool#desc=} for a description of
850
- # allowed formats. Defaults to the empty string.
889
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
890
+ # description of allowed formats. Defaults to the empty string.
851
891
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
852
- # Long description for the arg. See {Toys::Tool#long_desc=} for a
853
- # description of allowed formats. Defaults to the empty array.
892
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
893
+ # for a description of allowed formats. Defaults to the empty array.
854
894
  # @return [self]
855
895
  #
856
896
  def add_optional_arg(key, default: nil, accept: nil, complete: nil,
@@ -884,11 +924,11 @@ module Toys
884
924
  # @param display_name [String] A name to use for display (in help text and
885
925
  # error reports). Defaults to the key in upper case.
886
926
  # @param desc [String,Array<String>,Toys::WrappableString] Short
887
- # description for the arg. See {Toys::Tool#desc=} for a description of
888
- # allowed formats. Defaults to the empty string.
927
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
928
+ # description of allowed formats. Defaults to the empty string.
889
929
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
890
- # Long description for the arg. See {Toys::Tool#long_desc=} for a
891
- # description of allowed formats. Defaults to the empty array.
930
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
931
+ # for a description of allowed formats. Defaults to the empty array.
892
932
  # @return [self]
893
933
  #
894
934
  def set_remaining_args(key, default: [], accept: nil, complete: nil,
@@ -910,7 +950,9 @@ module Toys
910
950
  #
911
951
  def run_handler=(proc)
912
952
  check_definition_state(is_method: true)
913
- @tool_class.to_run(&proc)
953
+ tool_class.class_eval do
954
+ define_method(:run, &proc)
955
+ end
914
956
  end
915
957
 
916
958
  ##
@@ -966,7 +1008,7 @@ module Toys
966
1008
  end
967
1009
 
968
1010
  ##
969
- # Set the completion strategy for this Tool.
1011
+ # Set the completion strategy for this ToolDefinition.
970
1012
  #
971
1013
  # See {#completion} for details.
972
1014
  #
@@ -1056,7 +1098,7 @@ module Toys
1056
1098
  def finish_definition(loader)
1057
1099
  unless @definition_finished
1058
1100
  ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
1059
- config_proc = proc {}
1101
+ config_proc = proc { nil }
1060
1102
  @built_middleware.reverse_each do |middleware|
1061
1103
  config_proc = make_config_proc(middleware, loader, config_proc)
1062
1104
  end
@@ -1120,6 +1162,7 @@ module Toys
1120
1162
  def initialize(complete_subtools: true, include_hidden_subtools: false,
1121
1163
  complete_args: true, complete_flags: true, complete_flag_values: true,
1122
1164
  delegation_target: nil)
1165
+ super()
1123
1166
  @complete_subtools = complete_subtools
1124
1167
  @include_hidden_subtools = include_hidden_subtools
1125
1168
  @complete_flags = complete_flags
@@ -1206,7 +1249,7 @@ module Toys
1206
1249
  return unless arg_parser.flags_allowed?
1207
1250
  active_flag_def = arg_parser.active_flag_def
1208
1251
  return if active_flag_def && active_flag_def.value_type == :required
1209
- match = /\A(--\w[\?\w-]*)=(.*)\z/.match(context.fragment_prefix)
1252
+ match = /\A(--\w[?\w-]*)=(.*)\z/.match(context.fragment_prefix)
1210
1253
  return unless match
1211
1254
  flag_value_context = context.with(fragment_prefix: match[2])
1212
1255
  flag_def = flag_value_context.tool.resolve_flag(match[1]).unique_flag
@@ -1287,8 +1330,24 @@ module Toys
1287
1330
  end
1288
1331
  end
1289
1332
 
1333
+ ##
1334
+ # Tool-based settings class.
1335
+ #
1336
+ # The following settings are supported:
1337
+ #
1338
+ # * `propagate_helper_methods` (_Boolean_) - Whether subtools should
1339
+ # inherit methods defined by parent tools. Defaults to `false`.
1340
+ #
1341
+ class Settings < ::Toys::Settings
1342
+ settings_attr :propagate_helper_methods, default: false
1343
+ end
1344
+
1290
1345
  private
1291
1346
 
1347
+ def create_class
1348
+ ::Class.new(@parent&.settings&.propagate_helper_methods ? @parent.tool_class : ::Toys::Context)
1349
+ end
1350
+
1292
1351
  def make_config_proc(middleware, loader, next_config)
1293
1352
  if middleware.respond_to?(:config)
1294
1353
  proc { middleware.config(self, loader, &next_config) }
@@ -16,8 +16,6 @@ module Toys
16
16
  # This class is not loaded by default. Before using it directly, you should
17
17
  # `require "toys/utils/exec"`
18
18
  #
19
- # ## Features
20
- #
21
19
  # ### Controlling processes
22
20
  #
23
21
  # A process can be started in the *foreground* or the *background*. If you
@@ -138,7 +136,7 @@ module Toys
138
136
  # end
139
137
  # exec_service.exec(["git", "init"], result_callback: my_callback)
140
138
  #
141
- # ## Configuration options
139
+ # ### Configuration options
142
140
  #
143
141
  # A variety of options can be used to control subprocesses. These can be
144
142
  # provided to any method that starts a subprocess. Youc an also set
@@ -1093,9 +1091,10 @@ module Toys
1093
1091
 
1094
1092
  def interpret_out_array_within_fork(stream)
1095
1093
  if stream.first == :child
1096
- if stream[1] == :err
1094
+ case stream[1]
1095
+ when :err
1097
1096
  $stderr
1098
- elsif stream[1] == :out
1097
+ when :out
1099
1098
  $stdout
1100
1099
  end
1101
1100
  else