toys-core 0.11.5 → 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 (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
@@ -4,22 +4,225 @@ 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
+ ##
15
+ # A Completion that implements the default algorithm for a tool.
16
+ #
17
+ class DefaultCompletion < Completion::Base
18
+ ##
19
+ # Create a completion given configuration options.
20
+ #
21
+ # @param complete_subtools [Boolean] Whether to complete subtool names
22
+ # @param include_hidden_subtools [Boolean] Whether to include hidden
23
+ # subtools (i.e. those beginning with an underscore)
24
+ # @param complete_args [Boolean] Whether to complete positional args
25
+ # @param complete_flags [Boolean] Whether to complete flag names
26
+ # @param complete_flag_values [Boolean] Whether to complete flag values
27
+ # @param delegation_target [Array<String>,nil] Delegation target, or
28
+ # `nil` if none.
29
+ #
30
+ def initialize(complete_subtools: true, include_hidden_subtools: false,
31
+ complete_args: true, complete_flags: true, complete_flag_values: true,
32
+ delegation_target: nil)
33
+ super()
34
+ @complete_subtools = complete_subtools
35
+ @include_hidden_subtools = include_hidden_subtools
36
+ @complete_flags = complete_flags
37
+ @complete_args = complete_args
38
+ @complete_flag_values = complete_flag_values
39
+ @delegation_target = delegation_target
40
+ end
41
+
42
+ ##
43
+ # Whether to complete subtool names
44
+ # @return [Boolean]
45
+ #
46
+ def complete_subtools?
47
+ @complete_subtools
48
+ end
49
+
50
+ ##
51
+ # Whether to include hidden subtools
52
+ # @return [Boolean]
53
+ #
54
+ def include_hidden_subtools?
55
+ @include_hidden_subtools
56
+ end
57
+
58
+ ##
59
+ # Whether to complete flags
60
+ # @return [Boolean]
61
+ #
62
+ def complete_flags?
63
+ @complete_flags
64
+ end
65
+
66
+ ##
67
+ # Whether to complete positional args
68
+ # @return [Boolean]
69
+ #
70
+ def complete_args?
71
+ @complete_args
72
+ end
73
+
74
+ ##
75
+ # Whether to complete flag values
76
+ # @return [Boolean]
77
+ #
78
+ def complete_flag_values?
79
+ @complete_flag_values
80
+ end
81
+
82
+ ##
83
+ # Delegation target, or nil for none.
84
+ # @return [Array<String>] if there is a delegation target
85
+ # @return [nil] if there is no delegation target
86
+ #
87
+ attr_accessor :delegation_target
88
+
89
+ ##
90
+ # Returns candidates for the current completion.
91
+ #
92
+ # @param context [Toys::Completion::Context] the current completion
93
+ # context including the string fragment.
94
+ # @return [Array<Toys::Completion::Candidate>] an array of candidates
95
+ #
96
+ def call(context)
97
+ candidates = valued_flag_candidates(context)
98
+ return candidates if candidates
99
+ candidates = subtool_or_arg_candidates(context)
100
+ candidates += plain_flag_candidates(context)
101
+ candidates += flag_value_candidates(context)
102
+ if delegation_target
103
+ delegate_tool = context.cli.loader.lookup_specific(delegation_target)
104
+ if delegate_tool
105
+ context = context.with(previous_words: delegation_target)
106
+ candidates += delegate_tool.completion.call(context)
107
+ end
108
+ end
109
+ candidates
110
+ end
111
+
112
+ private
113
+
114
+ def valued_flag_candidates(context)
115
+ return unless @complete_flag_values
116
+ arg_parser = context.arg_parser
117
+ return unless arg_parser.flags_allowed?
118
+ active_flag_def = arg_parser.active_flag_def
119
+ return if active_flag_def && active_flag_def.value_type == :required
120
+ match = /\A(--\w[?\w-]*)=(.*)\z/.match(context.fragment_prefix)
121
+ return unless match
122
+ flag_value_context = context.with(fragment_prefix: match[2])
123
+ flag_def = flag_value_context.tool.resolve_flag(match[1]).unique_flag
124
+ return [] unless flag_def
125
+ flag_def.value_completion.call(flag_value_context)
126
+ end
127
+
128
+ def subtool_or_arg_candidates(context)
129
+ return [] if context.arg_parser.active_flag_def
130
+ return [] if context.arg_parser.flags_allowed? && context.fragment.start_with?("-")
131
+ subtool_candidates(context) || arg_candidates(context)
132
+ end
133
+
134
+ def subtool_candidates(context)
135
+ return if !@complete_subtools || !context.args.empty?
136
+ tool_name, prefix, fragment = analyze_subtool_fragment(context)
137
+ return unless tool_name
138
+ subtools = context.cli.loader.list_subtools(tool_name,
139
+ include_hidden: @include_hidden_subtools)
140
+ return if subtools.empty?
141
+ candidates = []
142
+ subtools.each do |subtool|
143
+ name = subtool.simple_name
144
+ candidates << Completion::Candidate.new("#{prefix}#{name}") if name.start_with?(fragment)
145
+ end
146
+ candidates
147
+ end
148
+
149
+ def analyze_subtool_fragment(context)
150
+ tool_name = context.tool.full_name
151
+ prefix = ""
152
+ fragment = context.fragment
153
+ delims = context.cli.extra_delimiters
154
+ unless context.fragment_prefix.empty?
155
+ if !context.fragment_prefix.end_with?(":") || !delims.include?(":")
156
+ return [nil, nil, nil]
157
+ end
158
+ tool_name += context.fragment_prefix.split(":")
159
+ end
160
+ unless delims.empty?
161
+ delims_regex = ::Regexp.escape(delims)
162
+ if (match = /\A((.+)[#{delims_regex}])(.*)\z/.match(fragment))
163
+ fragment = match[3]
164
+ tool_name += match[2].split(/[#{delims_regex}]/)
165
+ prefix = match[1]
166
+ end
167
+ end
168
+ [tool_name, prefix, fragment]
169
+ end
170
+
171
+ def arg_candidates(context)
172
+ return unless @complete_args
173
+ arg_def = context.arg_parser.next_arg_def
174
+ return [] unless arg_def
175
+ arg_def.completion.call(context)
176
+ end
177
+
178
+ def plain_flag_candidates(context)
179
+ return [] if !@complete_flags || context[:disable_flags]
180
+ arg_parser = context.arg_parser
181
+ return [] unless arg_parser.flags_allowed?
182
+ flag_def = arg_parser.active_flag_def
183
+ return [] if flag_def && flag_def.value_type == :required
184
+ return [] if context.fragment =~ /\A[^-]/ || !context.fragment_prefix.empty?
185
+ context.tool.flags.flat_map do |flag|
186
+ flag.flag_completion.call(context)
187
+ end
188
+ end
189
+
190
+ def flag_value_candidates(context)
191
+ return unless @complete_flag_values
192
+ arg_parser = context.arg_parser
193
+ flag_def = arg_parser.active_flag_def
194
+ return [] unless flag_def
195
+ return [] if @complete_flags && arg_parser.flags_allowed? &&
196
+ flag_def.value_type == :optional && context.fragment.start_with?("-")
197
+ flag_def.value_completion.call(context)
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Tool-based settings class.
203
+ #
204
+ # The following settings are supported:
205
+ #
206
+ # * `propagate_helper_methods` (_Boolean_) - Whether subtools should
207
+ # inherit methods defined by parent tools. Defaults to `false`.
208
+ #
209
+ class Settings < ::Toys::Settings
210
+ settings_attr :propagate_helper_methods, default: false
211
+ end
212
+
14
213
  ##
15
214
  # Create a new tool.
16
215
  # Should be created only from the DSL via the Loader.
216
+ #
17
217
  # @private
18
218
  #
19
- def initialize(loader, parent, full_name, priority, middleware_stack, middleware_lookup)
219
+ def initialize(parent, full_name, priority, source_root, middleware_stack, middleware_lookup,
220
+ tool_class = nil)
20
221
  @parent = parent
222
+ @settings = Settings.new(parent: parent&.settings)
21
223
  @full_name = full_name.dup.freeze
22
224
  @priority = priority
225
+ @source_root = source_root
23
226
  @built_middleware = middleware_stack.build(middleware_lookup)
24
227
  @subtool_middleware_stack = middleware_stack.dup
25
228
 
@@ -28,22 +231,25 @@ module Toys
28
231
  @templates = {}
29
232
  @completions = {}
30
233
 
31
- reset_definition(loader)
234
+ @precreated_class = tool_class
235
+
236
+ reset_definition
32
237
  end
33
238
 
34
239
  ##
35
240
  # Reset the definition of this tool, deleting all definition data but
36
241
  # leaving named acceptors, mixins, and templates intact.
37
242
  # Should be called only from the DSL.
243
+ #
38
244
  # @private
39
245
  #
40
- def reset_definition(loader)
41
- @tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
246
+ def reset_definition
247
+ @tool_class = @precreated_class || create_class
42
248
 
43
249
  @source_info = nil
44
250
  @definition_finished = false
45
251
 
46
- @desc = WrappableString.new("")
252
+ @desc = WrappableString.new
47
253
  @long_desc = []
48
254
 
49
255
  @default_data = {}
@@ -72,6 +278,13 @@ module Toys
72
278
  @completion = DefaultCompletion.new
73
279
  end
74
280
 
281
+ ##
282
+ # Settings for this tool
283
+ #
284
+ # @return [Toys::Tool::Settings]
285
+ #
286
+ attr_reader :settings
287
+
75
288
  ##
76
289
  # The name of the tool as an array of strings.
77
290
  # This array may not be modified.
@@ -87,6 +300,13 @@ module Toys
87
300
  #
88
301
  attr_reader :priority
89
302
 
303
+ ##
304
+ # The root source info defining this tool, or nil if there is no source.
305
+ #
306
+ # @return [Toys::SourceInfo,nil]
307
+ #
308
+ attr_reader :source_root
309
+
90
310
  ##
91
311
  # The tool class.
92
312
  #
@@ -217,14 +437,14 @@ module Toys
217
437
  #
218
438
  # When reading, this may return an instance of one of the subclasses of
219
439
  # {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.
440
+ # defaults to a {Toys::ToolDefinition::DefaultCompletion}, providing a
441
+ # standard algorithm that finds appropriate completions from flags,
442
+ # positional arguments, and subtools.
223
443
  #
224
444
  # When setting, you may pass any of the following:
225
445
  # * `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.
446
+ # * A Hash of options to pass to the
447
+ # {Toys::ToolDefinition::DefaultCompletion} constructor.
228
448
  # * Any other form recognized by {Toys::Completion.create}.
229
449
  #
230
450
  # @return [Toys::Completion::Base,Proc]
@@ -408,7 +628,7 @@ module Toys
408
628
  # Get the named acceptor from this tool or its ancestors.
409
629
  #
410
630
  # @param name [String] The acceptor name.
411
- # @return [Tool::Acceptor::Base] The acceptor.
631
+ # @return [Toys::Acceptor::Base] The acceptor.
412
632
  # @return [nil] if no acceptor of the given name is found.
413
633
  #
414
634
  def lookup_acceptor(name)
@@ -441,7 +661,7 @@ module Toys
441
661
  # Get the named completion from this tool or its ancestors.
442
662
  #
443
663
  # @param name [String] The completion name
444
- # @return [Tool::Completion::Base,Proc] The completion proc.
664
+ # @return [Toys::Completion::Base,Proc] The completion proc.
445
665
  # @return [nil] if no completion of the given name is found.
446
666
  #
447
667
  def lookup_completion(name)
@@ -451,11 +671,31 @@ module Toys
451
671
  ##
452
672
  # Include the given mixin in the tool class.
453
673
  #
454
- # @param name [String,Symbol,Module] The mixin name or module
674
+ # The mixin must be given as a module. You can use {#lookup_mixin} to
675
+ # resolve named mixins.
676
+ #
677
+ # @param mod [Module] The mixin module
455
678
  # @return [self]
456
679
  #
457
- def include_mixin(name)
458
- tool_class.include(name)
680
+ def include_mixin(mod, *args, **kwargs)
681
+ check_definition_state
682
+ if tool_class.included_modules.include?(mod)
683
+ raise ToolDefinitionError, "Mixin already included: #{mod.name}"
684
+ end
685
+ @includes_modules = true
686
+ if tool_class.respond_to?(:super_include)
687
+ tool_class.super_include(mod)
688
+ else
689
+ tool_class.include(mod)
690
+ end
691
+ if mod.respond_to?(:initializer)
692
+ callback = mod.initializer
693
+ add_initializer(callback, *args, **kwargs) if callback
694
+ end
695
+ if mod.respond_to?(:inclusion)
696
+ callback = mod.inclusion
697
+ tool_class.class_exec(*args, **kwargs, &callback) if callback
698
+ end
459
699
  self
460
700
  end
461
701
 
@@ -565,7 +805,7 @@ module Toys
565
805
  # completion.
566
806
  #
567
807
  # @param name [String] The name of the completion.
568
- # @param completion [Proc,Tool::Completion::Base,Object] The completion to
808
+ # @param completion [Proc,Toys::Completion::Base,Object] The completion to
569
809
  # add. You can provide either a completion object, or a spec understood
570
810
  # by {Toys::Completion.create}.
571
811
  # @param options [Hash] Additional options to pass to the completion.
@@ -669,11 +909,12 @@ module Toys
669
909
  #
670
910
  # @param type [Symbol] The type of group. Default is `:optional`.
671
911
  # @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"`.
912
+ # description for the group. See {Toys::ToolDefinition#desc} for a
913
+ # description of allowed formats. Defaults to `"Flags"`.
674
914
  # @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.
915
+ # Long description for the flag group. See
916
+ # {Toys::ToolDefinition#long_desc} for a description of allowed
917
+ # formats. Defaults to the empty array.
677
918
  # @param name [String,Symbol,nil] The name of the group, or nil for no
678
919
  # name.
679
920
  # @param report_collisions [Boolean] If `true`, raise an exception if a
@@ -741,11 +982,11 @@ module Toys
741
982
  # this flag. You may provide a group name, a FlagGroup object, or
742
983
  # `nil` which denotes the default group.
743
984
  # @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.
985
+ # description for the flag. See {Toys::ToolDefinition#desc} for a
986
+ # description of allowed formats. Defaults to the empty string.
746
987
  # @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.
988
+ # Long description for the flag. See {Toys::ToolDefinition#long_desc}
989
+ # for a description of allowed formats. Defaults to the empty array.
749
990
  # @param display_name [String] A display name for this flag, used in help
750
991
  # text and error messages.
751
992
  # @return [self]
@@ -808,11 +1049,11 @@ module Toys
808
1049
  # @param display_name [String] A name to use for display (in help text and
809
1050
  # error reports). Defaults to the key in upper case.
810
1051
  # @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.
1052
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
1053
+ # description of allowed formats. Defaults to the empty string.
813
1054
  # @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.
1055
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
1056
+ # for a description of allowed formats. Defaults to the empty array.
816
1057
  # @return [self]
817
1058
  #
818
1059
  def add_required_arg(key, accept: nil, complete: nil, display_name: nil,
@@ -846,11 +1087,11 @@ module Toys
846
1087
  # @param display_name [String] A name to use for display (in help text and
847
1088
  # error reports). Defaults to the key in upper case.
848
1089
  # @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.
1090
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
1091
+ # description of allowed formats. Defaults to the empty string.
851
1092
  # @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.
1093
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
1094
+ # for a description of allowed formats. Defaults to the empty array.
854
1095
  # @return [self]
855
1096
  #
856
1097
  def add_optional_arg(key, default: nil, accept: nil, complete: nil,
@@ -884,11 +1125,11 @@ module Toys
884
1125
  # @param display_name [String] A name to use for display (in help text and
885
1126
  # error reports). Defaults to the key in upper case.
886
1127
  # @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.
1128
+ # description for the arg. See {Toys::ToolDefinition#desc} for a
1129
+ # description of allowed formats. Defaults to the empty string.
889
1130
  # @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.
1131
+ # Long description for the arg. See {Toys::ToolDefinition#long_desc}
1132
+ # for a description of allowed formats. Defaults to the empty array.
892
1133
  # @return [self]
893
1134
  #
894
1135
  def set_remaining_args(key, default: [], accept: nil, complete: nil,
@@ -910,7 +1151,9 @@ module Toys
910
1151
  #
911
1152
  def run_handler=(proc)
912
1153
  check_definition_state(is_method: true)
913
- @tool_class.to_run(&proc)
1154
+ tool_class.class_eval do
1155
+ define_method(:run, &proc)
1156
+ end
914
1157
  end
915
1158
 
916
1159
  ##
@@ -966,7 +1209,7 @@ module Toys
966
1209
  end
967
1210
 
968
1211
  ##
969
- # Set the completion strategy for this Tool.
1212
+ # Set the completion strategy for this ToolDefinition.
970
1213
  #
971
1214
  # See {#completion} for details.
972
1215
  #
@@ -1032,6 +1275,7 @@ module Toys
1032
1275
 
1033
1276
  ##
1034
1277
  # Lookup the custom context directory in this tool and its ancestors.
1278
+ #
1035
1279
  # @private
1036
1280
  #
1037
1281
  def lookup_custom_context_directory
@@ -1040,6 +1284,7 @@ module Toys
1040
1284
 
1041
1285
  ##
1042
1286
  # Mark this tool as having at least one module included.
1287
+ #
1043
1288
  # @private
1044
1289
  #
1045
1290
  def mark_includes_modules
@@ -1051,12 +1296,13 @@ module Toys
1051
1296
  ##
1052
1297
  # Complete definition and run middleware configs. Should be called from
1053
1298
  # the Loader only.
1299
+ #
1054
1300
  # @private
1055
1301
  #
1056
1302
  def finish_definition(loader)
1057
1303
  unless @definition_finished
1058
1304
  ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
1059
- config_proc = proc {}
1305
+ config_proc = proc { nil }
1060
1306
  @built_middleware.reverse_each do |middleware|
1061
1307
  config_proc = make_config_proc(middleware, loader, config_proc)
1062
1308
  end
@@ -1072,6 +1318,7 @@ module Toys
1072
1318
 
1073
1319
  ##
1074
1320
  # Run all initializers against a context. Called from the Runner.
1321
+ #
1075
1322
  # @private
1076
1323
  #
1077
1324
  def run_initializers(context)
@@ -1083,6 +1330,7 @@ module Toys
1083
1330
  ##
1084
1331
  # Check that the tool can still be defined. Should be called internally
1085
1332
  # or from the DSL only.
1333
+ #
1086
1334
  # @private
1087
1335
  #
1088
1336
  def check_definition_state(is_arg: false, is_method: false)
@@ -1101,194 +1349,12 @@ module Toys
1101
1349
  self
1102
1350
  end
1103
1351
 
1104
- ##
1105
- # A Completion that implements the default algorithm for a tool.
1106
- #
1107
- class DefaultCompletion < Completion::Base
1108
- ##
1109
- # Create a completion given configuration options.
1110
- #
1111
- # @param complete_subtools [Boolean] Whether to complete subtool names
1112
- # @param include_hidden_subtools [Boolean] Whether to include hidden
1113
- # subtools (i.e. those beginning with an underscore)
1114
- # @param complete_args [Boolean] Whether to complete positional args
1115
- # @param complete_flags [Boolean] Whether to complete flag names
1116
- # @param complete_flag_values [Boolean] Whether to complete flag values
1117
- # @param delegation_target [Array<String>,nil] Delegation target, or
1118
- # `nil` if none.
1119
- #
1120
- def initialize(complete_subtools: true, include_hidden_subtools: false,
1121
- complete_args: true, complete_flags: true, complete_flag_values: true,
1122
- delegation_target: nil)
1123
- @complete_subtools = complete_subtools
1124
- @include_hidden_subtools = include_hidden_subtools
1125
- @complete_flags = complete_flags
1126
- @complete_args = complete_args
1127
- @complete_flag_values = complete_flag_values
1128
- @delegation_target = delegation_target
1129
- end
1130
-
1131
- ##
1132
- # Whether to complete subtool names
1133
- # @return [Boolean]
1134
- #
1135
- def complete_subtools?
1136
- @complete_subtools
1137
- end
1138
-
1139
- ##
1140
- # Whether to include hidden subtools
1141
- # @return [Boolean]
1142
- #
1143
- def include_hidden_subtools?
1144
- @include_hidden_subtools
1145
- end
1146
-
1147
- ##
1148
- # Whether to complete flags
1149
- # @return [Boolean]
1150
- #
1151
- def complete_flags?
1152
- @complete_flags
1153
- end
1154
-
1155
- ##
1156
- # Whether to complete positional args
1157
- # @return [Boolean]
1158
- #
1159
- def complete_args?
1160
- @complete_args
1161
- end
1162
-
1163
- ##
1164
- # Whether to complete flag values
1165
- # @return [Boolean]
1166
- #
1167
- def complete_flag_values?
1168
- @complete_flag_values
1169
- end
1170
-
1171
- ##
1172
- # Delegation target, or nil for none.
1173
- # @return [Array<String>] if there is a delegation target
1174
- # @return [nil] if there is no delegation target
1175
- #
1176
- attr_accessor :delegation_target
1177
-
1178
- ##
1179
- # Returns candidates for the current completion.
1180
- #
1181
- # @param context [Toys::Completion::Context] the current completion
1182
- # context including the string fragment.
1183
- # @return [Array<Toys::Completion::Candidate>] an array of candidates
1184
- #
1185
- def call(context)
1186
- candidates = valued_flag_candidates(context)
1187
- return candidates if candidates
1188
- candidates = subtool_or_arg_candidates(context)
1189
- candidates += plain_flag_candidates(context)
1190
- candidates += flag_value_candidates(context)
1191
- if delegation_target
1192
- delegate_tool = context.cli.loader.lookup_specific(delegation_target)
1193
- if delegate_tool
1194
- context = context.with(previous_words: delegation_target)
1195
- candidates += delegate_tool.completion.call(context)
1196
- end
1197
- end
1198
- candidates
1199
- end
1200
-
1201
- private
1202
-
1203
- def valued_flag_candidates(context)
1204
- return unless @complete_flag_values
1205
- arg_parser = context.arg_parser
1206
- return unless arg_parser.flags_allowed?
1207
- active_flag_def = arg_parser.active_flag_def
1208
- return if active_flag_def && active_flag_def.value_type == :required
1209
- match = /\A(--\w[\?\w-]*)=(.*)\z/.match(context.fragment_prefix)
1210
- return unless match
1211
- flag_value_context = context.with(fragment_prefix: match[2])
1212
- flag_def = flag_value_context.tool.resolve_flag(match[1]).unique_flag
1213
- return [] unless flag_def
1214
- flag_def.value_completion.call(flag_value_context)
1215
- end
1216
-
1217
- def subtool_or_arg_candidates(context)
1218
- return [] if context.arg_parser.active_flag_def
1219
- return [] if context.arg_parser.flags_allowed? && context.fragment.start_with?("-")
1220
- subtool_candidates(context) || arg_candidates(context)
1221
- end
1222
-
1223
- def subtool_candidates(context)
1224
- return if !@complete_subtools || !context.args.empty?
1225
- tool_name, prefix, fragment = analyze_subtool_fragment(context)
1226
- return unless tool_name
1227
- subtools = context.cli.loader.list_subtools(tool_name,
1228
- include_hidden: @include_hidden_subtools)
1229
- return if subtools.empty?
1230
- candidates = []
1231
- subtools.each do |subtool|
1232
- name = subtool.simple_name
1233
- candidates << Completion::Candidate.new("#{prefix}#{name}") if name.start_with?(fragment)
1234
- end
1235
- candidates
1236
- end
1237
-
1238
- def analyze_subtool_fragment(context)
1239
- tool_name = context.tool.full_name
1240
- prefix = ""
1241
- fragment = context.fragment
1242
- delims = context.cli.extra_delimiters
1243
- unless context.fragment_prefix.empty?
1244
- if !context.fragment_prefix.end_with?(":") || !delims.include?(":")
1245
- return [nil, nil, nil]
1246
- end
1247
- tool_name += context.fragment_prefix.split(":")
1248
- end
1249
- unless delims.empty?
1250
- delims_regex = ::Regexp.escape(delims)
1251
- if (match = /\A((.+)[#{delims_regex}])(.*)\z/.match(fragment))
1252
- fragment = match[3]
1253
- tool_name += match[2].split(/[#{delims_regex}]/)
1254
- prefix = match[1]
1255
- end
1256
- end
1257
- [tool_name, prefix, fragment]
1258
- end
1259
-
1260
- def arg_candidates(context)
1261
- return unless @complete_args
1262
- arg_def = context.arg_parser.next_arg_def
1263
- return [] unless arg_def
1264
- arg_def.completion.call(context)
1265
- end
1266
-
1267
- def plain_flag_candidates(context)
1268
- return [] if !@complete_flags || context[:disable_flags]
1269
- arg_parser = context.arg_parser
1270
- return [] unless arg_parser.flags_allowed?
1271
- flag_def = arg_parser.active_flag_def
1272
- return [] if flag_def && flag_def.value_type == :required
1273
- return [] if context.fragment =~ /\A[^-]/ || !context.fragment_prefix.empty?
1274
- context.tool.flags.flat_map do |flag|
1275
- flag.flag_completion.call(context)
1276
- end
1277
- end
1352
+ private
1278
1353
 
1279
- def flag_value_candidates(context)
1280
- return unless @complete_flag_values
1281
- arg_parser = context.arg_parser
1282
- flag_def = arg_parser.active_flag_def
1283
- return [] unless flag_def
1284
- return [] if @complete_flags && arg_parser.flags_allowed? &&
1285
- flag_def.value_type == :optional && context.fragment.start_with?("-")
1286
- flag_def.value_completion.call(context)
1287
- end
1354
+ def create_class
1355
+ ::Class.new(@parent&.settings&.propagate_helper_methods ? @parent.tool_class : ::Toys::Context)
1288
1356
  end
1289
1357
 
1290
- private
1291
-
1292
1358
  def make_config_proc(middleware, loader, next_config)
1293
1359
  if middleware.respond_to?(:config)
1294
1360
  proc { middleware.config(self, loader, &next_config) }