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/flag.rb CHANGED
@@ -6,6 +6,367 @@ module Toys
6
6
  # key. The flags within a single Flag definition are synonyms.
7
7
  #
8
8
  class Flag
9
+ ##
10
+ # Representation of a single flag.
11
+ #
12
+ class Syntax
13
+ # rubocop:disable Style/PerlBackrefs
14
+
15
+ ##
16
+ # Parse flag syntax
17
+ # @param str [String] syntax.
18
+ #
19
+ def initialize(str)
20
+ case str
21
+ when /\A(-([?\w]))\z/
22
+ setup(str, $1, nil, $1, $2, :short, nil, nil, nil, nil)
23
+ when /\A(-([?\w]))(?:( ?)\[|\[( ))(\w+)\]\z/
24
+ setup(str, $1, nil, $1, $2, :short, :value, :optional, $3 || $4, $5)
25
+ when /\A(-([?\w]))( ?)(\w+)\z/
26
+ setup(str, $1, nil, $1, $2, :short, :value, :required, $3, $4)
27
+ when /\A--\[no-\](\w[?\w-]*)\z/
28
+ setup(str, "--#{$1}", "--no-#{$1}", str, $1, :long, :boolean, nil, nil, nil)
29
+ when /\A(--(\w[?\w-]*))\z/
30
+ setup(str, $1, nil, $1, $2, :long, nil, nil, nil, nil)
31
+ when /\A(--(\w[?\w-]*))(?:([= ])\[|\[([= ]))(\w+)\]\z/
32
+ setup(str, $1, nil, $1, $2, :long, :value, :optional, $3 || $4, $5)
33
+ when /\A(--(\w[?\w-]*))([= ])(\w+)\z/
34
+ setup(str, $1, nil, $1, $2, :long, :value, :required, $3, $4)
35
+ else
36
+ raise ToolDefinitionError, "Illegal flag: #{str.inspect}"
37
+ end
38
+ end
39
+
40
+ # rubocop:enable Style/PerlBackrefs
41
+
42
+ ##
43
+ # The original string that was parsed to produce this syntax.
44
+ # @return [String]
45
+ #
46
+ attr_reader :original_str
47
+
48
+ ##
49
+ # The flags (without values) corresponding to this syntax.
50
+ # @return [Array<String>]
51
+ #
52
+ attr_reader :flags
53
+
54
+ ##
55
+ # The flag (without values) corresponding to the normal "positive" form
56
+ # of this flag.
57
+ # @return [String]
58
+ #
59
+ attr_reader :positive_flag
60
+
61
+ ##
62
+ # The flag (without values) corresponding to the "negative" form of this
63
+ # flag, if any. i.e. if the original string was `"--[no-]abc"`, the
64
+ # negative flag is `"--no-abc"`.
65
+ # @return [String] The negative form.
66
+ # @return [nil] if the flag has no negative form.
67
+ #
68
+ attr_reader :negative_flag
69
+
70
+ ##
71
+ # The original string with the value (if any) stripped, but retaining
72
+ # the `[no-]` prefix if present.
73
+ # @return [String]
74
+ #
75
+ attr_reader :str_without_value
76
+
77
+ ##
78
+ # A string used to sort this flag compared with others.
79
+ # @return [String]
80
+ #
81
+ attr_reader :sort_str
82
+
83
+ ##
84
+ # The style of flag (`:long` or `:short`).
85
+ # @return [:long] if this is a long flag (i.e. double hyphen)
86
+ # @return [:short] if this is a short flag (i.e. single hyphen with one
87
+ # character).
88
+ #
89
+ attr_reader :flag_style
90
+
91
+ ##
92
+ # The type of flag (`:boolean` or `:value`)
93
+ # @return [:boolean] if this is a boolean flag (i.e. no value)
94
+ # @return [:value] if this flag takes a value (even if optional)
95
+ #
96
+ attr_reader :flag_type
97
+
98
+ ##
99
+ # The type of value (`:required` or `:optional`)
100
+ # @return [:required] if this flag takes a required value
101
+ # @return [:optional] if this flag takes an optional value
102
+ # @return [nil] if this flag is a boolean flag
103
+ #
104
+ attr_reader :value_type
105
+
106
+ ##
107
+ # The default delimiter used for the value of this flag. This could be
108
+ # `""` or `" "` for a short flag, or `" "` or `"="` for a long flag.
109
+ # @return [String] delimiter
110
+ # @return [nil] if this flag is a boolean flag
111
+ #
112
+ attr_reader :value_delim
113
+
114
+ ##
115
+ # The default "label" for the value. e.g. in `--abc=VAL` the label is
116
+ # `"VAL"`.
117
+ # @return [String] the label
118
+ # @return [nil] if this flag is a boolean flag
119
+ #
120
+ attr_reader :value_label
121
+
122
+ ##
123
+ # A canonical string representing this flag's syntax, normalized to match
124
+ # the type, delimiters, etc. settings of other flag syntaxes. This is
125
+ # generally used in help strings to represent this flag.
126
+ # @return [String]
127
+ #
128
+ attr_reader :canonical_str
129
+
130
+ ##
131
+ # @private
132
+ #
133
+ def configure_canonical(canonical_flag_type, canonical_value_type,
134
+ canonical_value_label, canonical_value_delim)
135
+ return unless flag_type.nil?
136
+ @flag_type = canonical_flag_type
137
+ return unless canonical_flag_type == :value
138
+ @value_type = canonical_value_type
139
+ canonical_value_delim = "" if canonical_value_delim == "=" && flag_style == :short
140
+ canonical_value_delim = "=" if canonical_value_delim == "" && flag_style == :long
141
+ @value_delim = canonical_value_delim
142
+ @value_label = canonical_value_label
143
+ label = @value_type == :optional ? "[#{@value_label}]" : @value_label
144
+ @canonical_str = "#{str_without_value}#{@value_delim}#{label}"
145
+ end
146
+
147
+ private
148
+
149
+ def setup(original_str, positive_flag, negative_flag, str_without_value, sort_str,
150
+ flag_style, flag_type, value_type, value_delim, value_label)
151
+ @original_str = original_str
152
+ @positive_flag = positive_flag
153
+ @negative_flag = negative_flag
154
+ @flags = [positive_flag]
155
+ @flags << negative_flag if negative_flag
156
+ @str_without_value = str_without_value
157
+ @sort_str = sort_str
158
+ @flag_style = flag_style
159
+ @flag_type = flag_type
160
+ @value_type = value_type
161
+ @value_delim = value_delim
162
+ @value_label = value_label ? value_label.upcase : value_label
163
+ @canonical_str = original_str
164
+ end
165
+ end
166
+
167
+ ##
168
+ # The result of looking up a flag by name.
169
+ #
170
+ class Resolution
171
+ ##
172
+ # @private
173
+ #
174
+ def initialize(str)
175
+ @string = str
176
+ @flags = []
177
+ @found_exact = false
178
+ end
179
+
180
+ ##
181
+ # The flag string that was looked up
182
+ # @return [String]
183
+ #
184
+ attr_reader :string
185
+
186
+ ##
187
+ # Whether an exact match of the string was found
188
+ # @return [Boolean]
189
+ #
190
+ def found_exact?
191
+ @found_exact
192
+ end
193
+
194
+ ##
195
+ # The number of matches that were found.
196
+ # @return [Integer]
197
+ #
198
+ def count
199
+ @flags.size
200
+ end
201
+
202
+ ##
203
+ # Whether a single unique match was found.
204
+ # @return [Boolean]
205
+ #
206
+ def found_unique?
207
+ @flags.size == 1
208
+ end
209
+
210
+ ##
211
+ # Whether no matches were found.
212
+ # @return [Boolean]
213
+ #
214
+ def not_found?
215
+ @flags.empty?
216
+ end
217
+
218
+ ##
219
+ # Whether multiple matches were found (i.e. ambiguous input).
220
+ # @return [Boolean]
221
+ #
222
+ def found_multiple?
223
+ @flags.size > 1
224
+ end
225
+
226
+ ##
227
+ # Return the unique {Toys::Flag}, or `nil` if not found or
228
+ # not unique.
229
+ # @return [Toys::Flag,nil]
230
+ #
231
+ def unique_flag
232
+ found_unique? ? @flags.first[0] : nil
233
+ end
234
+
235
+ ##
236
+ # Return the unique {Toys::Flag::Syntax}, or `nil` if not found
237
+ # or not unique.
238
+ # @return [Toys::Flag::Syntax,nil]
239
+ #
240
+ def unique_flag_syntax
241
+ found_unique? ? @flags.first[1] : nil
242
+ end
243
+
244
+ ##
245
+ # Return whether the unique match was a hit on the negative (`--no-*`)
246
+ # case, or `nil` if not found or not unique.
247
+ # @return [Boolean,nil]
248
+ #
249
+ def unique_flag_negative?
250
+ found_unique? ? @flags.first[2] : nil
251
+ end
252
+
253
+ ##
254
+ # Returns an array of the matching full flag strings.
255
+ # @return [Array<String>]
256
+ #
257
+ def matching_flag_strings
258
+ @flags.map do |_flag, flag_syntax, negative|
259
+ negative ? flag_syntax.negative_flag : flag_syntax.positive_flag
260
+ end
261
+ end
262
+
263
+ ##
264
+ # @private
265
+ #
266
+ def add!(flag, flag_syntax, negative, exact)
267
+ @flags = [] if exact && !found_exact?
268
+ if exact || !found_exact?
269
+ @flags << [flag, flag_syntax, negative]
270
+ @found_exact = exact
271
+ end
272
+ self
273
+ end
274
+
275
+ ##
276
+ # @private
277
+ #
278
+ def merge!(other)
279
+ raise "String mismatch" unless string == other.string
280
+ other.instance_variable_get(:@flags).each do |flag, flag_syntax, negative|
281
+ add!(flag, flag_syntax, negative, other.found_exact?)
282
+ end
283
+ self
284
+ end
285
+ end
286
+
287
+ ##
288
+ # A Completion that returns all possible flags associated with a
289
+ # {Toys::Flag}.
290
+ #
291
+ class DefaultCompletion < Completion::Base
292
+ ##
293
+ # Create a completion given configuration options.
294
+ #
295
+ # @param flag [Toys::Flag] The flag definition.
296
+ # @param include_short [Boolean] Whether to include short flags.
297
+ # @param include_long [Boolean] Whether to include long flags.
298
+ # @param include_negative [Boolean] Whether to include `--no-*` forms.
299
+ #
300
+ def initialize(flag:, include_short: true, include_long: true, include_negative: true)
301
+ super()
302
+ @flag = flag
303
+ @include_short = include_short
304
+ @include_long = include_long
305
+ @include_negative = include_negative
306
+ end
307
+
308
+ ##
309
+ # Whether to include short flags
310
+ # @return [Boolean]
311
+ #
312
+ def include_short?
313
+ @include_short
314
+ end
315
+
316
+ ##
317
+ # Whether to include long flags
318
+ # @return [Boolean]
319
+ #
320
+ def include_long?
321
+ @include_long
322
+ end
323
+
324
+ ##
325
+ # Whether to include negative long flags
326
+ # @return [Boolean]
327
+ #
328
+ def include_negative?
329
+ @include_negative
330
+ end
331
+
332
+ ##
333
+ # Returns candidates for the current completion.
334
+ #
335
+ # @param context [Toys::Completion::Context] the current completion
336
+ # context including the string fragment.
337
+ # @return [Array<Toys::Completion::Candidate>] an array of candidates
338
+ #
339
+ def call(context)
340
+ results =
341
+ if @include_short && @include_long && @include_negative
342
+ @flag.effective_flags
343
+ else
344
+ collect_results
345
+ end
346
+ fragment = context.fragment
347
+ results.find_all { |val| val.start_with?(fragment) }
348
+ .map { |str| Completion::Candidate.new(str) }
349
+ end
350
+
351
+ private
352
+
353
+ def collect_results
354
+ results = []
355
+ if @include_short
356
+ results += @flag.short_flag_syntax.map(&:positive_flag)
357
+ end
358
+ if @include_long
359
+ results +=
360
+ if @include_negative
361
+ @flag.long_flag_syntax.flat_map(&:flags)
362
+ else
363
+ @flag.long_flag_syntax.map(&:positive_flag)
364
+ end
365
+ end
366
+ results
367
+ end
368
+ end
369
+
9
370
  ##
10
371
  # The set handler replaces the previous value.
11
372
  # @return [Proc]
@@ -24,30 +385,6 @@ module Toys
24
385
  #
25
386
  DEFAULT_HANDLER = SET_HANDLER
26
387
 
27
- ##
28
- # Create a Flag definition.
29
- # This argument list is subject to change. Use {Toys::Flag.create} instead
30
- # for a more stable interface.
31
- # @private
32
- #
33
- def initialize(key, flags, used_flags, report_collisions, acceptor, handler, default,
34
- flag_completion, value_completion, desc, long_desc, display_name, group)
35
- @group = group
36
- @key = key
37
- @flag_syntax = Array(flags).map { |s| Syntax.new(s) }
38
- @acceptor = Acceptor.create(acceptor)
39
- @handler = resolve_handler(handler)
40
- @desc = WrappableString.make(desc)
41
- @long_desc = WrappableString.make_array(long_desc)
42
- @default = default
43
- @flag_completion = create_flag_completion(flag_completion)
44
- @value_completion = Completion.create(value_completion, **{})
45
- create_default_flag if @flag_syntax.empty?
46
- remove_used_flags(used_flags, report_collisions)
47
- canonicalize
48
- summarize(display_name)
49
- end
50
-
51
388
  ##
52
389
  # Create a flag definition.
53
390
  #
@@ -85,11 +422,11 @@ module Toys
85
422
  # true.
86
423
  # @param group [Toys::FlagGroup] Group containing this flag.
87
424
  # @param desc [String,Array<String>,Toys::WrappableString] Short
88
- # description for the flag. See {Toys::Tool#desc=} for a description of
89
- # allowed formats. Defaults to the empty string.
425
+ # description for the flag. See {Toys::ToolDefinition#desc} for a
426
+ # description of allowed formats. Defaults to the empty string.
90
427
  # @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
91
- # Long description for the flag. See {Toys::Tool#long_desc=} for a
92
- # description of allowed formats. Defaults to the empty array.
428
+ # Long description for the flag. See {Toys::ToolDefinition#long_desc}
429
+ # for a description of allowed formats. Defaults to the empty array.
93
430
  # @param display_name [String] A display name for this flag, used in help
94
431
  # text and error messages.
95
432
  # @param used_flags [Array<String>] An array of flags already in use.
@@ -103,700 +440,369 @@ module Toys
103
440
  end
104
441
 
105
442
  ##
106
- # Returns the flag group containing this flag
107
- # @return [Toys::FlagGroup]
108
- #
109
- attr_reader :group
110
-
111
- ##
112
- # Returns the key.
113
- # @return [Symbol]
114
- #
115
- attr_reader :key
116
-
117
- ##
118
- # Returns an array of Flag::Syntax for the flags.
119
- # @return [Array<Toys::Flag::Syntax>]
120
- #
121
- attr_reader :flag_syntax
122
-
123
- ##
124
- # Returns the effective acceptor.
125
- # @return [Toys::Acceptor::Base]
126
- #
127
- attr_reader :acceptor
128
-
129
- ##
130
- # Returns the default value, which may be `nil`.
131
- # @return [Object]
132
- #
133
- attr_reader :default
134
-
135
- ##
136
- # The short description string.
137
- #
138
- # When reading, this is always returned as a {Toys::WrappableString}.
139
- #
140
- # When setting, the description may be provided as any of the following:
141
- # * A {Toys::WrappableString}.
142
- # * A normal String, which will be transformed into a
143
- # {Toys::WrappableString} using spaces as word delimiters.
144
- # * An Array of String, which will be transformed into a
145
- # {Toys::WrappableString} where each array element represents an
146
- # individual word for wrapping.
147
- #
148
- # @return [Toys::WrappableString]
149
- #
150
- attr_reader :desc
151
-
152
- ##
153
- # The long description strings.
154
- #
155
- # When reading, this is returned as an Array of {Toys::WrappableString}
156
- # representing the lines in the description.
157
- #
158
- # When setting, the description must be provided as an Array where *each
159
- # element* may be any of the following:
160
- # * A {Toys::WrappableString} representing one line.
161
- # * A normal String representing a line. This will be transformed into a
162
- # {Toys::WrappableString} using spaces as word delimiters.
163
- # * An Array of String representing a line. This will be transformed into
164
- # a {Toys::WrappableString} where each array element represents an
165
- # individual word for wrapping.
166
- #
167
- # @return [Array<Toys::WrappableString>]
168
- #
169
- attr_reader :long_desc
170
-
171
- ##
172
- # The handler for setting/updating the value.
173
- # @return [Proc]
174
- #
175
- attr_reader :handler
176
-
177
- ##
178
- # The proc that determines shell completions for the flag.
179
- # @return [Proc,Toys::Completion::Base]
180
- #
181
- attr_reader :flag_completion
182
-
183
- ##
184
- # The proc that determines shell completions for the value.
185
- # @return [Proc,Toys::Completion::Base]
186
- #
187
- attr_reader :value_completion
188
-
189
- ##
190
- # The type of flag.
191
- #
192
- # @return [:boolean] if the flag is a simple boolean switch
193
- # @return [:value] if the flag sets a value
194
- #
195
- attr_reader :flag_type
196
-
197
- ##
198
- # The type of value.
199
- #
200
- # @return [:required] if the flag type is `:value` and the value is
201
- # required.
202
- # @return [:optional] if the flag type is `:value` and the value is
203
- # optional.
204
- # @return [nil] if the flag type is not `:value`.
205
- #
206
- attr_reader :value_type
207
-
208
- ##
209
- # The string label for the value as it should display in help.
210
- # @return [String] The label
211
- # @return [nil] if the flag type is not `:value`.
212
- #
213
- attr_reader :value_label
214
-
215
- ##
216
- # The value delimiter, which may be `""`, `" "`, or `"="`.
217
- #
218
- # @return [String] The delimiter
219
- # @return [nil] if the flag type is not `:value`.
220
- #
221
- attr_reader :value_delim
222
-
223
- ##
224
- # The display name of this flag.
225
- # @return [String]
226
- #
227
- attr_reader :display_name
228
-
229
- ##
230
- # A string that can be used to sort this flag
231
- # @return [String]
232
- #
233
- attr_reader :sort_str
234
-
235
- ##
236
- # An array of Flag::Syntax including only short (single dash) flags.
237
- # @return [Array<Flag::Syntax>]
238
- #
239
- def short_flag_syntax
240
- @short_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == :short }
241
- end
242
-
243
- ##
244
- # An array of Flag::Syntax including only long (double-dash) flags.
245
- # @return [Array<Flag::Syntax>]
443
+ # Returns the flag group containing this flag
444
+ # @return [Toys::FlagGroup]
246
445
  #
247
- def long_flag_syntax
248
- @long_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == :long }
249
- end
446
+ attr_reader :group
250
447
 
