libclimate-ruby 0.14.0 → 0.15.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 +50 -11
- data/examples/flag_and_option_specifications.from_DATA.md +4 -6
- data/examples/flag_and_option_specifications.from_DATA.rb +3 -3
- data/examples/flag_and_option_specifications.md +24 -8
- data/examples/flag_and_option_specifications.rb +12 -2
- data/examples/show_usage_and_version.md +4 -5
- data/examples/show_usage_and_version.rb +2 -2
- data/lib/libclimate/climate.rb +463 -188
- data/lib/libclimate/version.rb +3 -3
- data/test/unit/tc_minimal.rb +3 -3
- data/test/unit/tc_minimal_CLASP.rb +3 -3
- data/test/unit/tc_parse.rb +171 -0
- data/test/unit/tc_parse_and_verify.rb +157 -0
- data/test/unit/tc_values.rb +52 -0
- metadata +34 -13
data/lib/libclimate/climate.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# Purpose: Definition of the ::LibCLImate::Climate class
|
6
6
|
#
|
7
7
|
# Created: 13th July 2015
|
8
|
-
# Updated:
|
8
|
+
# Updated: 29th April 2019
|
9
9
|
#
|
10
10
|
# Home: http://github.com/synesissoftware/libCLImate.Ruby
|
11
11
|
#
|
@@ -51,6 +51,7 @@ require 'yaml'
|
|
51
51
|
|
52
52
|
#:stopdoc:
|
53
53
|
|
54
|
+
# TODO: Need to work with other colouring libraries, too
|
54
55
|
if !defined? Colcon # :nodoc:
|
55
56
|
|
56
57
|
begin
|
@@ -77,11 +78,12 @@ class << CLASP
|
|
77
78
|
f = self.Flag_old(name, options)
|
78
79
|
|
79
80
|
# anticipate this functionality being added to CLASP
|
80
|
-
|
81
|
+
unless f.respond_to? :action
|
81
82
|
|
82
|
-
|
83
|
+
class << f
|
83
84
|
|
84
|
-
|
85
|
+
attr_accessor :action
|
86
|
+
end
|
85
87
|
end
|
86
88
|
|
87
89
|
if blk
|
@@ -105,11 +107,12 @@ class << CLASP
|
|
105
107
|
o = self.Option_old(name, options)
|
106
108
|
|
107
109
|
# anticipate this functionality being added to CLASP
|
108
|
-
|
110
|
+
unless o.respond_to? :action
|
109
111
|
|
110
|
-
|
112
|
+
class << o
|
111
113
|
|
112
|
-
|
114
|
+
attr_accessor :action
|
115
|
+
end
|
113
116
|
end
|
114
117
|
|
115
118
|
if blk
|
@@ -130,7 +133,6 @@ end
|
|
130
133
|
|
131
134
|
#:startdoc:
|
132
135
|
|
133
|
-
|
134
136
|
module LibCLImate
|
135
137
|
|
136
138
|
# Class used to gather together the CLI specification, and execute it
|
@@ -145,7 +147,7 @@ module LibCLImate
|
|
145
147
|
#
|
146
148
|
# cl.add_flag('--verbose', alias: '-v', help: 'Makes program output verbose') { program_options[:verbose] = true }
|
147
149
|
#
|
148
|
-
# cl.add_option('--flavour', alias: '-f', help: 'Specifies the flavour') do |o,
|
150
|
+
# cl.add_option('--flavour', alias: '-f', help: 'Specifies the flavour') do |o, sp|
|
149
151
|
#
|
150
152
|
# program_options[:flavour] = check_flavour(o.value) or cl.abort "Invalid flavour '#{o.value}'; use --help for usage"
|
151
153
|
# end
|
@@ -165,6 +167,200 @@ class Climate
|
|
165
167
|
|
166
168
|
include ::Xqsr3::Quality::ParameterChecking
|
167
169
|
|
170
|
+
# Represents the results obtained from +Climate#parse()+
|
171
|
+
class ParseResults
|
172
|
+
|
173
|
+
def initialize(climate, arguments, options)
|
174
|
+
|
175
|
+
@climate = climate
|
176
|
+
|
177
|
+
@arguments = arguments
|
178
|
+
|
179
|
+
@argv = arguments.argv
|
180
|
+
@argv_original_copy = arguments.argv_original_copy
|
181
|
+
@specifications = arguments.specifications
|
182
|
+
@program_name = climate.program_name
|
183
|
+
@flags = arguments.flags
|
184
|
+
@options = arguments.options
|
185
|
+
@values = arguments.values
|
186
|
+
end
|
187
|
+
|
188
|
+
# (Climate) The +Climate+ instance from which this instance was obtained
|
189
|
+
attr_reader :climate
|
190
|
+
|
191
|
+
# ([String]) The original arguments passed into the +Climate#parse()+ method
|
192
|
+
attr_reader :argv
|
193
|
+
|
194
|
+
# (Array) unchanged copy of the original array of arguments passed to parse
|
195
|
+
attr_reader :argv_original_copy
|
196
|
+
|
197
|
+
# (Array) a frozen array of specifications
|
198
|
+
attr_reader :specifications
|
199
|
+
|
200
|
+
# (String) The program name
|
201
|
+
attr_reader :program_name
|
202
|
+
|
203
|
+
# (String) A (frozen) array of flags
|
204
|
+
attr_reader :flags
|
205
|
+
|
206
|
+
# (String) A (frozen) array of options
|
207
|
+
attr_reader :options
|
208
|
+
|
209
|
+
# (String) A (frozen) array of values
|
210
|
+
attr_reader :values
|
211
|
+
|
212
|
+
# Verifies the initiating command-line against the specifications,
|
213
|
+
# raising an exception if any missing, unused, or unrecognised flags,
|
214
|
+
# options, or values are found
|
215
|
+
def verify(**options)
|
216
|
+
|
217
|
+
if v = options[:raise]
|
218
|
+
|
219
|
+
hm = {}
|
220
|
+
|
221
|
+
hm[:raise_on_required] = v unless options.has_key?(:raise_on_required)
|
222
|
+
hm[:raise_on_unrecognised] = v unless options.has_key?(:raise_on_unrecognised)
|
223
|
+
hm[:raise_on_unused] = v unless options.has_key?(:raise_on_unused)
|
224
|
+
|
225
|
+
options = options.merge hm
|
226
|
+
end
|
227
|
+
|
228
|
+
raise_on_required = options[:raise_on_required]
|
229
|
+
raise_on_unrecognised = options[:raise_on_unrecognised]
|
230
|
+
raise_on_unused = options[:raise_on_unused]
|
231
|
+
|
232
|
+
|
233
|
+
# Verification:
|
234
|
+
#
|
235
|
+
# 1. Check arguments recognised
|
236
|
+
# 1.a Flags
|
237
|
+
# 1.b Options
|
238
|
+
# 2. police any required options
|
239
|
+
# 3. Check values
|
240
|
+
|
241
|
+
# 1.a Flags
|
242
|
+
|
243
|
+
self.flags.each do |flag|
|
244
|
+
|
245
|
+
spec = specifications.detect do |sp|
|
246
|
+
|
247
|
+
sp.kind_of?(::CLASP::FlagSpecification) && flag.name == sp.name
|
248
|
+
end
|
249
|
+
|
250
|
+
if spec
|
251
|
+
|
252
|
+
if spec.respond_to?(:action) && !spec.action.nil?
|
253
|
+
|
254
|
+
spec.action.call(flag, spec)
|
255
|
+
end
|
256
|
+
else
|
257
|
+
|
258
|
+
message = make_abort_message_("unrecognised flag '#{f}'")
|
259
|
+
|
260
|
+
if false
|
261
|
+
|
262
|
+
elsif climate.ignore_unknown
|
263
|
+
|
264
|
+
;
|
265
|
+
elsif raise_on_unrecognised
|
266
|
+
|
267
|
+
if raise_on_unrecognised.is_a?(Class)
|
268
|
+
|
269
|
+
raise raise_on_unrecognised, message
|
270
|
+
else
|
271
|
+
|
272
|
+
raise RuntimeError, message
|
273
|
+
end
|
274
|
+
elsif climate.exit_on_unknown
|
275
|
+
|
276
|
+
climate.abort message
|
277
|
+
else
|
278
|
+
|
279
|
+
if program_name && !program_name.empty?
|
280
|
+
|
281
|
+
message = "#{program_name}: #{message}"
|
282
|
+
end
|
283
|
+
|
284
|
+
climate.stderr.puts message
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# 1.b Options
|
290
|
+
|
291
|
+
self.options.each do |option|
|
292
|
+
|
293
|
+
spec = specifications.detect do |sp|
|
294
|
+
|
295
|
+
sp.kind_of?(::CLASP::OptionSpecification) && option.name == sp.name
|
296
|
+
end
|
297
|
+
|
298
|
+
if spec
|
299
|
+
|
300
|
+
if spec.respond_to?(:action) && !spec.action.nil?
|
301
|
+
|
302
|
+
spec.action.call(option, spec)
|
303
|
+
end
|
304
|
+
else
|
305
|
+
|
306
|
+
message = make_abort_message_("unrecognised option '#{f}'")
|
307
|
+
|
308
|
+
if false
|
309
|
+
|
310
|
+
elsif climate.ignore_unknown
|
311
|
+
|
312
|
+
;
|
313
|
+
elsif raise_on_unrecognised
|
314
|
+
|
315
|
+
if raise_on_unrecognised.is_a?(Class)
|
316
|
+
|
317
|
+
raise raise_on_unrecognised, message
|
318
|
+
else
|
319
|
+
|
320
|
+
raise RuntimeError, message
|
321
|
+
end
|
322
|
+
elsif climate.exit_on_unknown
|
323
|
+
|
324
|
+
climate.abort message
|
325
|
+
else
|
326
|
+
|
327
|
+
if program_name && !program_name.empty?
|
328
|
+
|
329
|
+
message = "#{program_name}: #{message}"
|
330
|
+
end
|
331
|
+
|
332
|
+
climate.stderr.puts message
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# 2. police any required options
|
338
|
+
|
339
|
+
climate.check_required_options_(specifications, self.options, [], raise_on_required)
|
340
|
+
|
341
|
+
# 3. Check values
|
342
|
+
|
343
|
+
climate.check_value_constraints_(values)
|
344
|
+
end
|
345
|
+
|
346
|
+
def flag_is_specified(id)
|
347
|
+
|
348
|
+
!@arguments.find_flag(id).nil?
|
349
|
+
end
|
350
|
+
|
351
|
+
def lookup_flag(id)
|
352
|
+
|
353
|
+
@arguments.find_flag(id)
|
354
|
+
end
|
355
|
+
|
356
|
+
def lookup_option(id)
|
357
|
+
|
358
|
+
@arguments.find_option(id)
|
359
|
+
end
|
360
|
+
|
361
|
+
end # end class ParseResults
|
362
|
+
|
363
|
+
|
168
364
|
#:stopdoc:
|
169
365
|
|
170
366
|
private
|
@@ -173,7 +369,7 @@ class Climate
|
|
173
369
|
GIVEN_SPECS_ = "_Given_Specs_01B59422_8407_4c89_9432_8160C52BD5AD"
|
174
370
|
end # module Climate_Constants_
|
175
371
|
|
176
|
-
def make_abort_message_
|
372
|
+
def make_abort_message_(msg)
|
177
373
|
|
178
374
|
if 0 != (usage_help_suffix || 0).size
|
179
375
|
|
@@ -184,7 +380,7 @@ class Climate
|
|
184
380
|
end
|
185
381
|
end
|
186
382
|
|
187
|
-
def show_usage_
|
383
|
+
def show_usage_()
|
188
384
|
|
189
385
|
options = {}
|
190
386
|
options.merge! stream: stdout, program_name: program_name, version: version, exit: exit_on_usage ? 0 : nil
|
@@ -195,12 +391,12 @@ class Climate
|
|
195
391
|
CLASP.show_usage specifications, options
|
196
392
|
end
|
197
393
|
|
198
|
-
def show_version_
|
394
|
+
def show_version_()
|
199
395
|
|
200
396
|
CLASP.show_version specifications, stream: stdout, program_name: program_name, version: version, exit: exit_on_usage ? 0 : nil
|
201
397
|
end
|
202
398
|
|
203
|
-
def infer_version_
|
399
|
+
def infer_version_(ctxt)
|
204
400
|
|
205
401
|
# algorithm:
|
206
402
|
#
|
@@ -362,6 +558,144 @@ class Climate
|
|
362
558
|
return r
|
363
559
|
end
|
364
560
|
end
|
561
|
+
|
562
|
+
public
|
563
|
+
def check_required_options_(specifications, options, missing, raise_on_required)
|
564
|
+
|
565
|
+
required_specifications = specifications.select do |sp|
|
566
|
+
|
567
|
+
sp.kind_of?(::CLASP::OptionSpecification) && sp.required?
|
568
|
+
end
|
569
|
+
|
570
|
+
required_specifications = Hash[required_specifications.map { |sp| [ sp.name, sp ] }]
|
571
|
+
|
572
|
+
given_options = Hash[options.map { |o| [ o.name, o ]}]
|
573
|
+
|
574
|
+
required_specifications.each do |k, sp|
|
575
|
+
|
576
|
+
unless given_options.has_key? k
|
577
|
+
|
578
|
+
message = sp.required_message
|
579
|
+
|
580
|
+
if false
|
581
|
+
|
582
|
+
;
|
583
|
+
elsif raise_on_required
|
584
|
+
|
585
|
+
if raise_on_required.is_a?(Class)
|
586
|
+
|
587
|
+
raise raise_on_required, message
|
588
|
+
else
|
589
|
+
|
590
|
+
raise RuntimeError, message
|
591
|
+
end
|
592
|
+
elsif exit_on_missing
|
593
|
+
|
594
|
+
self.abort message
|
595
|
+
else
|
596
|
+
|
597
|
+
if program_name && !program_name.empty?
|
598
|
+
|
599
|
+
message = "#{program_name}: #{message}"
|
600
|
+
end
|
601
|
+
|
602
|
+
stderr.puts message
|
603
|
+
end
|
604
|
+
|
605
|
+
missing << sp
|
606
|
+
#results[:missing_option_aliases] << sp
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def check_value_constraints_(values)
|
612
|
+
|
613
|
+
# now police the values
|
614
|
+
|
615
|
+
values_constraint = constrain_values
|
616
|
+
values_constraint = values_constraint.begin if ::Range === values_constraint && values_constraint.end == values_constraint.begin
|
617
|
+
val_names = ::Array === value_names ? value_names : []
|
618
|
+
|
619
|
+
case values_constraint
|
620
|
+
when nil
|
621
|
+
|
622
|
+
;
|
623
|
+
when ::Array
|
624
|
+
|
625
|
+
warn "value of 'constrain_values' attribute, if an #{::Array}, must not be empty and all elements must be of type #{::Integer}" if values_constraint.empty? || !values_constraint.all? { |v| ::Integer === v }
|
626
|
+
|
627
|
+
unless values_constraint.include? values.size
|
628
|
+
|
629
|
+
message = make_abort_message_("wrong number of values: #{values.size} given, #{values_constraint} required")
|
630
|
+
|
631
|
+
if exit_on_missing
|
632
|
+
|
633
|
+
self.abort message
|
634
|
+
else
|
635
|
+
|
636
|
+
if program_name && !program_name.empty?
|
637
|
+
|
638
|
+
message = "#{program_name}: #{message}"
|
639
|
+
end
|
640
|
+
|
641
|
+
stderr.puts message
|
642
|
+
end
|
643
|
+
end
|
644
|
+
when ::Integer
|
645
|
+
|
646
|
+
unless values.size == values_constraint
|
647
|
+
|
648
|
+
if name = val_names[values.size]
|
649
|
+
|
650
|
+
message = make_abort_message_(name + ' not specified')
|
651
|
+
else
|
652
|
+
|
653
|
+
message = make_abort_message_("wrong number of values: #{values.size} given, #{values_constraint} required")
|
654
|
+
end
|
655
|
+
|
656
|
+
if exit_on_missing
|
657
|
+
|
658
|
+
self.abort message
|
659
|
+
else
|
660
|
+
|
661
|
+
if program_name && !program_name.empty?
|
662
|
+
|
663
|
+
message = "#{program_name}: #{message}"
|
664
|
+
end
|
665
|
+
|
666
|
+
stderr.puts message
|
667
|
+
end
|
668
|
+
end
|
669
|
+
when ::Range
|
670
|
+
|
671
|
+
unless values_constraint.include? values.size
|
672
|
+
|
673
|
+
if name = val_names[values.size]
|
674
|
+
|
675
|
+
message = make_abort_message_(name + ' not specified')
|
676
|
+
else
|
677
|
+
|
678
|
+
message = make_abort_message_("wrong number of values: #{values.size} givens, #{values_constraint.begin} - #{values_constraint.end - (values_constraint.exclude_end? ? 1 : 0)} required")
|
679
|
+
end
|
680
|
+
|
681
|
+
if exit_on_missing
|
682
|
+
|
683
|
+
self.abort message
|
684
|
+
else
|
685
|
+
|
686
|
+
if program_name && !program_name.empty?
|
687
|
+
|
688
|
+
message = "#{program_name}: #{message}"
|
689
|
+
end
|
690
|
+
|
691
|
+
stderr.puts message
|
692
|
+
end
|
693
|
+
end
|
694
|
+
else
|
695
|
+
|
696
|
+
warn "value of 'constrain_values' attribute - '#{constrain_values}' (#{constrain_values.class}) - of wrong type : must be #{::Array}, #{::Integer}, #{::Range}, or nil"
|
697
|
+
end
|
698
|
+
end
|
365
699
|
#:startdoc:
|
366
700
|
|
367
701
|
public
|
@@ -382,7 +716,7 @@ class Climate
|
|
382
716
|
# - +:version_context+ Object or class that defines a context for searching the version. Ignored if +:version+ is specified
|
383
717
|
#
|
384
718
|
# * *Block* An optional block that receives the initialising Climate instance, allowing the user to modify the attributes.
|
385
|
-
def self.load
|
719
|
+
def self.load(source, options = (options_defaulted_ = {}), &blk) # :yields: climate
|
386
720
|
|
387
721
|
check_parameter options, 'options', allow_nil: true, type: ::Hash
|
388
722
|
|
@@ -492,8 +826,39 @@ class Climate
|
|
492
826
|
version_context = options[:version_context]
|
493
827
|
@version = options[:version] || infer_version_(version_context)
|
494
828
|
|
495
|
-
|
496
|
-
|
829
|
+
unless options[:no_help_flag]
|
830
|
+
|
831
|
+
f = CLASP::Flag.Help()
|
832
|
+
|
833
|
+
unless f.respond_to?(:action)
|
834
|
+
|
835
|
+
class << f
|
836
|
+
|
837
|
+
attr_accessor :action
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
f.action = Proc.new { show_usage_ }
|
842
|
+
|
843
|
+
@specifications << f
|
844
|
+
end
|
845
|
+
|
846
|
+
unless options[:no_version_flag]
|
847
|
+
|
848
|
+
f = CLASP::Flag.Version()
|
849
|
+
|
850
|
+
unless f.respond_to?(:action)
|
851
|
+
|
852
|
+
class << f
|
853
|
+
|
854
|
+
attr_accessor :action
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
f.action = Proc.new { show_version_ }
|
859
|
+
|
860
|
+
@specifications << f
|
861
|
+
end
|
497
862
|
|
498
863
|
@specifications = @specifications + given_specs if given_specs
|
499
864
|
|
@@ -501,9 +866,9 @@ class Climate
|
|
501
866
|
end
|
502
867
|
|
503
868
|
# [DEPRECATED] This method is now deprecated. Instead use +program_name=+
|
504
|
-
def set_program_name
|
869
|
+
def set_program_name(name)
|
505
870
|
|
506
|
-
@program_name
|
871
|
+
@program_name = name
|
507
872
|
end
|
508
873
|
|
509
874
|
# ([CLASP::Specification]) An array of specifications attached to the climate instance, whose contents should be modified by adding (or removing) CLASP specifications
|
@@ -551,7 +916,52 @@ class Climate
|
|
551
916
|
# (String, [String], [Integer]) A version string or an array of integers/strings representing the version component(s)
|
552
917
|
attr_accessor :version
|
553
918
|
|
554
|
-
#
|
919
|
+
# Parse the given command-line (passed as +argv+) by the given instance
|
920
|
+
#
|
921
|
+
# === Signature
|
922
|
+
#
|
923
|
+
# * *Parameters:*
|
924
|
+
# - +argv+ ([String]) The array of arguments; defaults to <tt>ARGV</tt>
|
925
|
+
#
|
926
|
+
# === Returns
|
927
|
+
# (ParseResults) Results
|
928
|
+
def parse(argv = ARGV) # :yields: ParseResults
|
929
|
+
|
930
|
+
raise ArgumentError, "argv may not be nil" if argv.nil?
|
931
|
+
|
932
|
+
arguments = CLASP::Arguments.new argv, specifications
|
933
|
+
|
934
|
+
ParseResults.new(self, arguments, argv)
|
935
|
+
end
|
936
|
+
|
937
|
+
# Parse the given command-line (passed as +argv+) by the given instance,
|
938
|
+
# and verifies it
|
939
|
+
#
|
940
|
+
# === Signature
|
941
|
+
#
|
942
|
+
# * *Parameters:*
|
943
|
+
# - +argv+ ([String]) The array of arguments; defaults to <tt>ARGV</tt>
|
944
|
+
# - +options+ (Hash) Options
|
945
|
+
#
|
946
|
+
# * *Options:*
|
947
|
+
# - +:raise_on_required+ (boolean, Exception) Causes an/the given exception to be raised if any required options are not specified in the command-line
|
948
|
+
# - +:raise_on_unrecognised+ (boolean, Exception) Causes an/the given exception to be raised if any unrecognised flags/options are specified in the command-line
|
949
|
+
# - +:raise_on_unused+ (boolean, Exception) Causes an/the given exception to be raised if any given flags/options are not used
|
950
|
+
# - +:raise+ (boolean, Exception) Causes an/the given exception to be raised in all conditions
|
951
|
+
#
|
952
|
+
# === Returns
|
953
|
+
# (ParseResults) Results
|
954
|
+
def parse_and_verify(argv = ARGV, **options) # :yields: ParseResults
|
955
|
+
|
956
|
+
r = parse argv
|
957
|
+
|
958
|
+
r.verify(**options)
|
959
|
+
|
960
|
+
r
|
961
|
+
end
|
962
|
+
|
963
|
+
# [DEPRECATED] Use +Climate#parse_and_verify()+ (but be aware that the
|
964
|
+
# returned result is of a different type
|
555
965
|
#
|
556
966
|
# === Signature
|
557
967
|
#
|
@@ -562,17 +972,17 @@ class Climate
|
|
562
972
|
# an instance of a type derived from +::Hash+ with the additional
|
563
973
|
# attributes +flags+, +options+, +values+, and +argv+.
|
564
974
|
#
|
565
|
-
def run
|
975
|
+
def run(argv = ARGV) # :yields: customised +::Hash+
|
566
976
|
|
567
977
|
raise ArgumentError, "argv may not be nil" if argv.nil?
|
568
978
|
|
569
|
-
arguments
|
979
|
+
arguments = CLASP::Arguments.new argv, specifications
|
570
980
|
|
571
981
|
run_ argv, arguments
|
572
982
|
end
|
573
983
|
|
574
984
|
private
|
575
|
-
def run_
|
985
|
+
def run_(argv, arguments) # :nodoc:
|
576
986
|
|
577
987
|
flags = arguments.flags
|
578
988
|
options = arguments.options
|
@@ -601,39 +1011,32 @@ class Climate
|
|
601
1011
|
missing_option_aliases: [],
|
602
1012
|
}
|
603
1013
|
|
1014
|
+
# Verification:
|
1015
|
+
#
|
1016
|
+
# 1. Check arguments recognised
|
1017
|
+
# 1.a Flags
|
1018
|
+
# 1.b Options
|
1019
|
+
# 2. police any required options
|
1020
|
+
# 3. Check values
|
1021
|
+
|
1022
|
+
# 1.a Flags
|
1023
|
+
|
604
1024
|
flags.each do |f|
|
605
1025
|
|
606
|
-
|
1026
|
+
spec = specifications.detect do |sp|
|
607
1027
|
|
608
|
-
|
1028
|
+
sp.kind_of?(::CLASP::FlagSpecification) && f.name == sp.name
|
609
1029
|
end
|
610
1030
|
|
611
|
-
if
|
1031
|
+
if spec
|
612
1032
|
|
613
1033
|
selector = :unhandled
|
614
1034
|
|
615
|
-
|
616
|
-
# monkey-patched to CLASP.Flag()
|
1035
|
+
if spec.respond_to?(:action) && !spec.action.nil?
|
617
1036
|
|
618
|
-
|
619
|
-
|
620
|
-
al.action.call(f, al)
|
1037
|
+
spec.action.call(f, spec)
|
621
1038
|
|
622
1039
|
selector = :handled
|
623
|
-
else
|
624
|
-
|
625
|
-
ex = al.extras
|
626
|
-
|
627
|
-
case ex
|
628
|
-
when ::Hash
|
629
|
-
|
630
|
-
if ex.has_key? :handle
|
631
|
-
|
632
|
-
ex[:handle].call(f, al)
|
633
|
-
|
634
|
-
selector = :handled
|
635
|
-
end
|
636
|
-
end
|
637
1040
|
end
|
638
1041
|
|
639
1042
|
results[:flags][selector] << f
|
@@ -663,39 +1066,24 @@ class Climate
|
|
663
1066
|
end
|
664
1067
|
end
|
665
1068
|
|
1069
|
+
# 1.b Options
|
1070
|
+
|
666
1071
|
options.each do |o|
|
667
1072
|
|
668
|
-
|
1073
|
+
spec = specifications.detect do |sp|
|
669
1074
|
|
670
|
-
|
1075
|
+
sp.kind_of?(::CLASP::OptionSpecification) && o.name == sp.name
|
671
1076
|
end
|
672
1077
|
|
673
|
-
if
|
1078
|
+
if spec
|
674
1079
|
|
675
1080
|
selector = :unhandled
|
676
1081
|
|
677
|
-
|
678
|
-
# monkey-patched to CLASP.Option()
|
679
|
-
|
680
|
-
if al.respond_to?(:action) && !al.action.nil?
|
1082
|
+
if spec.respond_to?(:action) && !spec.action.nil?
|
681
1083
|
|
682
|
-
|
1084
|
+
spec.action.call(o, spec)
|
683
1085
|
|
684
1086
|
selector = :handled
|
685
|
-
else
|
686
|
-
|
687
|
-
ex = al.extras
|
688
|
-
|
689
|
-
case ex
|
690
|
-
when ::Hash
|
691
|
-
|
692
|
-
if ex.has_key? :handle
|
693
|
-
|
694
|
-
ex[:handle].call(o, al)
|
695
|
-
|
696
|
-
selector = :handled
|
697
|
-
end
|
698
|
-
end
|
699
1087
|
end
|
700
1088
|
|
701
1089
|
results[:options][selector] << o
|
@@ -725,126 +1113,13 @@ class Climate
|
|
725
1113
|
end
|
726
1114
|
end
|
727
1115
|
|
1116
|
+
# 2. police any required options
|
728
1117
|
|
729
|
-
|
730
|
-
|
731
|
-
required_specifications = specifications.select do |a|
|
732
|
-
|
733
|
-
a.kind_of?(::CLASP::Option) && a.required?
|
734
|
-
end
|
735
|
-
|
736
|
-
required_specifications = Hash[required_specifications.map { |a| [ a.name, a ] }]
|
737
|
-
|
738
|
-
given_options = Hash[results[:options][:given].map { |o| [ o.name, o ]}]
|
1118
|
+
check_required_options_(specifications, results[:options][:given], results[:missing_option_aliases], false)
|
739
1119
|
|
740
|
-
|
1120
|
+
# 3. Check values
|
741
1121
|
|
742
|
-
|
743
|
-
|
744
|
-
message = a.required_message
|
745
|
-
|
746
|
-
if exit_on_missing
|
747
|
-
|
748
|
-
self.abort message
|
749
|
-
else
|
750
|
-
|
751
|
-
if program_name && !program_name.empty?
|
752
|
-
|
753
|
-
message = "#{program_name}: #{message}"
|
754
|
-
end
|
755
|
-
|
756
|
-
stderr.puts message
|
757
|
-
end
|
758
|
-
|
759
|
-
results[:missing_option_aliases] << a
|
760
|
-
end
|
761
|
-
end
|
762
|
-
|
763
|
-
# now police the values
|
764
|
-
|
765
|
-
values_constraint = constrain_values
|
766
|
-
values_constraint = values_constraint.begin if ::Range === values_constraint && values_constraint.end == values_constraint.begin
|
767
|
-
val_names = ::Array === value_names ? value_names : []
|
768
|
-
|
769
|
-
case values_constraint
|
770
|
-
when nil
|
771
|
-
|
772
|
-
;
|
773
|
-
when ::Array
|
774
|
-
|
775
|
-
warn "value of 'constrain_values' attribute, if an #{::Array}, must not be empty and all elements must be of type #{::Integer}" if values_constraint.empty? || !values_constraint.all? { |v| ::Integer === v }
|
776
|
-
|
777
|
-
unless values_constraint.include? values.size
|
778
|
-
|
779
|
-
message = make_abort_message_("wrong number of values: #{values.size} given, #{values_constraint} required")
|
780
|
-
|
781
|
-
if exit_on_missing
|
782
|
-
|
783
|
-
self.abort message
|
784
|
-
else
|
785
|
-
|
786
|
-
if program_name && !program_name.empty?
|
787
|
-
|
788
|
-
message = "#{program_name}: #{message}"
|
789
|
-
end
|
790
|
-
|
791
|
-
stderr.puts message
|
792
|
-
end
|
793
|
-
end
|
794
|
-
when ::Integer
|
795
|
-
|
796
|
-
unless values.size == values_constraint
|
797
|
-
|
798
|
-
if name = val_names[values.size]
|
799
|
-
|
800
|
-
message = make_abort_message_(name + ' not specified')
|
801
|
-
else
|
802
|
-
|
803
|
-
message = make_abort_message_("wrong number of values: #{values.size} given, #{values_constraint} required")
|
804
|
-
end
|
805
|
-
|
806
|
-
if exit_on_missing
|
807
|
-
|
808
|
-
self.abort message
|
809
|
-
else
|
810
|
-
|
811
|
-
if program_name && !program_name.empty?
|
812
|
-
|
813
|
-
message = "#{program_name}: #{message}"
|
814
|
-
end
|
815
|
-
|
816
|
-
stderr.puts message
|
817
|
-
end
|
818
|
-
end
|
819
|
-
when ::Range
|
820
|
-
|
821
|
-
unless values_constraint.include? values.size
|
822
|
-
|
823
|
-
if name = val_names[values.size]
|
824
|
-
|
825
|
-
message = make_abort_message_(name + ' not specified')
|
826
|
-
else
|
827
|
-
|
828
|
-
message = make_abort_message_("wrong number of values: #{values.size} givens, #{values_constraint.begin} - #{values_constraint.end - (values_constraint.exclude_end? ? 1 : 0)} required")
|
829
|
-
end
|
830
|
-
|
831
|
-
if exit_on_missing
|
832
|
-
|
833
|
-
self.abort message
|
834
|
-
else
|
835
|
-
|
836
|
-
if program_name && !program_name.empty?
|
837
|
-
|
838
|
-
message = "#{program_name}: #{message}"
|
839
|
-
end
|
840
|
-
|
841
|
-
stderr.puts message
|
842
|
-
end
|
843
|
-
end
|
844
|
-
else
|
845
|
-
|
846
|
-
warn "value of 'constrain_values' attribute - '#{constrain_values}' (#{constrain_values.class}) - of wrong type : must be #{::Array}, #{::Integer}, #{::Range}, or nil"
|
847
|
-
end
|
1122
|
+
check_value_constraints_(values)
|
848
1123
|
|
849
1124
|
|
850
1125
|
|
@@ -887,7 +1162,7 @@ class Climate
|
|
887
1162
|
#
|
888
1163
|
# === Returns
|
889
1164
|
# The combined message string, if <tt>exit()</tt> not called.
|
890
|
-
def abort
|
1165
|
+
def abort(message, options={})
|
891
1166
|
|
892
1167
|
prog_name = options[:program_name]
|
893
1168
|
prog_name ||= program_name
|
@@ -939,7 +1214,7 @@ class Climate
|
|
939
1214
|
|
940
1215
|
check_parameter name_or_flag, 'name_or_flag', allow_nil: false, types: [ ::String, ::Symbol, ::CLASP::FlagSpecification ]
|
941
1216
|
|
942
|
-
if ::CLASP::
|
1217
|
+
if ::CLASP::FlagSpecification === name_or_flag
|
943
1218
|
|
944
1219
|
specifications << name_or_flag
|
945
1220
|
else
|
@@ -969,7 +1244,7 @@ class Climate
|
|
969
1244
|
|
970
1245
|
check_parameter name_or_option, 'name_or_option', allow_nil: false, types: [ ::String, ::Symbol, ::CLASP::OptionSpecification ]
|
971
1246
|
|
972
|
-
if ::CLASP::
|
1247
|
+
if ::CLASP::OptionSpecification === name_or_option
|
973
1248
|
|
974
1249
|
specifications << name_or_option
|
975
1250
|
else
|
@@ -1022,10 +1297,10 @@ class Climate
|
|
1022
1297
|
raise ArgumentError, "must supply at least one alias" if aliases.empty?
|
1023
1298
|
|
1024
1299
|
case name_or_specification
|
1025
|
-
when ::CLASP::
|
1300
|
+
when ::CLASP::FlagSpecification
|
1026
1301
|
|
1027
1302
|
self.specifications << name_or_specification
|
1028
|
-
when ::CLASP::
|
1303
|
+
when ::CLASP::OptionSpecification
|
1029
1304
|
|
1030
1305
|
self.specifications << name_or_specification
|
1031
1306
|
else
|