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
@@ -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) }