251
448
  ##
252
- # The list of all effective flags used.
253
- # @return [Array<String>]
449
+ # Returns the key.
450
+ # @return [Symbol]
254
451
  #
255
- def effective_flags
256
- @effective_flags ||= flag_syntax.flat_map(&:flags)
257
- end
452
+ attr_reader :key
258
453
 
259
454
  ##
260
- # Look up the flag by string. Returns an object that indicates whether
261
- # the given string matched this flag, whether the match was unique, and
262
- # other pertinent information.
263
- #
264
- # @param str [String] Flag string to look up
265
- # @return [Toys::Flag::Resolution] Information about the match.
455
+ # Returns an array of Flag::Syntax for the flags.
456
+ # @return [Array<Toys::Flag::Syntax>]
266
457
  #
267
- def resolve(str)
268
- resolution = Resolution.new(str)
269
- flag_syntax.each do |fs|
270
- if fs.positive_flag == str
271
- resolution.add!(self, fs, false, true)
272
- elsif fs.negative_flag == str
273
- resolution.add!(self, fs, true, true)
274
- elsif fs.positive_flag.start_with?(str)
275
- resolution.add!(self, fs, false, false)
276
- elsif fs.negative_flag.to_s.start_with?(str)
277
- resolution.add!(self, fs, true, false)
278
- end
279
- end
280
- resolution
281
- end
458
+ attr_reader :flag_syntax
282
459
 
