toys-core 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,190 @@
1
+ # Copyright 2018 Daniel Azuma
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the copyright holder, nor the names of any other
14
+ # contributors to this software, may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ ;
29
+
30
+ module Toys
31
+ class Tool
32
+ ##
33
+ # An Acceptor validates and converts arguments. It is designed to be
34
+ # compatible with the OptionParser accept mechanism.
35
+ #
36
+ # First, an acceptor validates an argument via the {#match} method. This
37
+ # method should determine whether the argument is valid, and return
38
+ # information that will help with conversion of the argument.
39
+ #
40
+ # Second, an acceptor converts the argument from the input string to its
41
+ # final form via the {#convert} method.
42
+ #
43
+ # Finally, an acceptor has a name that may appear in help text for flags
44
+ # and arguments that use it.
45
+ #
46
+ class Acceptor
47
+ ##
48
+ # Create a base acceptor.
49
+ #
50
+ # The basic acceptor does not do any validation (i.e. it accepts all
51
+ # arguments). You may subclass this object and implement the {#match}
52
+ # method to change this behavior.
53
+ #
54
+ # The converter should take one or more arguments, the first of which is
55
+ # the entire argument string, and the others of which may be returned
56
+ # from validation. The converter should return the final converted value
57
+ # of the argument. If you do not provide a converter, this acceptor will
58
+ # set the final value to the input argument string by default.
59
+ # You may provide a converter either as a proc or a block.
60
+ #
61
+ # @param [String] name A visible name for the acceptor, shown in help.
62
+ # @param [Proc] converter A converter function. May also be given as a
63
+ # block.
64
+ #
65
+ def initialize(name, converter = nil, &block)
66
+ @name = name
67
+ @converter = converter || block
68
+ end
69
+
70
+ ##
71
+ # Name of the acceptor
72
+ # @return [String]
73
+ #
74
+ attr_reader :name
75
+
76
+ ##
77
+ # Name of the acceptor as a string
78
+ # @return [String]
79
+ #
80
+ def to_s
81
+ name.to_s
82
+ end
83
+
84
+ ##
85
+ # Validate the given input.
86
+ #
87
+ # You may override this method to specify a validation function. For a
88
+ # valid input, the function must return either the original argument
89
+ # string, or an array of which the first element is the original argument
90
+ # string, and the remaining elements may comprise additional information.
91
+ # All returned information is then passed to the conversion function.
92
+ # Note that a MatchInfo object is a legitimate return value since it
93
+ # duck-types the appropriate array.
94
+ #
95
+ # For an invalid input, you should return a falsy value.
96
+ #
97
+ # The default implementation simply returns the original argument string,
98
+ # indicating all inputs are valid.
99
+ #
100
+ # @param [String] str Input argument string
101
+ # @return [String,Array]
102
+ #
103
+ def match(str)
104
+ str
105
+ end
106
+
107
+ ##
108
+ # Convert the given input.
109
+ #
110
+ # @param [String] str Original argument string
111
+ # @param [Object...] extra Zero or more additional arguments comprising
112
+ # additional elements returned from the match function.
113
+ # @return [Object] The converted argument as it should be stored in the
114
+ # context data.
115
+ #
116
+ def convert(str, *extra)
117
+ @converter ? @converter.call(str, *extra) : str
118
+ end
119
+ end
120
+
121
+ ##
122
+ # An acceptor that uses a regex to validate input.
123
+ #
124
+ class PatternAcceptor < Acceptor
125
+ ##
126
+ # Create an acceptor.
127
+ #
128
+ # You must provide a regular expression as a validator. You may also
129
+ # provide a converter.
130
+ #
131
+ # @param [String] name A visible name for the acceptor, shown in help.
132
+ # @param [Regexp] regex Regular expression defining value values.
133
+ # @param [Proc] converter A converter function. May also be given as a
134
+ # block. Note that the converter will be passed all elements of
135
+ # the MatchInfo.
136
+ #
137
+ def initialize(name, regex, converter = nil, &block)
138
+ super(name, converter, &block)
139
+ @regex = regex
140
+ end
141
+
142
+ ##
143
+ # Overrides match to match from the given regex.
144
+ #
145
+ def match(str)
146
+ @regex.match(str)
147
+ end
148
+ end
149
+
150
+ ##
151
+ # An acceptor that recognizes a fixed set of values.
152
+ #
153
+ # You provide a list of valid values. The input argument string will be
154
+ # matched against the string forms of these valid values. If it matches,
155
+ # the converter will return the actual value.
156
+ #
157
+ # For example, you could pass `[:one, :two, 3]` as the set of values. If
158
+ # an argument of `"two"` is passed in, the converter will yield a final
159
+ # value of the symbol `:two`. If an argument of "3" is passed in, the
160
+ # converter will yield the integer `3`. If an argument of "three" is
161
+ # passed in, the match will fail.
162
+ #
163
+ class EnumAcceptor < Acceptor
164
+ ##
165
+ # Create an acceptor.
166
+ #
167
+ # @param [String] name A visible name for the acceptor, shown in help.
168
+ # @param [Array] values Valid values.
169
+ #
170
+ def initialize(name, values)
171
+ super(name)
172
+ @values = Array(values).map { |v| [v.to_s, v] }
173
+ end
174
+
175
+ ##
176
+ # Overrides match to find the value.
177
+ #
178
+ def match(str)
179
+ @values.find { |s, _e| s == str }
180
+ end
181
+
182
+ ##
183
+ # Overrides convert to return the original element.
184
+ #
185
+ def convert(_str, elem)
186
+ elem
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,130 @@
1
+ # Copyright 2018 Daniel Azuma
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the copyright holder, nor the names of any other
14
+ # contributors to this software, may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ ;
29
+
30
+ require "optparse"
31
+
32
+ require "toys/utils/wrappable_string"
33
+
34
+ module Toys
35
+ class Tool
36
+ ##
37
+ # Representation of a formal positional argument
38
+ #
39
+ class ArgDefinition
40
+ ##
41
+ # Create an ArgDefinition
42
+ # @private
43
+ #
44
+ def initialize(key, type, accept, default, desc, long_desc, display_name)
45
+ @key = key
46
+ @type = type
47
+ @accept = accept
48
+ @default = default
49
+ @desc = Utils::WrappableString.make(desc)
50
+ @long_desc = Utils::WrappableString.make_array(long_desc)
51
+ @display_name = display_name || key.to_s.tr("-", "_").gsub(/\W/, "").upcase
52
+ end
53
+
54
+ ##
55
+ # Returns the key.
56
+ # @return [Symbol]
57
+ #
58
+ attr_reader :key
59
+
60
+ ##
61
+ # Type of this argument.
62
+ # @return [:required,:optional,:remaining]
63
+ #
64
+ attr_reader :type
65
+
66
+ ##
67
+ # Returns the acceptor, which may be `nil`.
68
+ # @return [Object]
69
+ #
70
+ attr_accessor :accept
71
+
72
+ ##
73
+ # Returns the default value, which may be `nil`.
74
+ # @return [Object]
75
+ #
76
+ attr_reader :default
77
+
78
+ ##
79
+ # Returns the short description string.
80
+ # @return [Toys::Utils::WrappableString]
81
+ #
82
+ attr_reader :desc
83
+
84
+ ##
85
+ # Returns the long description strings as an array.
86
+ # @return [Array<Toys::Utils::WrappableString>]
87
+ #
88
+ attr_reader :long_desc
89
+
90
+ ##
91
+ # Returns the displayable name.
92
+ # @return [String]
93
+ #
94
+ attr_accessor :display_name
95
+
96
+ ##
97
+ # Process the given value through the acceptor.
98
+ # May raise an exception if the acceptor rejected the input.
99
+ #
100
+ # @param [String] input Input value
101
+ # @return [Object] Accepted value
102
+ #
103
+ def process_value(input)
104
+ return input unless accept
105
+ result = input
106
+ optparse = ::OptionParser.new
107
+ optparse.accept(accept) if accept.is_a?(Acceptor)
108
+ optparse.on("--abc=VALUE", accept) { |v| result = v }
109
+ optparse.parse(["--abc", input])
110
+ result
111
+ end
112
+
113
+ ##
114
+ # Set description
115
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc
116
+ #
117
+ def desc=(desc)
118
+ @desc = Utils::WrappableString.make(desc)
119
+ end
120
+
121
+ ##
122
+ # Set long description
123
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
124
+ #
125
+ def long_desc=(long_desc)
126
+ @long_desc = Utils::WrappableString.make_array(long_desc)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,322 @@
1
+ # Copyright 2018 Daniel Azuma
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the copyright holder, nor the names of any other
14
+ # contributors to this software, may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ ;
29
+
30
+ require "optparse"
31
+
32
+ require "toys/utils/wrappable_string"
33
+
34
+ module Toys
35
+ class Tool
36
+ ##
37
+ # Representation of a single flag.
38
+ #
39
+ class FlagSyntax
40
+ ##
41
+ # Parse flag syntax
42
+ # @param [String] str syntax.
43
+ #
44
+ def initialize(str)
45
+ case str
46
+ when /^(-[\?\w])$/
47
+ setup(str, [$1], $1, "-", nil, nil, nil, nil)
48
+ when /^(-[\?\w])( ?)\[(\w+)\]$/
49
+ setup(str, [$1], $1, "-", :value, :optional, $2, $3)
50
+ when /^(-[\?\w])( ?)(\w+)$/
51
+ setup(str, [$1], $1, "-", :value, :required, $2, $3)
52
+ when /^--\[no-\](\w[\?\w-]*)$/
53
+ setup(str, ["--#{$1}", "--no-#{$1}"], str, "--", :boolean, nil, nil, nil)
54
+ when /^(--\w[\?\w-]*)$/
55
+ setup(str, [$1], $1, "--", nil, nil, nil, nil)
56
+ when /^(--\w[\?\w-]*)([= ])\[(\w+)\]$/
57
+ setup(str, [$1], $1, "--", :value, :optional, $2, $3)
58
+ when /^(--\w[\?\w-]*)([= ])(\w+)$/
59
+ setup(str, [$1], $1, "--", :value, :required, $2, $3)
60
+ else
61
+ raise ToolDefinitionError, "Illegal flag: #{str.inspect}"
62
+ end
63
+ end
64
+
65
+ attr_reader :original_str
66
+ attr_reader :flags
67
+ attr_reader :str_without_value
68
+ attr_reader :flag_style
69
+ attr_reader :flag_type
70
+ attr_reader :value_type
71
+ attr_reader :value_delim
72
+ attr_reader :value_label
73
+ attr_reader :canonical_str
74
+
75
+ ## @private
76
+ def configure_canonical(canonical_flag_type, canonical_value_type,
77
+ canonical_value_label, canonical_value_delim)
78
+ return unless flag_type.nil?
79
+ @flag_type = canonical_flag_type
80
+ return unless canonical_flag_type == :value
81
+ @value_type = canonical_value_type
82
+ canonical_value_delim = "" if canonical_value_delim == "=" && flag_style == "-"
83
+ canonical_value_delim = "=" if canonical_value_delim == "" && flag_style == "--"
84
+ @value_delim = canonical_value_delim
85
+ @value_label = canonical_value_label
86
+ label = @value_type == :optional ? "[#{@value_label}]" : @value_label
87
+ @canonical_str = "#{str_without_value}#{@value_delim}#{label}"
88
+ end
89
+
90
+ private
91
+
92
+ def setup(original_str, flags, str_without_value, flag_style, flag_type, value_type,
93
+ value_delim, value_label)
94
+ @original_str = original_str
95
+ @flags = flags
96
+ @str_without_value = str_without_value
97
+ @flag_style = flag_style
98
+ @flag_type = flag_type
99
+ @value_type = value_type
100
+ @value_delim = value_delim
101
+ @value_label = value_label ? value_label.upcase : value_label
102
+ @canonical_str = original_str
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Representation of a formal set of flags that set a particular context
108
+ # key. The flags within a single FlagDefinition are synonyms.
109
+ #
110
+ class FlagDefinition
111
+ ##
112
+ # The default handler replaces the previous value.
113
+ # @return [Proc]
114
+ #
115
+ DEFAULT_HANDLER = ->(val, _prev) { val }
116
+
117
+ ##
118
+ # Create a FlagDefinition
119
+ # @private
120
+ #
121
+ def initialize(key, flags, used_flags, report_collisions, accept, handler, default)
122
+ @key = key
123
+ @flag_syntax = flags.map { |s| FlagSyntax.new(s) }
124
+ @accept = accept
125
+ @handler = handler || DEFAULT_HANDLER
126
+ @desc = Utils::WrappableString.make(desc)
127
+ @long_desc = Utils::WrappableString.make_array(long_desc)
128
+ @default = default
129
+ needs_val = (!accept.nil? && accept != ::TrueClass && accept != ::FalseClass) ||
130
+ (!default.nil? && default != true && default != false)
131
+ create_default_flag_if_needed(needs_val)
132
+ remove_used_flags(used_flags, report_collisions)
133
+ canonicalize(needs_val)
134
+ end
135
+
136
+ ##
137
+ # Returns the key.
138
+ # @return [Symbol]
139
+ #
140
+ attr_reader :key
141
+
142
+ ##
143
+ # Returns an array of FlagSyntax for the flags.
144
+ # @return [Array<FlagSyntax>]
145
+ #
146
+ attr_reader :flag_syntax
147
+
148
+ ##
149
+ # Returns the acceptor, which may be `nil`.
150
+ # @return [Object]
151
+ #
152
+ attr_reader :accept
153
+
154
+ ##
155
+ # Returns the default value, which may be `nil`.
156
+ # @return [Object]
157
+ #
158
+ attr_reader :default
159
+
160
+ ##
161
+ # Returns the short description string.
162
+ # @return [Toys::Utils::WrappableString]
163
+ #
164
+ attr_reader :desc
165
+
166
+ ##
167
+ # Returns the long description strings as an array.
168
+ # @return [Array<Toys::Utils::WrappableString>]
169
+ #
170
+ attr_reader :long_desc
171
+
172
+ ##
173
+ # Returns the handler for setting/updating the value.
174
+ # @return [Proc]
175
+ #
176
+ attr_reader :handler
177
+
178
+ ##
179
+ # The type of flag. Possible values are `:boolean` for a simple boolean
180
+ # switch, or `:value` for a flag that sets a value.
181
+ # @return [:boolean,:value]
182
+ #
183
+ attr_reader :flag_type
184
+
185
+ ##
186
+ # The type of value. Set to `:required` or `:optional` if the flag type
187
+ # is `:value`. Otherwise set to `nil`.
188
+ # @return [:required,:optional,nil]
189
+ #
190
+ attr_reader :value_type
191
+
192
+ ##
193
+ # The string label for the value as it should display in help, or `nil`
194
+ # if the flag type is not `:value`.
195
+ # @return [String,nil]
196
+ #
197
+ attr_reader :value_label
198
+
199
+ ##
200
+ # The value delimiter, which may be `""`, `" "`, or `"="`. Set to `nil`
201
+ # if the flag type is not `:value`.
202
+ # @return [String,nil]
203
+ #
204
+ attr_reader :value_delim
205
+
206
+ ##
207
+ # Returns an array of FlagSyntax including only single-dash flags
208
+ # @return [Array<FlagSyntax>]
209
+ #
210
+ def single_flag_syntax
211
+ @single_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == "-" }
212
+ end
213
+
214
+ ##
215
+ # Returns an array of FlagSyntax including only double-dash flags
216
+ # @return [Array<FlagSyntax>]
217
+ #
218
+ def double_flag_syntax
219
+ @double_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == "--" }
220
+ end
221
+
222
+ ##
223
+ # Returns the list of effective flags used.
224
+ # @return [Array<String>]
225
+ #
226
+ def effective_flags
227
+ @effective_flags ||= flag_syntax.map(&:flags).flatten
228
+ end
229
+
230
+ ##
231
+ # All optparser flags and acceptor if present
232
+ # @return [Array]
233
+ #
234
+ def optparser_info
235
+ @optparser_info ||= flag_syntax.map(&:canonical_str) + Array(accept)
236
+ end
237
+
238
+ ##
239
+ # Returns true if this flag is active. That is, it has a nonempty
240
+ # flags list.
241
+ # @return [Boolean]
242
+ #
243
+ def active?
244
+ !effective_flags.empty?
245
+ end
246
+
247
+ ##
248
+ # Set description
249
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc
250
+ #
251
+ def desc=(desc)
252
+ @desc = Utils::WrappableString.make(desc)
253
+ end
254
+
255
+ ##
256
+ # Set long description
257
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
258
+ #
259
+ def long_desc=(long_desc)
260
+ @long_desc = Utils::WrappableString.make_array(long_desc)
261
+ end
262
+
263
+ private
264
+
265
+ def create_default_flag_if_needed(needs_val)
266
+ return unless @flag_syntax.empty?
267
+ canonical_flag = key.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "").sub(/^-+/, "")
268
+ unless canonical_flag.empty?
269
+ flag = needs_val ? "--#{canonical_flag} VALUE" : "--#{canonical_flag}"
270
+ @flag_syntax << FlagSyntax.new(flag)
271
+ end
272
+ end
273
+
274
+ def remove_used_flags(used_flags, report_collisions)
275
+ @flag_syntax.select! do |fs|
276
+ fs.flags.all? do |f|
277
+ collision = used_flags.include?(f)
278
+ if collision && report_collisions
279
+ raise ToolDefinitionError,
280
+ "Cannot use flag #{f.inspect} because it is already assigned or reserved."
281
+ end
282
+ !collision
283
+ end
284
+ end
285
+ used_flags.concat(effective_flags.uniq)
286
+ end
287
+
288
+ def canonicalize(needs_val)
289
+ @flag_type = needs_val ? :value : nil
290
+ @value_type = nil
291
+ @value_label = needs_val ? "VALUE" : nil
292
+ @value_delim = " "
293
+ single_flag_syntax.each do |flag|
294
+ analyze_flag_syntax(flag)
295
+ end
296
+ double_flag_syntax.each do |flag|
297
+ analyze_flag_syntax(flag)
298
+ end
299
+ @flag_type ||= :boolean
300
+ flag_syntax.each do |flag|
301
+ flag.configure_canonical(@flag_type, @value_type, @value_label, @value_delim)
302
+ end
303
+ end
304
+
305
+ def analyze_flag_syntax(flag)
306
+ return if flag.flag_type.nil?
307
+ if !@flag_type.nil? && @flag_type != flag.flag_type
308
+ raise ToolDefinitionError, "Cannot have both value and boolean flags for #{key.inspect}"
309
+ end
310
+ @flag_type = flag.flag_type
311
+ return unless @flag_type == :value
312
+ if !@value_type.nil? && @value_type != flag.value_type
313
+ raise ToolDefinitionError,
314
+ "Cannot have both required and optional values for flag #{key.inspect}"
315
+ end
316
+ @value_type = flag.value_type
317
+ @value_label = flag.value_label
318
+ @value_delim = flag.value_delim
319
+ end
320
+ end
321
+ end
322
+ end