clasp-ruby 0.22.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 +543 -531
- 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 +702 -656
- 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 +8 -7
data/lib/clasp/arguments.rb
CHANGED
@@ -6,12 +6,13 @@
|
|
6
6
|
# CLASP.Ruby
|
7
7
|
#
|
8
8
|
# Created: 14th February 2014
|
9
|
-
# Updated: 20th
|
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-2024, Matthew Wilson and Synesis Information Systems
|
15
16
|
# Copyright (c) 2014-2019, Matthew Wilson and Synesis Software
|
16
17
|
# All rights reserved.
|
17
18
|
#
|
@@ -62,704 +63,715 @@ module CLASP
|
|
62
63
|
# The main class for processing command-line arguments
|
63
64
|
class Arguments
|
64
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
|
-
|
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 || {})
|
153
154
|
=begin
|
154
|
-
|
155
|
+
when Proc
|
155
156
|
|
156
|
-
|
157
|
+
resolved_value = value_from_Proc(constraint, value, arg, given_index, given_name, argument_spec, extras)
|
157
158
|
=end
|
158
|
-
|
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
|
-
|
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
|
220
221
|
|
221
|
-
|
222
|
-
|
222
|
+
# @!visibility private
|
223
|
+
def ==(rhs) # :nodoc:
|
223
224
|
|
224
|
-
|
225
|
-
|
225
|
+
return false if rhs.nil?
|
226
|
+
if not rhs.instance_of? String
|
226
227
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
228
|
+
rhs = rhs.name
|
229
|
+
end
|
230
|
+
eql? rhs
|
231
|
+
end
|
231
232
|
|
232
|
-
|
233
|
-
|
233
|
+
# A hash-code for this instance
|
234
|
+
def hash
|
234
235
|
|
235
|
-
|
236
|
-
|
236
|
+
@arg.hash
|
237
|
+
end
|
237
238
|
|
238
|
-
|
239
|
-
|
239
|
+
# (String) The string form of the flag, which is the same as +name+=+value+
|
240
|
+
def to_s
|
240
241
|
|
241
|
-
|
242
|
-
|
243
|
-
|
242
|
+
"#{name}=#{value}"
|
243
|
+
end
|
244
|
+
end
|
244
245
|
|
245
|
-
|
246
|
-
|
246
|
+
# ######################
|
247
|
+
# Construction
|
247
248
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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+
|
263
264
|
|
264
|
-
|
265
|
+
options ||= {}
|
265
266
|
|
266
|
-
|
267
|
+
specs = load_specifications(source, options)
|
267
268
|
|
268
|
-
|
269
|
-
|
269
|
+
self.new argv, specs, options
|
270
|
+
end
|
270
271
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
279
280
|
|
280
|
-
|
281
|
+
options ||= {}
|
281
282
|
|
282
|
-
|
283
|
+
h = nil
|
283
284
|
|
284
|
-
|
285
|
-
|
285
|
+
case source
|
286
|
+
when ::IO
|
286
287
|
|
287
|
-
|
288
|
-
|
288
|
+
h = YAML.load(source.read)
|
289
|
+
when ::Hash
|
289
290
|
|
290
|
-
|
291
|
-
|
291
|
+
h = source
|
292
|
+
else
|
292
293
|
|
293
|
-
|
294
|
+
if source.respond_to?(:to_hash)
|
294
295
|
|
295
|
-
|
296
|
-
|
296
|
+
h = source.to_hash
|
297
|
+
else
|
297
298
|
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
301
302
|
|
302
|
-
|
303
|
+
specs = []
|
303
304
|
|
304
|
-
|
305
|
-
|
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}"
|
306
307
|
|
307
|
-
|
308
|
-
|
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}"
|
309
310
|
|
310
|
-
|
311
|
+
_specs.each do |_spec|
|
311
312
|
|
312
|
-
|
313
|
-
|
313
|
+
case _spec
|
314
|
+
when ::Hash
|
314
315
|
|
315
|
-
|
316
|
+
# TODO: make a utility function and shrink all the following
|
316
317
|
|
317
|
-
|
318
|
+
_spec.each do |_arg_type, _details|
|
318
319
|
|
319
|
-
|
320
|
-
|
320
|
+
case _arg_type
|
321
|
+
when 'flag', :flag
|
321
322
|
|
322
|
-
|
323
|
+
_name = _details['name']
|
323
324
|
|
324
|
-
|
325
|
+
unless _name
|
325
326
|
|
326
|
-
|
327
|
-
|
327
|
+
warn "flag specification missing required 'name' field"
|
328
|
+
else
|
328
329
|
|
329
|
-
|
330
|
-
|
331
|
-
|
330
|
+
_alias = _details['alias']
|
331
|
+
_aliases = _details['aliases']
|
332
|
+
_help = _details['help'] || _details['description']
|
332
333
|
|
333
|
-
|
334
|
-
|
335
|
-
|
334
|
+
specs << CLASP.Flag(_name, alias: _alias, aliases: _aliases, help: _help)
|
335
|
+
end
|
336
|
+
when 'option', :option
|
336
337
|
|
337
|
-
|
338
|
+
_name = _details['name']
|
338
339
|
|
339
|
-
|
340
|
+
unless _name
|
340
341
|
|
341
|
-
|
342
|
-
|
342
|
+
warn "option specification missing required 'name' field"
|
343
|
+
else
|
343
344
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
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']
|
351
352
|
|
352
|
-
|
353
|
-
|
354
|
-
|
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
|
355
356
|
|
356
|
-
|
357
|
+
_resolved = _details['resolved']
|
357
358
|
|
358
|
-
|
359
|
+
unless _resolved
|
359
360
|
|
360
|
-
|
361
|
-
|
361
|
+
warn "alias specification missing required 'resolved' field"
|
362
|
+
else
|
362
363
|
|
363
|
-
|
364
|
-
|
364
|
+
_alias = _details['alias']
|
365
|
+
_aliases = _details['aliases']
|
365
366
|
|
366
|
-
|
367
|
+
unless _alias || _aliases
|
367
368
|
|
368
|
-
|
369
|
-
|
369
|
+
warn "alias specification missing required 'alias' or 'aliases' field"
|
370
|
+
else
|
370
371
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
372
|
+
specs << CLASP.Flag(_resolved, alias: _alias, aliases: _aliases)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
else
|
375
376
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
377
|
+
warn "unknown arg-type '#{_arg_type}' specified"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
else
|
380
381
|
|
381
|
-
|
382
|
-
|
383
|
-
|
382
|
+
warn "non-#{::Hash} element in 'clasp/specifications': #{_spec} (of type #{_spec.class})"
|
383
|
+
end
|
384
|
+
end
|
384
385
|
|
385
|
-
|
386
|
-
|
386
|
+
specs
|
387
|
+
end
|
387
388
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
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 = {})
|
403
404
|
|
404
|
-
|
405
|
-
|
406
|
-
|
405
|
+
# have to do this name-swap, as 'options' has CLASP-specific
|
406
|
+
# meaning
|
407
|
+
init_opts, options = options.dup, nil
|
407
408
|
|
408
|
-
|
409
|
+
init_opts[:mutate_argv] = true unless init_opts.has_key? :mutate_argv
|
409
410
|
|
410
|
-
|
411
|
+
@program_name = init_opts[:program_name] || Arguments.derive_program_name_
|
411
412
|
|
412
|
-
|
413
|
-
|
414
|
-
|
413
|
+
@argv = argv
|
414
|
+
argv = argv.dup
|
415
|
+
@argv_original_copy = argv.dup.freeze
|
415
416
|
|
416
|
-
|
417
|
-
|
417
|
+
@specifications = specifications
|
418
|
+
@aliases = @specifications
|
418
419
|
|
419
|
-
|
420
|
+
specifications = nil if specifications and specifications.empty?
|
420
421
|
|
421
|
-
|
422
|
+
flags, options, values, double_slash_index = Arguments.parse_(argv, specifications)
|
422
423
|
|
423
|
-
|
424
|
+
[ flags, options, values ].each do |ar|
|
424
425
|
|
425
|
-
|
426
|
+
class << ar
|
426
427
|
|
427
|
-
|
428
|
-
|
428
|
+
undef :inspect
|
429
|
+
undef :to_s
|
429
430
|
|
430
|
-
|
431
|
+
def to_s
|
431
432
|
|
432
|
-
|
433
|
+
s = ''
|
433
434
|
|
434
|
-
|
435
|
-
|
436
|
-
|
435
|
+
s += '['
|
436
|
+
s += self.map { |v| %Q<"#{v}"> }.join(', ')
|
437
|
+
s += ']'
|
437
438
|
|
438
|
-
|
439
|
-
|
439
|
+
s
|
440
|
+
end
|
440
441
|
|
441
|
-
|
442
|
+
def inspect
|
442
443
|
|
443
|
-
|
444
|
+
s = ''
|
444
445
|
|
445
|
-
|
446
|
-
|
447
|
-
|
446
|
+
s += "#<#{self.class}:0x#{(object_id << 1).to_s(16)} ["
|
447
|
+
s += self.map { |v| v.inspect }.join(', ')
|
448
|
+
s += "]>"
|
448
449
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
450
|
+
s
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
453
454
|
|
454
|
-
|
455
|
-
|
456
|
-
|
455
|
+
@flags = flags.freeze
|
456
|
+
@options = options.freeze
|
457
|
+
@values = values.freeze
|
457
458
|
|
458
|
-
|
459
|
-
if init_opts[:mutate_argv]
|
459
|
+
@double_slash_index = double_slash_index
|
460
460
|
|
461
|
-
|
461
|
+
# do argv-mutation, if required
|
462
|
+
if init_opts[:mutate_argv]
|
462
463
|
|
463
|
-
|
464
|
-
end
|
464
|
+
while not argv.empty?
|
465
465
|
|
466
|
-
|
466
|
+
argv.shift
|
467
|
+
end
|
467
468
|
|
468
|
-
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
469
|
+
@values.each do |v|
|
472
470
|
|
473
|
-
|
474
|
-
|
475
|
-
|
471
|
+
argv << v
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
476
475
|
|
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
|
-
|
485
|
-
values = []
|
483
|
+
# @!visibility private
|
484
|
+
def self.parse_(argv, specifications) # :nodoc:
|
486
485
|
|
487
|
-
|
488
|
-
|
486
|
+
flags = []
|
487
|
+
options = []
|
488
|
+
values = []
|
489
489
|
|
490
|
-
|
490
|
+
double_slash_index = nil
|
491
491
|
|
492
|
-
|
492
|
+
forced_value = false
|
493
|
+
pending_option = nil
|
493
494
|
|
494
|
-
|
495
|
+
argv.each_with_index do |arg, index|
|
495
496
|
|
496
|
-
|
497
|
-
forced_value = true
|
498
|
-
next
|
499
|
-
end
|
497
|
+
if not forced_value
|
500
498
|
|
501
|
-
|
502
|
-
if arg =~ /^(-+)([^=]+)/
|
499
|
+
if '--' == arg
|
503
500
|
|
504
|
-
|
505
|
-
|
506
|
-
given_name = "#$1#$2"
|
507
|
-
value = ($' and not $'.empty?) ? $'[1 ... $'.size] : nil
|
508
|
-
argument_spec = nil
|
509
|
-
resolved_name = nil
|
501
|
+
# all subsequent arguments are values
|
502
|
+
forced_value = true
|
510
503
|
|
511
|
-
|
504
|
+
double_slash_index = index if double_slash_index.nil?
|
512
505
|
|
513
|
-
|
506
|
+
next
|
507
|
+
end
|
514
508
|
|
515
|
-
|
516
|
-
|
509
|
+
# do regex test to see if option/flag/value
|
510
|
+
if arg =~ /^(-+)([^=]+)/
|
517
511
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
522
518
|
|
523
|
-
|
524
|
-
value ||= $'
|
519
|
+
(specifications || []).each do |s|
|
525
520
|
|
526
|
-
|
527
|
-
specifications.each do |t|
|
521
|
+
if s.name == given_name or s.aliases.include? given_name
|
528
522
|
|
529
|
-
|
523
|
+
argument_spec = s
|
524
|
+
resolved_name = s.name
|
530
525
|
|
531
|
-
|
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 =~ /^(-+)([^=]+)=/
|
532
530
|
|
533
|
-
|
534
|
-
|
535
|
-
end
|
536
|
-
end
|
531
|
+
resolved_name = "#$1#$2"
|
532
|
+
value ||= $'
|
537
533
|
|
538
|
-
|
539
|
-
|
540
|
-
end
|
534
|
+
# now find the underlying (option) specification
|
535
|
+
specifications.each do |t|
|
541
536
|
|
542
|
-
|
543
|
-
if not argument_spec and not value and specifications and 1 == hyphens.size
|
537
|
+
if t.name == resolved_name or t.aliases.include? resolved_name
|
544
538
|
|
545
|
-
|
546
|
-
flag_aliases = []
|
547
|
-
given_label[0 ... given_label.size].each_char do |c|
|
539
|
+
argument_spec = t
|
548
540
|
|
549
|
-
|
541
|
+
break
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
550
545
|
|
551
|
-
|
546
|
+
break
|
547
|
+
end
|
548
|
+
end
|
552
549
|
|
553
|
-
|
554
|
-
|
550
|
+
# Here we intercept and (potentially) cater to grouped flags
|
551
|
+
if not argument_spec and not value and specifications and 1 == hyphens.size
|
555
552
|
|
556
|
-
|
557
|
-
|
553
|
+
# Must match all
|
554
|
+
flag_aliases = []
|
555
|
+
given_label[0 ... given_label.size].each_char do |c|
|
558
556
|
|
559
|
-
|
557
|
+
new_flag = "-#{c.chr}"
|
560
558
|
|
561
|
-
|
562
|
-
break
|
563
|
-
else
|
559
|
+
flag_alias = nil
|
564
560
|
|
565
|
-
|
566
|
-
|
567
|
-
end
|
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 }
|
568
563
|
|
569
|
-
|
564
|
+
# if not found as a flag, look in each specifications' aliases
|
565
|
+
flag_alias ||= specifications.detect { |s| s.aliases.include? new_flag }
|
570
566
|
|
571
|
-
|
572
|
-
# as normal. Note: is this susceptible to
|
573
|
-
# infinite recursion
|
567
|
+
if not flag_alias
|
574
568
|
|
575
|
-
|
576
|
-
|
569
|
+
flag_aliases = nil
|
570
|
+
break
|
571
|
+
else
|
577
572
|
|
578
|
-
|
573
|
+
flag_aliases << flag_alias
|
574
|
+
end
|
575
|
+
end
|
579
576
|
|
580
|
-
|
581
|
-
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) }
|
577
|
+
if flag_aliases
|
582
578
|
|
583
|
-
|
584
|
-
|
585
|
-
|
579
|
+
# got them all, so now have to process them all
|
580
|
+
# as normal. Note: is this susceptible to
|
581
|
+
# infinite recursion
|
586
582
|
|
587
|
-
|
588
|
-
|
589
|
-
end
|
583
|
+
# convert to argv and invoke
|
584
|
+
flags_argv = flag_aliases.map { |s| s.name }
|
590
585
|
|
591
|
-
|
586
|
+
grp_flags, grp_options, grp_value, grp_double_slash_index = Arguments.parse_(flags_argv, specifications)
|
592
587
|
|
593
|
-
|
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) }
|
594
590
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
resolved_name: resolved_name,
|
599
|
-
argument_spec: argument_spec,
|
600
|
-
hyphens_size: hyphens.size,
|
601
|
-
given_label: given_label,
|
602
|
-
extras: argument_spec ? argument_spec.extras : nil,
|
603
|
-
}
|
604
|
-
elsif value
|
591
|
+
flags.push(*grp_flags)
|
592
|
+
options.push(*grp_options)
|
593
|
+
values.push(*grp_value)
|
605
594
|
|
606
|
-
|
607
|
-
|
595
|
+
next
|
596
|
+
end
|
597
|
+
end
|
608
598
|
|
609
|
-
|
610
|
-
end
|
599
|
+
if argument_spec and argument_spec.is_a? CLASP::OptionSpecification and not value
|
611
600
|
|
612
|
-
|
613
|
-
end
|
614
|
-
end
|
601
|
+
pending_option = {
|
615
602
|
|
616
|
-
|
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
|
617
613
|
|
618
|
-
|
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
|
619
616
|
|
620
|
-
|
617
|
+
flags << FlagArgument.new(arg, index, given_name, resolved_name, argument_spec, hyphens.size, given_label, argument_spec ? argument_spec.extras : nil)
|
618
|
+
end
|
621
619
|
|
622
|
-
|
620
|
+
next
|
621
|
+
end
|
622
|
+
end
|
623
623
|
|
624
|
-
|
625
|
-
end
|
624
|
+
if pending_option
|
626
625
|
|
627
|
-
|
628
|
-
arg_ix = ::Integer === index ? index : index.dup
|
626
|
+
value = forced_value ? nil : arg
|
629
627
|
|
630
|
-
|
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])
|
631
629
|
|
632
|
-
|
633
|
-
end
|
630
|
+
pending_option = nil
|
634
631
|
|
635
|
-
|
632
|
+
next unless forced_value
|
633
|
+
end
|
636
634
|
|
637
|
-
|
635
|
+
arg = arg.dup
|
636
|
+
arg_ix = ::Integer === index ? index : index.dup
|
638
637
|
|
639
|
-
|
638
|
+
arg.define_singleton_method(:given_index) { arg_ix }
|
640
639
|
|
641
|
-
|
640
|
+
values << arg
|
641
|
+
end
|
642
642
|
|
643
|
-
|
644
|
-
end
|
643
|
+
if pending_option
|
645
644
|
|
646
|
-
|
647
|
-
# Attributes
|
645
|
+
value = nil
|
648
646
|
|
649
|
-
|
650
|
-
# (Array) a frozen array of specifications
|
651
|
-
attr_reader :specifications
|
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])
|
652
648
|
|
653
|
-
|
654
|
-
attr_reader :aliases
|
649
|
+
end
|
655
650
|
|
656
|
-
|
657
|
-
|
651
|
+
return flags, options, values, double_slash_index
|
652
|
+
end
|
658
653
|
|
659
|
-
|
660
|
-
|
654
|
+
# ######################
|
655
|
+
# Attributes
|
661
656
|
|
662
|
-
|
663
|
-
|
657
|
+
public
|
658
|
+
# (Array) a frozen array of specifications
|
659
|
+
attr_reader :specifications
|
664
660
|
|
665
|
-
|
666
|
-
|
661
|
+
# [DEPRECATED] Instead refer to +specifications+
|
662
|
+
attr_reader :aliases
|
667
663
|
|
668
|
-
|
669
|
-
|
664
|
+
# (Array) a frozen array of flags
|
665
|
+
attr_reader :flags
|
670
666
|
|
671
|
-
|
672
|
-
|
667
|
+
# (Array) a frozen array of options
|
668
|
+
attr_reader :options
|
673
669
|
|
674
|
-
|
675
|
-
|
676
|
-
# === Signature
|
677
|
-
#
|
678
|
-
# * *Parameters:*
|
679
|
-
# - +options+ (Hash) options
|
680
|
-
#
|
681
|
-
# * *Options:*
|
682
|
-
# - +:specifications+ ([CLASP::Specification]) Array of specifications. If not specified, the instance's +specifications+ attribute is used
|
683
|
-
#
|
684
|
-
# === Return
|
685
|
-
# (CLASP::Arguments::OptionArgument) The first unknown option; +nil+ if none found
|
686
|
-
def find_first_unknown options = {}
|
670
|
+
# (Array) a frozen array of values
|
671
|
+
attr_reader :values
|
687
672
|
|
688
|
-
|
673
|
+
# (Integer, +nil+) index of the first '--', if present; +nil+ otherwise
|
674
|
+
attr_reader :double_slash_index
|
689
675
|
|
690
|
-
|
676
|
+
# (Array) the (possibly mutated) array of arguments instance passed to new
|
677
|
+
attr_reader :argv
|
691
678
|
|
692
|
-
|
679
|
+
# (Array) unchanged copy of the original array of arguments passed to new
|
680
|
+
attr_reader :argv_original_copy
|
693
681
|
|
694
|
-
|
682
|
+
# (String) The program name
|
683
|
+
attr_reader :program_name
|
695
684
|
|
696
|
-
|
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 = {}
|
697
698
|
|
698
|
-
|
699
|
-
end
|
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
|
-
|
704
|
-
end
|
703
|
+
specifications = options[:specifications] || options[:aliases] || @specifications
|
705
704
|
|
706
|
-
|
707
|
-
end
|
705
|
+
raise ArgumentError, "specifications may not be nil" if specifications.nil?
|
708
706
|
|
709
|
-
|
710
|
-
# found; +nil+ otherwise
|
711
|
-
#
|
712
|
-
# === Signature
|
713
|
-
#
|
714
|
-
# * *Parameters:*
|
715
|
-
# - +id+ (String, CLASP::FlagArgument) The name of a flag, or the flag itself
|
716
|
-
#
|
717
|
-
# === Return
|
718
|
-
# (CLASP::Arguments::FlagArgument) The first flag matching +id+; +nil+ if none found
|
719
|
-
def find_flag(id)
|
707
|
+
flags.each do |f|
|
720
708
|
|
721
|
-
|
709
|
+
return f unless specifications.any? { |al| al.is_a?(::CLASP::FlagSpecification) && al.name == f.name }
|
710
|
+
end
|
722
711
|
|
723
|
-
|
724
|
-
end
|
712
|
+
self.options.each do |o|
|
725
713
|
|
726
|
-
|
727
|
-
|
714
|
+
return o unless specifications.any? { |al| al.is_a?(::CLASP::OptionSpecification) && al.name == o.name }
|
715
|
+
end
|
728
716
|
|
729
|
-
|
730
|
-
|
731
|
-
#
|
732
|
-
# === Signature
|
733
|
-
#
|
734
|
-
# * *Parameter:*
|
735
|
-
# - +id+ (String, CLASP::OptionArgument) The name of a option, or the option itself
|
736
|
-
#
|
737
|
-
# === Return
|
738
|
-
# (CLASP::Arguments::OptionArgument) The first option matching +id+; +nil+ if none found
|
739
|
-
def find_option(id)
|
717
|
+
nil
|
718
|
+
end
|
740
719
|
|
741
|
-
|
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)
|
742
731
|
|
743
|
-
|
744
|
-
end
|
732
|
+
flags.each do |flag|
|
745
733
|
|
746
|
-
|
747
|
-
|
734
|
+
return flag if flag == id
|
735
|
+
end
|
748
736
|
|
749
|
-
|
750
|
-
|
737
|
+
nil
|
738
|
+
end
|
751
739
|
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
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
|
+
|
752
|
+
options.each do |option|
|
753
|
+
|
754
|
+
return option if option == id
|
755
|
+
end
|
756
|
+
|
757
|
+
nil
|
758
|
+
end
|
759
|
+
|
760
|
+
# #################################################################### #
|
761
|
+
# backwards-compatible
|
762
|
+
|
763
|
+
# @!visibility private
|
764
|
+
Flag = FlagArgument # :nodoc:
|
765
|
+
# @!visibility private
|
766
|
+
Option = OptionArgument # :nodoc:
|
756
767
|
end # class Arguments
|
757
768
|
|
769
|
+
|
758
770
|
# ######################################################################## #
|
759
771
|
# module
|
760
772
|
|
761
773
|
end # module CLASP
|
762
774
|
|
763
|
-
# ############################## end of file ############################# #
|
764
775
|
|
776
|
+
# ############################## end of file ############################# #
|
765
777
|
|