283
460
  ##
284
- # A list of canonical flag syntax strings.
285
- #
286
- # @return [Array<String>]
461
+ # Returns the effective acceptor.
462
+ # @return [Toys::Acceptor::Base]
287
463
  #
288
- def canonical_syntax_strings
289
- @canonical_syntax_strings ||= flag_syntax.map(&:canonical_str)
290
- end
464
+ attr_reader :acceptor
291
465
 
292
466
  ##
293
- # Whether this flag is active--that is, it has a nonempty flags list.
294
- #
295
- # @return [Boolean]
467
+ # Returns the default value, which may be `nil`.
468
+ # @return [Object]
296
469
  #
297
- def active?
298
- !effective_flags.empty?
299
- end
470
+ attr_reader :default
300
471
 
301
472
  ##
302
- # Set the short description string.
303
- #
304
- # See {#desc} for details.
305
- #
306
- # @param desc [Toys::WrappableString,String,Array<String>]
473
+ # The short description string.
307
474
  #
308
- def desc=(desc)
309
- @desc = WrappableString.make(desc)
310
- end
311
-
312
- ##
313
- # Set the long description strings.
475
+ # When reading, this is always returned as a {Toys::WrappableString}.
314
476
  #
315
- # See {#long_desc} for details.
477
+ # When setting, the description may be provided as any of the following:
478
+ # * A {Toys::WrappableString}.
479
+ # * A normal String, which will be transformed into a
480
+ # {Toys::WrappableString} using spaces as word delimiters.
481
+ # * An Array of String, which will be transformed into a
482
+ # {Toys::WrappableString} where each array element represents an
483
+ # individual word for wrapping.
316
484
  #
