clasp-ruby 0.23.0.1 → 0.23.0.2
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.
- checksums.yaml +4 -4
- data/README.md +63 -52
- data/examples/cr-example.rb +16 -16
- data/examples/flag_and_option_specifications.md +25 -25
- data/examples/flag_and_option_specifications.rb +6 -6
- data/examples/show_usage_and_version.md +5 -5
- data/examples/show_usage_and_version.rb +1 -1
- data/examples/simple_command_line_no_specifications.rb +1 -1
- data/lib/clasp/arguments.rb +538 -537
- data/lib/clasp/clasp.rb +7 -7
- data/lib/clasp/cli.rb +140 -135
- data/lib/clasp/doc_.rb +3 -3
- data/lib/clasp/old_module.rb +3 -3
- data/lib/clasp/specifications.rb +337 -333
- data/lib/clasp/util/exceptions.rb +17 -17
- data/lib/clasp/util/value_parser.rb +97 -97
- data/lib/clasp/version.rb +15 -15
- data/lib/clasp-ruby.rb +3 -2
- data/lib/clasp.rb +3 -2
- data/test/scratch/test_list_command_line.rb +6 -6
- data/test/scratch/test_specifications.rb +14 -14
- data/test/scratch/test_usage.rb +6 -6
- data/test/scratch/test_usage_from_DATA.rb +1 -1
- data/test/scratch/test_usage_with_duplicate_specifications.rb +6 -6
- data/test/unit/tc_ARGV_rewrite.rb +36 -38
- data/test/unit/tc_arguments_1.rb +694 -694
- data/test/unit/tc_arguments_2.rb +52 -53
- data/test/unit/tc_arguments_3.rb +77 -77
- data/test/unit/tc_arguments_inspect.rb +55 -56
- data/test/unit/tc_cli.rb +4 -4
- data/test/unit/tc_default_value.rb +91 -91
- data/test/unit/tc_defaults_1.rb +38 -38
- data/test/unit/tc_examples_Arguments.rb +130 -132
- data/test/unit/tc_extras.rb +24 -26
- data/test/unit/tc_option_required.rb +38 -39
- data/test/unit/tc_option_value_aliases.rb +44 -44
- data/test/unit/tc_specifications.rb +7 -8
- data/test/unit/tc_typed_options.rb +204 -204
- data/test/unit/tc_usage.rb +112 -55
- data/test/unit/tc_with_action.rb +23 -24
- data/test/unit/ts_all.rb +1 -1
- metadata +5 -4
data/lib/clasp/arguments.rb
CHANGED
@@ -6,13 +6,13 @@
|
|
6
6
|
# CLASP.Ruby
|
7
7
|
#
|
8
8
|
# Created: 14th February 2014
|
9
|
-
# Updated:
|
9
|
+
# Updated: 20th January 2024
|
10
10
|
#
|
11
11
|
# Home: http://github.com/synesissoftware/CLASP.Ruby
|
12
12
|
#
|
13
13
|
# Author: Matthew Wilson
|
14
14
|
#
|
15
|
-
# Copyright (c) 2019-
|
15
|
+
# Copyright (c) 2019-2024, Matthew Wilson and Synesis Information Systems
|
16
16
|
# Copyright (c) 2014-2019, Matthew Wilson and Synesis Software
|
17
17
|
# All rights reserved.
|
18
18
|
#
|
@@ -63,714 +63,715 @@ module CLASP
|
|
63
63
|
# The main class for processing command-line arguments
|
64
64
|
class Arguments
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
66
|
+
# Class that represents a parsed flag
|
67
|
+
class FlagArgument
|
68
|
+
|
69
|
+
# @!visibility private
|
70
|
+
#
|
71
|
+
# [PRIVATE] This method is subject to changed between versions and
|
72
|
+
# should not be called directly from application code
|
73
|
+
def initialize(arg, given_index, given_name, resolved_name, argument_spec, given_hyphens, given_label, extras) # :nodoc:
|
74
|
+
|
75
|
+
@arg = arg
|
76
|
+
@given_index = given_index
|
77
|
+
@given_name = given_name
|
78
|
+
@argument_specification = argument_spec
|
79
|
+
@given_hyphens = given_hyphens
|
80
|
+
@given_label = given_label
|
81
|
+
@name = resolved_name || given_name
|
82
|
+
@extras = extras.nil? ? {} : extras
|
83
|
+
end
|
84
|
+
|
85
|
+
# (Integer) The command-line index of the argument
|
86
|
+
attr_reader :given_index
|
87
|
+
# (String) The given name of the argument as it appeared in the command-line
|
88
|
+
attr_reader :given_name
|
89
|
+
# (CLASP::FlagSpecification) The specification matching the argument, or +nil+
|
90
|
+
attr_reader :argument_specification
|
91
|
+
# (Integer) The number of hyphens of the argument as it appeared in the command-line
|
92
|
+
attr_reader :given_hyphens
|
93
|
+
# (String) The label of the argument as it appeared in the command-line
|
94
|
+
attr_reader :given_label
|
95
|
+
# (String) The resolved name of the argument
|
96
|
+
attr_reader :name
|
97
|
+
# (Object, Hash) The extras associated with the argument
|
98
|
+
attr_reader :extras
|
99
|
+
|
100
|
+
# [DEPRECATED] Use +argument_specification+
|
101
|
+
def argument_alias; @argument_specification; end
|
102
|
+
|
103
|
+
# (String) The string form of the flag, which is the same as +name+
|
104
|
+
def to_s
|
105
|
+
|
106
|
+
@name
|
107
|
+
end
|
108
|
+
|
109
|
+
# @!visibility private
|
110
|
+
def eql?(rhs) # :nodoc:
|
111
|
+
|
112
|
+
return false if rhs.nil?
|
113
|
+
|
114
|
+
# check name and aliases
|
115
|
+
return true if @name == rhs
|
116
|
+
return argument_specification.aliases.include? rhs if argument_specification
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!visibility private
|
121
|
+
def ==(rhs) # :nodoc:
|
122
|
+
|
123
|
+
return false if rhs.nil?
|
124
|
+
if not rhs.instance_of? String
|
125
|
+
|
126
|
+
rhs = rhs.name
|
127
|
+
end
|
128
|
+
eql? rhs
|
129
|
+
end
|
130
|
+
|
131
|
+
# A hash-code for this instance
|
132
|
+
def hash
|
133
|
+
|
134
|
+
@arg.hash
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Class that represents a parsed option
|
139
|
+
class OptionArgument
|
140
|
+
|
141
|
+
include ::CLASP::Util::ValueParser
|
142
|
+
|
143
|
+
# @!visibility private
|
144
|
+
#
|
145
|
+
# [PRIVATE] This method is subject to changed between versions and
|
146
|
+
# should not be called directly from application code
|
147
|
+
def initialize(arg, given_index, given_name, resolved_name, argument_spec, given_hyphens, given_label, value, extras) # :nodoc:
|
148
|
+
|
149
|
+
resolved_value = nil
|
150
|
+
|
151
|
+
if argument_spec
|
152
|
+
|
153
|
+
case constraint = (argument_spec.constraint || {})
|
154
154
|
=begin
|
155
|
-
|
155
|
+
when Proc
|
156
156
|
|
157
|
-
|
157
|
+
resolved_value = value_from_Proc(constraint, value, arg, given_index, given_name, argument_spec, extras)
|
158
158
|
=end
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
159
|
+
when Hash
|
160
|
+
|
161
|
+
if constraint.empty?
|
162
|
+
|
163
|
+
resolved_value = (value || '').empty? ? argument_spec.default_value : value
|
164
|
+
else
|
165
|
+
|
166
|
+
resolved_value = value_from_Hash(constraint, value, arg, given_index, given_name, argument_spec, extras)
|
167
|
+
end
|
168
|
+
else
|
169
|
+
|
170
|
+
warn "unexpected constraint on argument specification #{argument_spec} when parsing argument '#{arg}'"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
|
174
|
+
resolved_value = value
|
175
|
+
end
|
176
|
+
|
177
|
+
@arg = arg
|
178
|
+
@given_index = given_index
|
179
|
+
@given_name = given_name
|
180
|
+
@argument_specification = argument_spec
|
181
|
+
@given_hyphens = given_hyphens
|
182
|
+
@given_label = given_label
|
183
|
+
@given_value = value
|
184
|
+
@value = resolved_value
|
185
|
+
@name = resolved_name || given_name
|
186
|
+
@extras = extras.nil? ? {} : extras
|
187
|
+
end
|
188
|
+
|
189
|
+
# (Integer) The command-line index of the argument
|
190
|
+
attr_reader :given_index
|
191
|
+
# (String) The given name of the argument as it appeared in the command-line
|
192
|
+
attr_reader :given_name
|
193
|
+
# (CLASP::OptionSpecification) The specification matching the argument, or +nil+
|
194
|
+
attr_reader :argument_specification
|
195
|
+
# (Integer) The number of hyphens of the argument as it appeared in the command-line
|
196
|
+
attr_reader :given_hyphens
|
197
|
+
# (String) The label of the argument as it appeared in the command-line
|
198
|
+
attr_reader :given_label
|
199
|
+
# (String) The resolved name of the argument
|
200
|
+
attr_reader :name
|
201
|
+
# (String) The given value of the option
|
202
|
+
attr_reader :given_value
|
203
|
+
# (????) The value of the option, which may be of a type other than string subject to the option specification's constraint
|
204
|
+
attr_reader :value
|
205
|
+
# (Object, Hash) The extras associated with the argument
|
206
|
+
attr_reader :extras
|
207
|
+
|
208
|
+
# [DEPRECATED] Use +argument_specification+
|
209
|
+
def argument_alias; @argument_specification; end
|
210
|
+
|
211
|
+
# @!visibility private
|
212
|
+
def eql?(rhs) # :nodoc:
|
213
|
+
|
214
|
+
return false if rhs.nil?
|
215
|
+
|
216
|
+
# check name and aliases
|
217
|
+
return true if @name == rhs
|
218
|
+
return argument_specification.aliases.include? rhs if argument_specification
|
219
|
+
false
|
220
|
+
end
|
221
221
|
|
222
|
-
|
223
|
-
|
222
|
+
# @!visibility private
|
223
|
+
def ==(rhs) # :nodoc:
|
224
224
|
|
225
|
-
|
226
|
-
|
225
|
+
return false if rhs.nil?
|
226
|
+
if not rhs.instance_of? String
|
227
227
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
228
|
+
rhs = rhs.name
|
229
|
+
end
|
230
|
+
eql? rhs
|
231
|
+
end
|
232
232
|
|
233
|
-
|
234
|
-
|
233
|
+
# A hash-code for this instance
|
234
|
+
def hash
|
235
235
|
|
236
|
-
|
237
|
-
|
236
|
+
@arg.hash
|
237
|
+
end
|
238
238
|
|
239
|
-
|
240
|
-
|
239
|
+
# (String) The string form of the flag, which is the same as +name+=+value+
|
240
|
+
def to_s
|
241
241
|
|
242
|
-
|
243
|
-
|
244
|
-
|
242
|
+
"#{name}=#{value}"
|
243
|
+
end
|
244
|
+
end
|
245
245
|
|
246
|
-
|
247
|
-
|
246
|
+
# ######################
|
247
|
+
# Construction
|
248
248
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
249
|
+
# Loads an instance of the class, as specified by +source+, according to the given parameters
|
250
|
+
#
|
251
|
+
# See the documentation for the ::CLASP module for examples
|
252
|
+
#
|
253
|
+
# === Signature
|
254
|
+
#
|
255
|
+
# * *Parameters:*
|
256
|
+
# - +argv+ (+Array+) The arguments array. May not be +nil+. Defaults to +ARGV+
|
257
|
+
# - +source+ (+Hash+, +IO+) The arguments specification, either as a Hash or an instance of an IO-implementing type containing a YAML specification
|
258
|
+
# - +options+ An options hash, containing any of the following options
|
259
|
+
#
|
260
|
+
# * *Options:*
|
261
|
+
# - +:mutate_argv+ (+Boolean+) Determines if the library should mutate +argv+. Defaults to +true+. This is essential when using CLASP in conjunction with <tt>$\<</tt>
|
262
|
+
#
|
263
|
+
def self.load(argv, source, options = {}) # :yields: An instance of +CLASP::Arguments+
|
264
264
|
|
265
|
-
|
265
|
+
options ||= {}
|
266
266
|
|
267
|
-
|
267
|
+
specs = load_specifications(source, options)
|
268
268
|
|
269
|
-
|
270
|
-
|
269
|
+
self.new argv, specs, options
|
270
|
+
end
|
271
271
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
272
|
+
# Loads the specifications as specified by +source+, according to the given parameters
|
273
|
+
#
|
274
|
+
# === Signature
|
275
|
+
#
|
276
|
+
# * *Parameters:*
|
277
|
+
# - +source+ (+Hash+, +IO+) The arguments specification, either as a Hash or an instance of an IO-implementing type containing a YAML specification
|
278
|
+
# - +options+ An options hash, containing any of the following options
|
279
|
+
def self.load_specifications(source, options = {}) # :yields: An array of specification instances
|
280
280
|
|
281
|
-
|
281
|
+
options ||= {}
|
282
282
|
|
283
|
-
|
283
|
+
h = nil
|
284
284
|
|
285
|
-
|
286
|
-
|
285
|
+
case source
|
286
|
+
when ::IO
|
287
287
|
|
288
|
-
|
289
|
-
|
288
|
+
h = YAML.load(source.read)
|
289
|
+
when ::Hash
|
290
290
|
|
291
|
-
|
292
|
-
|
291
|
+
h = source
|
292
|
+
else
|
293
293
|
|
294
|
-
|
294
|
+
if source.respond_to?(:to_hash)
|
295
295
|
|
296
|
-
|
297
|
-
|
296
|
+
h = source.to_hash
|
297
|
+
else
|
298
298
|
|
299
|
-
|
300
|
-
|
301
|
-
|
299
|
+
raise TypeError, "#{self}.#{__method__}() 'source' argument must be a #{::Hash}, or an object implementing #{::IO}, or a type implementing to_hash'"
|
300
|
+
end
|
301
|
+
end
|
302
302
|
|
303
|
-
|
303
|
+
specs = []
|
304
304
|
|
305
|
-
|
306
|
-
|
305
|
+
_clasp = h['clasp'] or raise ArgumentError, "missing top-level 'clasp' element in load configuration"
|
306
|
+
::Hash === _clasp or raise ArgumentError, "top-level 'clasp' element must be a #{::Hash}"
|
307
307
|
|
308
|
-
|
309
|
-
|
308
|
+
_specs = (_clasp['arg-specs'] || _clasp['specifications'] || _clasp['aliases']) or raise ArgumentError, "missing element 'clasp/specifications'"
|
309
|
+
::Array === _specs or raise ArgumentError, "top-level 'specifications' element must be a #{::Hash}"
|
310
310
|
|
311
|
-
|
311
|
+
_specs.each do |_spec|
|
312
312
|
|
313
|
-
|
314
|
-
|
313
|
+
case _spec
|
314
|
+
when ::Hash
|
315
315
|
|
316
|
-
|
316
|
+
# TODO: make a utility function and shrink all the following
|
317
317
|
|
318
|
-
|
318
|
+
_spec.each do |_arg_type, _details|
|
319
319
|
|
320
|
-
|
321
|
-
|
320
|
+
case _arg_type
|
321
|
+
when 'flag', :flag
|
322
322
|
|
323
|
-
|
323
|
+
_name = _details['name']
|
324
324
|
|
325
|
-
|
325
|
+
unless _name
|
326
326
|
|
327
|
-
|
328
|
-
|
327
|
+
warn "flag specification missing required 'name' field"
|
328
|
+
else
|
329
329
|
|
330
|
-
|
331
|
-
|
332
|
-
|
330
|
+
_alias = _details['alias']
|
331
|
+
_aliases = _details['aliases']
|
332
|
+
_help = _details['help'] || _details['description']
|
333
333
|
|
334
|
-
|
335
|
-
|
336
|
-
|
334
|
+
specs << CLASP.Flag(_name, alias: _alias, aliases: _aliases, help: _help)
|
335
|
+
end
|
336
|
+
when 'option', :option
|
337
337
|
|
338
|
-
|
338
|
+
_name = _details['name']
|
339
339
|
|
340
|
-
|
340
|
+
unless _name
|
341
341
|
|
342
|
-
|
343
|
-
|
342
|
+
warn "option specification missing required 'name' field"
|
343
|
+
else
|
344
344
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
345
|
+
_alias = _details['alias']
|
346
|
+
_aliases = _details['aliases']
|
347
|
+
_default_value = _details['default_value'] || _details['default']
|
348
|
+
_help = _details['help'] || _details['description']
|
349
|
+
_required = _details['required']
|
350
|
+
_required_message = _details['required_message']
|
351
|
+
_values_range = _details['values_range'] || _details['values']
|
352
352
|
|
353
|
-
|
354
|
-
|
355
|
-
|
353
|
+
specs << CLASP.Option(_name, alias: _alias, aliases: _aliases, default_value: _default_value, help: _help, required: _required, required_message: _required_message, values_range: _values_range)
|
354
|
+
end
|
355
|
+
when 'alias', :alias
|
356
356
|
|
357
|
-
|
357
|
+
_resolved = _details['resolved']
|
358
358
|
|
359
|
-
|
359
|
+
unless _resolved
|
360
360
|
|
361
|
-
|
362
|
-
|
361
|
+
warn "alias specification missing required 'resolved' field"
|
362
|
+
else
|
363
363
|
|
364
|
-
|
365
|
-
|
364
|
+
_alias = _details['alias']
|
365
|
+
_aliases = _details['aliases']
|
366
366
|
|
367
|
-
|
367
|
+
unless _alias || _aliases
|
368
368
|
|
369
|
-
|
370
|
-
|
369
|
+
warn "alias specification missing required 'alias' or 'aliases' field"
|
370
|
+
else
|
371
371
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
372
|
+
specs << CLASP.Flag(_resolved, alias: _alias, aliases: _aliases)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
else
|
376
376
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
377
|
+
warn "unknown arg-type '#{_arg_type}' specified"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
else
|
381
381
|
|
382
|
-
|
383
|
-
|
384
|
-
|
382
|
+
warn "non-#{::Hash} element in 'clasp/specifications': #{_spec} (of type #{_spec.class})"
|
383
|
+
end
|
384
|
+
end
|
385
385
|
|
386
|
-
|
387
|
-
|
386
|
+
specs
|
387
|
+
end
|
388
388
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
389
|
+
# Constructs an instance of the class, according to the given parameters
|
390
|
+
#
|
391
|
+
# See the documentation for the ::CLASP module for examples
|
392
|
+
#
|
393
|
+
# === Signature
|
394
|
+
#
|
395
|
+
# * *Parameters:*
|
396
|
+
# - +argv+ (+Array+) The arguments array. May not be +nil+. Defaults to +ARGV+
|
397
|
+
# - +specifications+ (+Array+) The specifications array. Defaults to +nil+. If none supplied, no aliasing will be performed
|
398
|
+
# - +options+ An options hash, containing any of the following options
|
399
|
+
#
|
400
|
+
# * *Options:*
|
401
|
+
# - +:mutate_argv+ (+Boolean+) Determines if the library should mutate +argv+. Defaults to +true+. This is essential when using CLASP in conjunction with <tt>$\<</tt>
|
402
|
+
#
|
403
|
+
def initialize(argv = ARGV, specifications = nil, options = {})
|
404
404
|
|
405
|
-
|
406
|
-
|
407
|
-
|
405
|
+
# have to do this name-swap, as 'options' has CLASP-specific
|
406
|
+
# meaning
|
407
|
+
init_opts, options = options.dup, nil
|
408
408
|
|
409
|
-
|
409
|
+
init_opts[:mutate_argv] = true unless init_opts.has_key? :mutate_argv
|
410
410
|
|
411
|
-
|
411
|
+
@program_name = init_opts[:program_name] || Arguments.derive_program_name_
|
412
412
|
|
413
|
-
|
414
|
-
|
415
|
-
|
413
|
+
@argv = argv
|
414
|
+
argv = argv.dup
|
415
|
+
@argv_original_copy = argv.dup.freeze
|
416
416
|
|
417
|
-
|
418
|
-
|
417
|
+
@specifications = specifications
|
418
|
+
@aliases = @specifications
|
419
419
|
|
420
|
-
|
420
|
+
specifications = nil if specifications and specifications.empty?
|
421
421
|
|
422
|
-
|
422
|
+
flags, options, values, double_slash_index = Arguments.parse_(argv, specifications)
|
423
423
|
|
424
|
-
|
424
|
+
[ flags, options, values ].each do |ar|
|
425
425
|
|
426
|
-
|
426
|
+
class << ar
|
427
427
|
|
428
|
-
|
429
|
-
|
428
|
+
undef :inspect
|
429
|
+
undef :to_s
|
430
430
|
|
431
|
-
|
431
|
+
def to_s
|
432
432
|
|
433
|
-
|
433
|
+
s = ''
|
434
434
|
|
435
|
-
|
436
|
-
|
437
|
-
|
435
|
+
s += '['
|
436
|
+
s += self.map { |v| %Q<"#{v}"> }.join(', ')
|
437
|
+
s += ']'
|
438
438
|
|
439
|
-
|
440
|
-
|
439
|
+
s
|
440
|
+
end
|
441
441
|
|
442
|
-
|
442
|
+
def inspect
|
443
443
|
|
444
|
-
|
444
|
+
s = ''
|
445
445
|
|
446
|
-
|
447
|
-
|
448
|
-
|
446
|
+
s += "#<#{self.class}:0x#{(object_id << 1).to_s(16)} ["
|
447
|
+
s += self.map { |v| v.inspect }.join(', ')
|
448
|
+
s += "]>"
|
449
449
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
450
|
+
s
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
454
|
|
455
|
-
|
456
|
-
|
457
|
-
|
455
|
+
@flags = flags.freeze
|
456
|
+
@options = options.freeze
|
457
|
+
@values = values.freeze
|
458
458
|
|
459
|
-
|
459
|
+
@double_slash_index = double_slash_index
|
460
460
|
|
461
|
-
|
462
|
-
|
461
|
+
# do argv-mutation, if required
|
462
|
+
if init_opts[:mutate_argv]
|
463
463
|
|
464
|
-
|
464
|
+
while not argv.empty?
|
465
465
|
|
466
|
-
|
467
|
-
|
466
|
+
argv.shift
|
467
|
+
end
|
468
468
|
|
469
|
-
|
469
|
+
@values.each do |v|
|
470
470
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
471
|
+
argv << v
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
475
|
|
476
|
-
|
477
|
-
|
478
|
-
|
476
|
+
private
|
477
|
+
# @!visibility private
|
478
|
+
def self.derive_program_name_ # :nodoc:
|
479
479
|
|
480
|
-
|
481
|
-
|
480
|
+
$0
|
481
|
+
end
|
482
482
|
|
483
|
-
|
484
|
-
|
483
|
+
# @!visibility private
|
484
|
+
def self.parse_(argv, specifications) # :nodoc:
|
485
485
|
|
486
|
-
|
487
|
-
|
488
|
-
|
486
|
+
flags = []
|
487
|
+
options = []
|
488
|
+
values = []
|
489
489
|
|
490
|
-
|
490
|
+
double_slash_index = nil
|
491
491
|
|
492
|
-
|
493
|
-
|
492
|
+
forced_value = false
|
493
|
+
pending_option = nil
|
494
494
|
|
495
|
-
|
495
|
+
argv.each_with_index do |arg, index|
|
496
496
|
|
497
|
-
|
497
|
+
if not forced_value
|
498
498
|
|
499
|
-
|
499
|
+
if '--' == arg
|
500
500
|
|
501
|
-
|
502
|
-
|
501
|
+
# all subsequent arguments are values
|
502
|
+
forced_value = true
|
503
503
|
|
504
|
-
|
504
|
+
double_slash_index = index if double_slash_index.nil?
|
505
505
|
|
506
|
-
|
507
|
-
|
506
|
+
next
|
507
|
+
end
|
508
508
|
|
509
|
-
|
510
|
-
|
509
|
+
# do regex test to see if option/flag/value
|
510
|
+
if arg =~ /^(-+)([^=]+)/
|
511
511
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
512
|
+
hyphens = $1
|
513
|
+
given_label = $2
|
514
|
+
given_name = "#$1#$2"
|
515
|
+
value = ($' and not $'.empty?) ? $'[1 ... $'.size] : nil
|
516
|
+
argument_spec = nil
|
517
|
+
resolved_name = nil
|
518
518
|
|
519
|
-
|
519
|
+
(specifications || []).each do |s|
|
520
520
|
|
521
|
-
|
521
|
+
if s.name == given_name or s.aliases.include? given_name
|
522
522
|
|
523
|
-
|
524
|
-
|
523
|
+
argument_spec = s
|
524
|
+
resolved_name = s.name
|
525
525
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
526
|
+
# need to check whether the alias is a default-option
|
527
|
+
# and, if so, expand out its name and value, and replace
|
528
|
+
# the name and (if none previously specified) the value
|
529
|
+
if resolved_name =~ /^(-+)([^=]+)=/
|
530
530
|
|
531
|
-
|
532
|
-
|
531
|
+
resolved_name = "#$1#$2"
|
532
|
+
value ||= $'
|
533
533
|
|
534
|
-
|
535
|
-
|
534
|
+
# now find the underlying (option) specification
|
535
|
+
specifications.each do |t|
|
536
536
|
|
537
|
-
|
537
|
+
if t.name == resolved_name or t.aliases.include? resolved_name
|
538
538
|
|
539
|
-
|
539
|
+
argument_spec = t
|
540
540
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
541
|
+
break
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
545
|
|
546
|
-
|
547
|
-
|
548
|
-
|
546
|
+
break
|
547
|
+
end
|
548
|
+
end
|
549
549
|
|
550
|
-
|
551
|
-
|
550
|
+
# Here we intercept and (potentially) cater to grouped flags
|
551
|
+
if not argument_spec and not value and specifications and 1 == hyphens.size
|
552
552
|
|
553
|
-
|
554
|
-
|
555
|
-
|
553
|
+
# Must match all
|
554
|
+
flag_aliases = []
|
555
|
+
given_label[0 ... given_label.size].each_char do |c|
|
556
556
|
|
557
|
-
|
557
|
+
new_flag = "-#{c.chr}"
|
558
558
|
|
559
|
-
|
559
|
+
flag_alias = nil
|
560
560
|
|
561
|
-
|
562
|
-
|
561
|
+
# special case where the flag's actual name is short form and found here
|
562
|
+
flag_alias ||= specifications.detect { |s| s.is_a?(CLASP::FlagSpecification) && s.name == new_flag }
|
563
563
|
|
564
|
-
|
565
|
-
|
564
|
+
# if not found as a flag, look in each specifications' aliases
|
565
|
+
flag_alias ||= specifications.detect { |s| s.aliases.include? new_flag }
|
566
566
|
|
567
|
-
|
567
|
+
if not flag_alias
|
568
568
|
|
569
|
-
|
570
|
-
|
571
|
-
|
569
|
+
flag_aliases = nil
|
570
|
+
break
|
571
|
+
else
|
572
572
|
|
573
|
-
|
574
|
-
|
575
|
-
|
573
|
+
flag_aliases << flag_alias
|
574
|
+
end
|
575
|
+
end
|
576
576
|
|
577
|
-
|
577
|
+
if flag_aliases
|
578
578
|
|
579
|
-
|
580
|
-
|
581
|
-
|
579
|
+
# got them all, so now have to process them all
|
580
|
+
# as normal. Note: is this susceptible to
|
581
|
+
# infinite recursion
|
582
582
|
|
583
|
-
|
584
|
-
|
583
|
+
# convert to argv and invoke
|
584
|
+
flags_argv = flag_aliases.map { |s| s.name }
|
585
585
|
|
586
|
-
|
586
|
+
grp_flags, grp_options, grp_value, grp_double_slash_index = Arguments.parse_(flags_argv, specifications)
|
587
587
|
|
588
|
-
|
589
|
-
|
588
|
+
grp_flags.map! { |f| FlagArgument.new(arg, index, given_name, f.name, f.argument_specification, hyphens.size, given_label, argument_spec ? argument_spec.extras : nil) }
|
589
|
+
grp_options.map! { |o| OptionArgument.new(arg, index, given_name, o.name, o.argument_specification, hyphens.size, given_label, o.value, argument_spec ? argument_spec.extras : nil) }
|
590
590
|
|
591
|
-
|
592
|
-
|
593
|
-
|
591
|
+
flags.push(*grp_flags)
|
592
|
+
options.push(*grp_options)
|
593
|
+
values.push(*grp_value)
|
594
594
|
|
595
|
-
|
596
|
-
|
597
|
-
|
595
|
+
next
|
596
|
+
end
|
597
|
+
end
|
598
598
|
|
599
|
-
|
599
|
+
if argument_spec and argument_spec.is_a? CLASP::OptionSpecification and not value
|
600
600
|
|
601
|
-
|
601
|
+
pending_option = {
|
602
602
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
603
|
+
arg: arg,
|
604
|
+
index: index,
|
605
|
+
given_name: given_name,
|
606
|
+
resolved_name: resolved_name,
|
607
|
+
argument_spec: argument_spec,
|
608
|
+
hyphens_size: hyphens.size,
|
609
|
+
given_label: given_label,
|
610
|
+
extras: argument_spec ? argument_spec.extras : nil,
|
611
|
+
}
|
612
|
+
elsif value
|
613
613
|
|
614
|
-
|
615
|
-
|
614
|
+
options << OptionArgument.new(arg, index, given_name, resolved_name, argument_spec, hyphens.size, given_label, value, argument_spec ? argument_spec.extras : nil)
|
615
|
+
else
|
616
616
|
|
617
|
-
|
618
|
-
|
617
|
+
flags << FlagArgument.new(arg, index, given_name, resolved_name, argument_spec, hyphens.size, given_label, argument_spec ? argument_spec.extras : nil)
|
618
|
+
end
|
619
619
|
|
620
|
-
|
621
|
-
|
622
|
-
|
620
|
+
next
|
621
|
+
end
|
622
|
+
end
|
623
623
|
|
624
|
-
|
624
|
+
if pending_option
|
625
625
|
|
626
|
-
|
626
|
+
value = forced_value ? nil : arg
|
627
627
|
|
628
|
-
|
628
|
+
options << OptionArgument.new(pending_option[:arg], pending_option[:index], pending_option[:given_name], pending_option[:resolved_name], pending_option[:argument_spec], pending_option[:hyphens_size], pending_option[:given_label], value, pending_option[:extras])
|
629
629
|
|
630
|
-
|
630
|
+
pending_option = nil
|
631
631
|
|
632
|
-
|
633
|
-
|
632
|
+
next unless forced_value
|
633
|
+
end
|
634
634
|
|
635
|
-
|
636
|
-
|
635
|
+
arg = arg.dup
|
636
|
+
arg_ix = ::Integer === index ? index : index.dup
|
637
637
|
|
638
|
-
|
638
|
+
arg.define_singleton_method(:given_index) { arg_ix }
|
639
639
|
|
640
|
-
|
641
|
-
|
640
|
+
values << arg
|
641
|
+
end
|
642
642
|
|
643
|
-
|
643
|
+
if pending_option
|
644
644
|
|
645
|
-
|
645
|
+
value = nil
|
646
646
|
|
647
|
-
|
647
|
+
options << OptionArgument.new(pending_option[:arg], pending_option[:index], pending_option[:given_name], pending_option[:resolved_name], pending_option[:argument_spec], pending_option[:hyphens_size], pending_option[:given_label], value, pending_option[:extras])
|
648
648
|
|
649
|
-
|
649
|
+
end
|
650
650
|
|
651
|
-
|
652
|
-
|
651
|
+
return flags, options, values, double_slash_index
|
652
|
+
end
|
653
653
|
|
654
|
-
|
655
|
-
|
654
|
+
# ######################
|
655
|
+
# Attributes
|
656
656
|
|
657
|
-
|
658
|
-
|
659
|
-
|
657
|
+
public
|
658
|
+
# (Array) a frozen array of specifications
|
659
|
+
attr_reader :specifications
|
660
660
|
|
661
|
-
|
662
|
-
|
661
|
+
# [DEPRECATED] Instead refer to +specifications+
|
662
|
+
attr_reader :aliases
|
663
663
|
|
664
|
-
|
665
|
-
|
664
|
+
# (Array) a frozen array of flags
|
665
|
+
attr_reader :flags
|
666
666
|
|
667
|
-
|
668
|
-
|
667
|
+
# (Array) a frozen array of options
|
668
|
+
attr_reader :options
|
669
669
|
|
670
|
-
|
671
|
-
|
670
|
+
# (Array) a frozen array of values
|
671
|
+
attr_reader :values
|
672
672
|
|
673
|
-
|
674
|
-
|
673
|
+
# (Integer, +nil+) index of the first '--', if present; +nil+ otherwise
|
674
|
+
attr_reader :double_slash_index
|
675
675
|
|
676
|
-
|
677
|
-
|
676
|
+
# (Array) the (possibly mutated) array of arguments instance passed to new
|
677
|
+
attr_reader :argv
|
678
678
|
|
679
|
-
|
680
|
-
|
679
|
+
# (Array) unchanged copy of the original array of arguments passed to new
|
680
|
+
attr_reader :argv_original_copy
|
681
681
|
|
682
|
-
|
683
|
-
|
682
|
+
# (String) The program name
|
683
|
+
attr_reader :program_name
|
684
684
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
685
|
+
# Finds the first unknown flag or option; +nil+ if all used
|
686
|
+
#
|
687
|
+
# === Signature
|
688
|
+
#
|
689
|
+
# * *Parameters:*
|
690
|
+
# - +options+ (Hash) options
|
691
|
+
#
|
692
|
+
# * *Options:*
|
693
|
+
# - +:specifications+ ([CLASP::Specification]) Array of specifications. If not specified, the instance's +specifications+ attribute is used
|
694
|
+
#
|
695
|
+
# === Return
|
696
|
+
# (CLASP::Arguments::OptionArgument) The first unknown option; +nil+ if none found
|
697
|
+
def find_first_unknown options = {}
|
698
698
|
|
699
|
-
|
699
|
+
option = {} if options.nil?
|
700
700
|
|
701
|
-
|
701
|
+
raise ArgumentError, "options must be nil or Hash - #{option.class} given" unless options.is_a? ::Hash
|
702
702
|
|
703
|
-
|
703
|
+
specifications = options[:specifications] || options[:aliases] || @specifications
|
704
704
|
|
705
|
-
|
705
|
+
raise ArgumentError, "specifications may not be nil" if specifications.nil?
|
706
706
|
|
707
|
-
|
707
|
+
flags.each do |f|
|
708
708
|
|
709
|
-
|
710
|
-
|
709
|
+
return f unless specifications.any? { |al| al.is_a?(::CLASP::FlagSpecification) && al.name == f.name }
|
710
|
+
end
|
711
711
|
|
712
|
-
|
712
|
+
self.options.each do |o|
|
713
713
|
|
714
|
-
|
715
|
-
|
714
|
+
return o unless specifications.any? { |al| al.is_a?(::CLASP::OptionSpecification) && al.name == o.name }
|
715
|
+
end
|
716
716
|
|
717
|
-
|
718
|
-
|
717
|
+
nil
|
718
|
+
end
|
719
719
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
720
|
+
# Searches for a flag that matches the given id, returning the flag if
|
721
|
+
# found; +nil+ otherwise
|
722
|
+
#
|
723
|
+
# === Signature
|
724
|
+
#
|
725
|
+
# * *Parameters:*
|
726
|
+
# - +id+ (String, CLASP::FlagArgument) The name of a flag, or the flag itself
|
727
|
+
#
|
728
|
+
# === Return
|
729
|
+
# (CLASP::Arguments::FlagArgument) The first flag matching +id+; +nil+ if none found
|
730
|
+
def find_flag(id)
|
731
731
|
|
732
|
-
|
732
|
+
flags.each do |flag|
|
733
733
|
|
734
|
-
|
735
|
-
|
734
|
+
return flag if flag == id
|
735
|
+
end
|
736
736
|
|
737
|
-
|
738
|
-
|
737
|
+
nil
|
738
|
+
end
|
739
739
|
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
740
|
+
# Searches for a option that matches the given id, returning the option
|
741
|
+
# if found; +nil+ otherwise
|
742
|
+
#
|
743
|
+
# === Signature
|
744
|
+
#
|
745
|
+
# * *Parameter:*
|
746
|
+
# - +id+ (String, CLASP::OptionArgument) The name of a option, or the option itself
|
747
|
+
#
|
748
|
+
# === Return
|
749
|
+
# (CLASP::Arguments::OptionArgument) The first option matching +id+; +nil+ if none found
|
750
|
+
def find_option(id)
|
751
751
|
|
752
|
-
|
752
|
+
options.each do |option|
|
753
753
|
|
754
|
-
|
755
|
-
|
754
|
+
return option if option == id
|
755
|
+
end
|
756
756
|
|
757
|
-
|
758
|
-
|
757
|
+
nil
|
758
|
+
end
|
759
759
|
|
760
|
-
|
761
|
-
|
760
|
+
# #################################################################### #
|
761
|
+
# backwards-compatible
|
762
762
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
763
|
+
# @!visibility private
|
764
|
+
Flag = FlagArgument # :nodoc:
|
765
|
+
# @!visibility private
|
766
|
+
Option = OptionArgument # :nodoc:
|
767
767
|
end # class Arguments
|
768
768
|
|
769
|
+
|
769
770
|
# ######################################################################## #
|
770
771
|
# module
|
771
772
|
|
772
773
|
end # module CLASP
|
773
774
|
|
774
|
-
# ############################## end of file ############################# #
|
775
775
|
|
776
|
+
# ############################## end of file ############################# #
|
776
777
|
|