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