317
- # @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
485
+ # @return [Toys::WrappableString]
318
486
  #
319
- def long_desc=(long_desc)
320
- @long_desc = WrappableString.make_array(long_desc)
321
- end
487
+ attr_reader :desc
322
488
 
323
489
  ##
324
- # Append long description strings.
325
- #
326
- # You must pass an array of lines in the long description. See {#long_desc}
327
- # for details on how each line may be represented.
328
- #
329
- # @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
330
- # @return [self]
490
+ # The long description strings.
331
491
  #
332
- def append_long_desc(long_desc)
333
- @long_desc.concat(WrappableString.make_array(long_desc))
334
- self
335
- end
336
-
337
- private
338
-
339
- def resolve_handler(handler)
340
- case handler
341
- when ::Proc
342
- handler
343
- when nil, :default
344
- DEFAULT_HANDLER
345
- when :set
346
- SET_HANDLER
347
- when :push, :append
348
- PUSH_HANDLER
349
- else
350
- raise ToolDefinitionError, "Unknown handler: #{handler.inspect}"
351
- end
352
- end
353
-
354
- def create_flag_completion(spec)
355
- spec =
356
- case spec
357
- when nil, :default
358
- {"": DefaultCompletion, flag: self}
359
- when ::Hash
360
- spec[:""].nil? ? spec.merge({"": DefaultCompletion, flag: self}) : spec
361
- else
362
- spec
363
- end
364
- Completion.create(spec, **{})
365
- end
366
-
367
- def create_default_flag
368
- key_str = key.to_s
369
- flag_str =
370
- if key_str.length == 1
371
- "-#{key_str}" if key_str =~ /[a-zA-Z0-9\?]/
372
- elsif key_str.length > 1
373
- key_str = key_str.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "").sub(/^-+/, "")
374
- "--#{key_str}" unless key_str.empty?
375
- end
376
- if flag_str
377
- needs_val = @value_completion != Completion::EMPTY ||
378
- ![::Object, ::TrueClass, ::FalseClass].include?(@acceptor.well_known_spec) ||
379
- ![nil, true, false].include?(@default)
380
- flag_str = "#{flag_str} VALUE" if needs_val
381
- @flag_syntax << Syntax.new(flag_str)
382
- end
383
- end
384
-
385
- def remove_used_flags(used_flags, report_collisions)
386
- return if !used_flags && !report_collisions
387
- @flag_syntax.select! do |fs|
388
- fs.flags.all? do |f|
389
- collision = used_flags&.include?(f)
390
- if collision && report_collisions
391
- raise ToolDefinitionError,
392
- "Cannot use flag #{f.inspect} because it is already assigned or reserved."
393
- end
394
- !collision
395
- end
396
- end
397
- used_flags&.concat(effective_flags.uniq)
398
- end
399
-
400
- def canonicalize
401
- @flag_type = nil
402
- @value_type = nil
403
- @value_label = nil
404
- @value_delim = " "
405
- short_flag_syntax.reverse_each do |flag|
406
- analyze_flag_syntax(flag)
407
- end
408
- long_flag_syntax.reverse_each do |flag|
409
- analyze_flag_syntax(flag)
410
- end
411
- @flag_type ||= :boolean
412
- @value_type ||= :required if @flag_type == :value
413
- flag_syntax.each do |flag|
414
- flag.configure_canonical(@flag_type, @value_type, @value_label, @value_delim)
415
- end
416
- end
417
-
418
- def analyze_flag_syntax(flag)
419
- return if flag.flag_type.nil?
420
- if !@flag_type.nil? && @flag_type != flag.flag_type
421
- raise ToolDefinitionError, "Cannot have both value and boolean flags for #{key.inspect}"
422
- end
423
- @flag_type = flag.flag_type
424
- return unless @flag_type == :value
425
- if !@value_type.nil? && @value_type != flag.value_type
426
- raise ToolDefinitionError,
427
- "Cannot have both required and optional values for flag #{key.inspect}"
428
- end
429
- @value_type = flag.value_type
430
- @value_label = flag.value_label
431
- @value_delim = flag.value_delim
432
- end
433
-
434
- def summarize(name)
435
- @display_name =
436
- name ||
437
- long_flag_syntax.first&.canonical_str ||
438
- short_flag_syntax.first&.canonical_str ||
439
- key.to_s
440
- @sort_str =
441
- long_flag_syntax.first&.sort_str ||
442
- short_flag_syntax.first&.sort_str ||
443
- ""
444
- end
445
-
446
- ##
447
- # Representation of a single flag.
492
+ # When reading, this is returned as an Array of {Toys::WrappableString}
493
+ # representing the lines in the description.
448
494
  #
