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
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