449
- class Syntax
450
- # rubocop:disable Style/PerlBackrefs
451
-
452
- ##
453
- # Parse flag syntax
454
- # @param str [String] syntax.
455
- #
456
- def initialize(str)
457
- case str
458
- when /\A(-([\?\w]))\z/
459
- setup(str, $1, nil, $1, $2, :short, nil, nil, nil, nil)
460
- when /\A(-([\?\w]))( ?)\[(\w+)\]\z/
461
- setup(str, $1, nil, $1, $2, :short, :value, :optional, $3, $4)
462
- when /\A(-([\?\w]))\[( )(\w+)\]\z/
463
- setup(str, $1, nil, $1, $2, :short, :value, :optional, $3, $4)
464
- when /\A(-([\?\w]))( ?)(\w+)\z/
465
- setup(str, $1, nil, $1, $2, :short, :value, :required, $3, $4)
466
- when /\A--\[no-\](\w[\?\w-]*)\z/
467
- setup(str, "--#{$1}", "--no-#{$1}", str, $1, :long, :boolean, nil, nil, nil)
468
- when /\A(--(\w[\?\w-]*))\z/
469
- setup(str, $1, nil, $1, $2, :long, nil, nil, nil, nil)
470
- when /\A(--(\w[\?\w-]*))([= ])\[(\w+)\]\z/
471
- setup(str, $1, nil, $1, $2, :long, :value, :optional, $3, $4)
472
- when /\A(--(\w[\?\w-]*))\[([= ])(\w+)\]\z/
473
- setup(str, $1, nil, $1, $2, :long, :value, :optional, $3, $4)
474
- when /\A(--(\w[\?\w-]*))([= ])(\w+)\z/
475
- setup(str, $1, nil, $1, $2, :long, :value, :required, $3, $4)
476
- else
477
- raise ToolDefinitionError, "Illegal flag: #{str.inspect}"
478
- end
479
- end
480
-
481
- # rubocop:enable Style/PerlBackrefs
482
-
483
- ##
484
- # The original string that was parsed to produce this syntax.
485
- # @return [String]
486
- #
487
- attr_reader :original_str
488
-
489
- ##
490
- # The flags (without values) corresponding to this syntax.
491
- # @return [Array<String>]
492
- #
493
- attr_reader :flags
494
-
495
- ##
496
- # The flag (without values) corresponding to the normal "positive" form
497
- # of this flag.
498
- # @return [String]
499
- #
500
- attr_reader :positive_flag
495
+ # When setting, the description must be provided as an Array where *each
496
+ # element* may be any of the following:
497
+ # * A {Toys::WrappableString} representing one line.
498
+ # * A normal String representing a line. This will be transformed into a
499
+ # {Toys::WrappableString} using spaces as word delimiters.
500
+ # * An Array of String representing a line. This will be transformed into
501
+ # a {Toys::WrappableString} where each array element represents an
502
+ # individual word for wrapping.
503
+ #
504
+ # @return [Array<Toys::WrappableString>]
505
+ #
506
+ attr_reader :long_desc
501
507
 
502
- ##
503
- # The flag (without values) corresponding to the "negative" form of this
504
- # flag, if any. i.e. if the original string was `"--[no-]abc"`, the
505
- # negative flag is `"--no-abc"`.
506
- # @return [String] The negative form.
507
- # @return [nil] if the flag has no negative form.
508
- #
509
- attr_reader :negative_flag
508
+ ##
509
+ # The handler for setting/updating the value.
510
+ # @return [Proc]
511
+ #
512
+ attr_reader :handler
510
513
 
511
- ##
512
- # The original string with the value (if any) stripped, but retaining
513
- # the `[no-]` prefix if present.
514
- # @return [String]
515
- #
516
- attr_reader :str_without_value
514
+ ##
515
+ # The proc that determines shell completions for the flag.
516
+ # @return [Proc,Toys::Completion::Base]
517
+ #
518
+ attr_reader :flag_completion
517
519
 
518
- ##
519
- # A string used to sort this flag compared with others.
520
- # @return [String]
521
- #
522
- attr_reader :sort_str
520
+ ##
521
+ # The proc that determines shell completions for the value.
522
+ # @return [Proc,Toys::Completion::Base]
523
+ #
524
+ attr_reader :value_completion
523
525
 
524
- ##
525
- # The style of flag (`:long` or `:short`).
526
- # @return [:long] if this is a long flag (i.e. double hyphen)
527
- # @return [:short] if this is a short flag (i.e. single hyphen with one
528
- # character).
529
- #
530
- attr_reader :flag_style
526
+ ##
527
+ # The type of flag.
528
+ #
529
+ # @return [:boolean] if the flag is a simple boolean switch
530
+ # @return [:value] if the flag sets a value
531
+ #
532
+ attr_reader :flag_type
531
533
 
532
- ##
533
- # The type of flag (`:boolean` or `:value`)
534
- # @return [:boolean] if this is a boolean flag (i.e. no value)
535
- # @return [:value] if this flag takes a value (even if optional)
536
- #
537
- attr_reader :flag_type
534
+ ##
535
+ # The type of value.
536
+ #
537
+ # @return [:required] if the flag type is `:value` and the value is
538
+ # required.
539
+ # @return [:optional] if the flag type is `:value` and the value is
540
+ # optional.
541
+ # @return [nil] if the flag type is not `:value`.
542
+ #
543
+ attr_reader :value_type
538
544
 
539
- ##
540
- # The type of value (`:required` or `:optional`)
541
- # @return [:required] if this flag takes a required value
542
- # @return [:optional] if this flag takes an optional value
543
- # @return [nil] if this flag is a boolean flag
544
- #
545
- attr_reader :value_type
545
+ ##
546
+ # The string label for the value as it should display in help.
547
+ # @return [String] The label
548
+ # @return [nil] if the flag type is not `:value`.
549
+ #
550
+ attr_reader :value_label
546
551
 
547
- ##
548
- # The default delimiter used for the value of this flag. This could be
549
- # `""` or `" "` for a short flag, or `" "` or `"="` for a long flag.
550
- # @return [String] delimiter
551
- # @return [nil] if this flag is a boolean flag
552
- #
553
- attr_reader :value_delim
552
+ ##
553
+ # The value delimiter, which may be `""`, `" "`, or `"="`.
554
+ #
555
+ # @return [String] The delimiter
556
+ # @return [nil] if the flag type is not `:value`.
557
+ #
558
+ attr_reader :value_delim
554
559
 
555
- ##
556
- # The default "label" for the value. e.g. in `--abc=VAL` the label is
557
- # `"VAL"`.
558
- # @return [String] the label
559
- # @return [nil] if this flag is a boolean flag
560
- #
561
- attr_reader :value_label
560
+ ##
561
+ # The display name of this flag.
562
+ # @return [String]
563
+ #
564
+ attr_reader :display_name
562
565
 
563
- ##
564
- # A canonical string representing this flag's syntax, normalized to match
565
- # the type, delimiters, etc. settings of other flag syntaxes. This is
566
- # generally used in help strings to represent this flag.
567
- # @return [String]
568
- #
569
- attr_reader :canonical_str
566
+ ##
567
+ # A string that can be used to sort this flag
568
+ # @return [String]
569
+ #
570
+ attr_reader :sort_str
570
571
 
571
- ## @private
572
- def configure_canonical(canonical_flag_type, canonical_value_type,
573
- canonical_value_label, canonical_value_delim)
574
- return unless flag_type.nil?
575
- @flag_type = canonical_flag_type
576
- return unless canonical_flag_type == :value
577
- @value_type = canonical_value_type
578
- canonical_value_delim = "" if canonical_value_delim == "=" && flag_style == :short
579
- canonical_value_delim = "=" if canonical_value_delim == "" && flag_style == :long
580
- @value_delim = canonical_value_delim
581
- @value_label = canonical_value_label
582
- label = @value_type == :optional ? "[#{@value_label}]" : @value_label
583
- @canonical_str = "#{str_without_value}#{@value_delim}#{label}"
584
- end
572
+ ##
573
+ # An array of Flag::Syntax including only short (single dash) flags.
574
+ # @return [Array<Flag::Syntax>]
575
+ #
576
+ def short_flag_syntax
577
+ @short_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == :short }
578
+ end
585
579
 
586
- private
580
+ ##
581
+ # An array of Flag::Syntax including only long (double-dash) flags.
582
+ # @return [Array<Flag::Syntax>]
583
+ #
584
+ def long_flag_syntax
585
+ @long_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == :long }
586
+ end
587
587
 
588
- def setup(original_str, positive_flag, negative_flag, str_without_value, sort_str,
589
- flag_style, flag_type, value_type, value_delim, value_label)
590
- @original_str = original_str
591
- @positive_flag = positive_flag
592
- @negative_flag = negative_flag
593
- @flags = [positive_flag]
594
- @flags << negative_flag if negative_flag
595
- @str_without_value = str_without_value
596
- @sort_str = sort_str
597
- @flag_style = flag_style
598
- @flag_type = flag_type
599
- @value_type = value_type
600
- @value_delim = value_delim
601
- @value_label = value_label ? value_label.upcase : value_label
602
- @canonical_str = original_str
603
- end
588
+ ##
589
+ # The list of all effective flags used.
590
+ # @return [Array<String>]
591
+ #
592
+ def effective_flags
593
+ @effective_flags ||= flag_syntax.flat_map(&:flags)
604
594
  end
605
595
 
606
596
  ##
607
- # The result of looking up a flag by name.
597
+ # Look up the flag by string. Returns an object that indicates whether
598
+ # the given string matched this flag, whether the match was unique, and
599
+ # other pertinent information.
608
600
  #
609
- class Resolution
610
- ## @private
611
- def initialize(str)
612
- @string = str
613
- @flags = []
614
- @found_exact = false
601
+ # @param str [String] Flag string to look up
602
+ # @return [Toys::Flag::Resolution] Information about the match.
603
+ #
604
+ def resolve(str)
605
+ resolution = Resolution.new(str)
606
+ flag_syntax.each do |fs|
607
+ if fs.positive_flag == str
608
+ resolution.add!(self, fs, false, true)
609
+ elsif fs.negative_flag == str
610
+ resolution.add!(self, fs, true, true)
611
+ elsif fs.positive_flag.start_with?(str)
612
+ resolution.add!(self, fs, false, false)
613
+ elsif fs.negative_flag.to_s.start_with?(str)
614
+ resolution.add!(self, fs, true, false)
615
+ end
615
616
  end
617
+ resolution
618
+ end
616
619
 
617
- ##
618
- # The flag string that was looked up
619
- # @return [String]
620
- #
621
- attr_reader :string
622
-
623
- ##
624
- # Whether an exact match of the string was found
625
- # @return [Boolean]
626
- #
627
- def found_exact?
628
- @found_exact
629
- end
620
+ ##
621
+ # A list of canonical flag syntax strings.
622
+ #
623
+ # @return [Array<String>]
624
+ #
625
+ def canonical_syntax_strings
626
+ @canonical_syntax_strings ||= flag_syntax.map(&:canonical_str)
627
+ end
630
628
 
631
- ##
632
- # The number of matches that were found.
633
- # @return [Integer]
634
- #
635
- def count
636
- @flags.size
637
- end
629
+ ##
630
+ # Whether this flag is active--that is, it has a nonempty flags list.
631
+ #
632
+ # @return [Boolean]
633
+ #
634
+ def active?
635
+ !effective_flags.empty?
636
+ end
638
637
 
639
- ##
640
- # Whether a single unique match was found.
641
- # @return [Boolean]
642
- #
643
- def found_unique?
644
- @flags.size == 1
645
- end
638
+ ##
639
+ # Set the short description string.
640
+ #
641
+ # See {#desc} for details.
642
+ #
643
+ # @param desc [Toys::WrappableString,String,Array<String>]
644
+ #
645
+ def desc=(desc)
646
+ @desc = WrappableString.make(desc)
647
+ end
646
648
 
647
- ##
648
- # Whether no matches were found.
649
- # @return [Boolean]
650
- #
651
- def not_found?
652
- @flags.empty?
653
- end
649
+ ##
650
+ # Set the long description strings.
651
+ #
652
+ # See {#long_desc} for details.
653
+ #
654
+ # @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
655
+ #
656
+ def long_desc=(long_desc)
657
+ @long_desc = WrappableString.make_array(long_desc)
658
+ end
654
659
 
655
- ##
656
- # Whether multiple matches were found (i.e. ambiguous input).
657
- # @return [Boolean]
658
- #
659
- def found_multiple?
660
- @flags.size > 1
661
- end
660
+ ##
661
+ # Append long description strings.
662
+ #
663
+ # You must pass an array of lines in the long description. See {#long_desc}
664
+ # for details on how each line may be represented.
665
+ #
666
+ # @param long_desc [Array<Toys::WrappableString,String,Array<String>>]
667
+ # @return [self]
668
+ #
669
+ def append_long_desc(long_desc)
670
+ @long_desc.concat(WrappableString.make_array(long_desc))
671
+ self
672
+ end
662
673
 
663
- ##
664
- # Return the unique {Toys::Flag}, or `nil` if not found or
665
- # not unique.
666
- # @return [Toys::Flag,nil]
667
- #
668
- def unique_flag
669
- found_unique? ? @flags.first[0] : nil
670
- end
674
+ ##
675
+ # Create a Flag definition.
676
+ # This argument list is subject to change. Use {Toys::Flag.create} instead
677
+ # for a more stable interface.
678
+ #
679
+ # @private
680
+ #
681
+ def initialize(key, flags, used_flags, report_collisions, acceptor, handler, default,
682
+ flag_completion, value_completion, desc, long_desc, display_name, group)
683
+ @group = group
684
+ @key = key
685
+ @flag_syntax = Array(flags).map { |s| Syntax.new(s) }
686
+ @acceptor = Acceptor.create(acceptor)
687
+ @handler = resolve_handler(handler)
688
+ @desc = WrappableString.make(desc)
689
+ @long_desc = WrappableString.make_array(long_desc)
690
+ @default = default
691
+ @flag_completion = create_flag_completion(flag_completion)
692
+ @value_completion = Completion.create(value_completion, **{})
693
+ create_default_flag if @flag_syntax.empty?
694
+ remove_used_flags(used_flags, report_collisions)
695
+ canonicalize
696
+ summarize(display_name)
697
+ end
671
698
 
672
- ##
673
- # Return the unique {Toys::Flag::Syntax}, or `nil` if not found
674
- # or not unique.
675
- # @return [Toys::Flag::Syntax,nil]
676
- #
677
- def unique_flag_syntax
678
- found_unique? ? @flags.first[1] : nil
679
- end
699
+ private
680
700
 
681
- ##
682
- # Return whether the unique match was a hit on the negative (`--no-*`)
683
- # case, or `nil` if not found or not unique.
684
- # @return [Boolean,nil]
685
- #
686
- def unique_flag_negative?
687
- found_unique? ? @flags.first[2] : nil
701
+ def resolve_handler(handler)
702
+ case handler
703
+ when ::Proc
704
+ handler
705
+ when nil, :default
706
+ DEFAULT_HANDLER
707
+ when :set
708
+ SET_HANDLER
709
+ when :push, :append
710
+ PUSH_HANDLER
711
+ else
712
+ raise ToolDefinitionError, "Unknown handler: #{handler.inspect}"
688
713
  end
714
+ end
689
715
 
690
- ##
691
- # Returns an array of the matching full flag strings.
692
- # @return [Array<String>]
693
- #
694
- def matching_flag_strings
695
- @flags.map do |_flag, flag_syntax, negative|
696
- negative ? flag_syntax.negative_flag : flag_syntax.positive_flag
716
+ def create_flag_completion(spec)
717
+ spec =
718
+ case spec
719
+ when nil, :default
720
+ {"": DefaultCompletion, flag: self}
721
+ when ::Hash
722
+ spec[:""].nil? ? spec.merge({"": DefaultCompletion, flag: self}) : spec
723
+ else
724
+ spec
697
725
  end
698
- end
726
+ Completion.create(spec, **{})
727
+ end
699
728
 
700
- ## @private
701
- def add!(flag, flag_syntax, negative, exact)
702
- @flags = [] if exact && !found_exact?
703
- if exact || !found_exact?
704
- @flags << [flag, flag_syntax, negative]
705
- @found_exact = exact
729
+ def create_default_flag
730
+ key_str = key.to_s
731
+ flag_str =
732
+ if key_str.length == 1
733
+ "-#{key_str}" if key_str =~ /[a-zA-Z0-9?]/
734
+ elsif key_str.length > 1
735
+ key_str = key_str.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "").sub(/^-+/, "")
736
+ "--#{key_str}" unless key_str.empty?
706
737
  end
707
- self
738
+ if flag_str
739
+ needs_val = @value_completion != Completion::EMPTY ||
740
+ ![::Object, ::TrueClass, ::FalseClass].include?(@acceptor.well_known_spec) ||
741
+ ![nil, true, false].include?(@default)
742
+ flag_str = "#{flag_str} VALUE" if needs_val
743
+ @flag_syntax << Syntax.new(flag_str)
708
744
  end
745
+ end
709
746
 
710
- ## @private
711
- def merge!(other)
712
- raise "String mismatch" unless string == other.string
713
- other.instance_variable_get(:@flags).each do |flag, flag_syntax, negative|
714
- add!(flag, flag_syntax, negative, other.found_exact?)
747
+ def remove_used_flags(used_flags, report_collisions)
748
+ return if !used_flags && !report_collisions
749
+ @flag_syntax.select! do |fs|
750
+ fs.flags.all? do |f|
751
+ collision = used_flags&.include?(f)
752
+ if collision && report_collisions
753
+ raise ToolDefinitionError,
754
+ "Cannot use flag #{f.inspect} because it is already assigned or reserved."
755
+ end
756
+ !collision
715
757
  end
716
- self
717
758
  end
759
+ used_flags&.concat(effective_flags.uniq)
718
760
  end
719
761
 
720
- ##
721
- # A Completion that returns all possible flags associated with a
722
- # {Toys::Flag}.
723
- #
724
- class DefaultCompletion < Completion::Base
725
- ##
726
- # Create a completion given configuration options.
727
- #
728
- # @param flag [Toys::Flag] The flag definition.
729
- # @param include_short [Boolean] Whether to include short flags.
730
- # @param include_long [Boolean] Whether to include long flags.
731
- # @param include_negative [Boolean] Whether to include `--no-*` forms.
732
- #
733
- def initialize(flag:, include_short: true, include_long: true, include_negative: true)
734
- @flag = flag
735
- @include_short = include_short
736
- @include_long = include_long
737
- @include_negative = include_negative
762
+ def canonicalize
763
+ @flag_type = nil
764
+ @value_type = nil
765
+ @value_label = nil
766
+ @value_delim = " "
767
+ short_flag_syntax.reverse_each do |flag|
768
+ analyze_flag_syntax(flag)
738
769
  end
739
-
740
- ##
741
- # Whether to include short flags
742
- # @return [Boolean]
743
- #
744
- def include_short?
745
- @include_short
770
+ long_flag_syntax.reverse_each do |flag|
771
+ analyze_flag_syntax(flag)
746
772
  end
747
-
748
- ##
749
- # Whether to include long flags
750
- # @return [Boolean]
751
- #
752
- def include_long?
753
- @include_long
773
+ @flag_type ||= :boolean
774
+ @value_type ||= :required if @flag_type == :value
775
+ flag_syntax.each do |flag|
776
+ flag.configure_canonical(@flag_type, @value_type, @value_label, @value_delim)
754
777
  end
778
+ end
755
779
 
756
- ##
757
- # Whether to include negative long flags
758
- # @return [Boolean]
759
- #
760
- def include_negative?
761
- @include_negative
780
+ def analyze_flag_syntax(flag)
781
+ return if flag.flag_type.nil?
782
+ if !@flag_type.nil? && @flag_type != flag.flag_type
783
+ raise ToolDefinitionError, "Cannot have both value and boolean flags for #{key.inspect}"
762
784
  end
763
-
764
- ##
765
- # Returns candidates for the current completion.
766
- #
767
- # @param context [Toys::Completion::Context] the current completion
768
- # context including the string fragment.
769
- # @return [Array<Toys::Completion::Candidate>] an array of candidates
770
- #
771
- def call(context)
772
- results =
773
- if @include_short && @include_long && @include_negative
774
- @flag.effective_flags
775
- else
776
- collect_results
777
- end
778
- fragment = context.fragment
779
- results.find_all { |val| val.start_with?(fragment) }
780
- .map { |str| Completion::Candidate.new(str) }
785
+ @flag_type = flag.flag_type
786
+ return unless @flag_type == :value
787
+ if !@value_type.nil? && @value_type != flag.value_type
788
+ raise ToolDefinitionError,
789
+ "Cannot have both required and optional values for flag #{key.inspect}"
781
790
  end
791
+ @value_type = flag.value_type
792
+ @value_label = flag.value_label
793
+ @value_delim = flag.value_delim
794
+ end
782
795
 
783
- private
784
-
785
- def collect_results
786
- results = []
787
- if @include_short
788
- results += @flag.short_flag_syntax.map(&:positive_flag)
789
- end
790
- if @include_long
791
- results +=
792
- if @include_negative
793
- @flag.long_flag_syntax.flat_map(&:flags)
794
- else
795
- @flag.long_flag_syntax.map(&:positive_flag)
796
- end
797
- end
798
- results
799
- end
796
+ def summarize(name)
797
+ @display_name =
798
+ name ||
799
+ long_flag_syntax.first&.canonical_str ||
800
+ short_flag_syntax.first&.canonical_str ||
801
+ key.to_s
802
+ @sort_str =
803
+ long_flag_syntax.first&.sort_str ||
804
+ short_flag_syntax.first&.sort_str ||
805
+ ""
800
806
  end
801
807
  end
802
808
  end