libclimate-ruby 0.14.0.1 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e9c4224cfebaa4069ba4cd5544fa888b1223fed
4
- data.tar.gz: f5b70a644cae5c0431a038f6f6afbfc0ce29da7b
3
+ metadata.gz: 3b5494527c8f55a49015bfa7fb78ee362315a117
4
+ data.tar.gz: ed66aad3de517bf0aa0689c40cfd4f469a820ad1
5
5
  SHA512:
6
- metadata.gz: 6ee75cc2c09e1490dc92567a8dadb5cc091ea92e8c455fe823ba1179449c5c91453d1e290df7557927c64dfe02ad1074081ef2ac71d0b827e2d7e2309ea0a29b
7
- data.tar.gz: beaca3d7b2e613565d1169acc705bf62750677877b6d80d04d05583e1bcd1e2c358b0c5d175c3d9630b19bd70f0a6ec30be47b2acc84c0301b7fd9125f431a4c
6
+ metadata.gz: 0b1eece97175423d77773c767dacf8148ea03dfd73c38a11840bf3eb0bfaeab54120be55ac5975c6e5cad9ca7afc82016750856bfccdffa091fa3a709b7141bf
7
+ data.tar.gz: 4913b8c35f85347a4c3ccbbe8cf9e89d74caaafdcfc9aa2eb8ac30f3f3d4cc5e7878a779326ac821b6a37f1d6b5f9270dc26f5fa9c5ea415d00d479fcf9ff197
data/README.md CHANGED
@@ -3,6 +3,14 @@ libCLImate, for Ruby
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/libclimate-ruby.svg)](https://badge.fury.io/rb/libclimate-ruby)
5
5
 
6
+ ## Table of Contents
7
+
8
+ 1. [Introduction](#introduction)
9
+ 2. [Installation](#installation)
10
+ 3. [Components](#components)
11
+ 4. [Examples](#examples)
12
+ 5. [Project Information](#project-information)
13
+
6
14
  ## Introduction
7
15
 
8
16
  **libCLImate** is a portable, lightweight mini-framework that encapsulates the common aspects of **C**ommand-**L**ine **I**nterface boilerplate, including:
@@ -12,14 +20,6 @@ libCLImate, for Ruby
12
20
 
13
21
  **libCLImate.Ruby** is the Ruby version.
14
22
 
15
- ## Table of Contents
16
-
17
- 1. [Introduction](#introduction)
18
- 2. [Installation](#installation)
19
- 3. [Components](#components)
20
- 4. [Examples](#examples)
21
- 5. [Project Information](#project-information)
22
-
23
23
  ## Installation
24
24
 
25
25
  Install via **gem** as in:
@@ -38,7 +38,41 @@ require 'libclimate'
38
38
 
39
39
  ## Components
40
40
 
41
- T.B.C.
41
+ In common with several other variants of **libCLImate**, **libCLImate.Ruby** revolves around a ``Climate`` ``class`` whose initialiser takes a block and acts as a lightweight DSL for concise definition of a command-line parsing instance, as in:
42
+
43
+ ```Ruby
44
+ options = {}
45
+ climate = LibCLImate::Climate.new do |cl|
46
+
47
+ cl.add_flag('--debug', alias: '-d', help: 'runs in Debug mode') do
48
+
49
+ options[:debug] = true
50
+ end
51
+ cl.add_option('--verbosity', alias: '-v', help: 'specifies the verbosity', values: [ 'terse', 'quiet', 'silent', 'chatty' ]) do |o, a|
52
+
53
+ options[:verbosity] = o.value
54
+ end
55
+ cl.add_alias('--verbosity=chatty', '-c')
56
+
57
+ cl.version = [ 0, 1, 0 ]
58
+
59
+ cl.info_lines = [
60
+
61
+ 'libCLImate.Ruby examples',
62
+ :version,
63
+ "Illustrates use of libCLImate.Ruby's specification of flags, options, and specifications",
64
+ '',
65
+ ]
66
+
67
+ cl.constrain_values = 1..2
68
+ cl.usage_values = "<dir-1> [ <dir-2> ]"
69
+ cl.value_names = [
70
+
71
+ "first directory",
72
+ "second directory",
73
+ ]
74
+ end
75
+ ```
42
76
 
43
77
  ## Examples
44
78
 
@@ -27,7 +27,7 @@ climate = LibCLImate::Climate.load DATA do |cl|
27
27
  cl.on_option('--verbosity') { |o, a| options[:verbosity] = o.value }
28
28
  end
29
29
 
30
- r = climate.run ARGV
30
+ r = climate.parse_and_verify ARGV
31
31
 
32
32
 
33
33
  # Program-specific processing of flags/options
@@ -87,10 +87,8 @@ libclimate:
87
87
  -
88
88
  version:
89
89
  - 0
90
- - 1
91
- - "2"
92
- ```
93
- ```ruby
90
+ - 2
91
+ - "0"
94
92
  ```
95
93
 
96
94
  ## Usage
@@ -130,7 +128,7 @@ it gives the output:
130
128
 
131
129
  ```
132
130
  libCLImate.Ruby examples
133
- flag_and_option_specifications.from_DATA(.rb) 0.1.2
131
+ flag_and_option_specifications.from_DATA(.rb) 0.2.0
134
132
  Illustrates use of libCLImate.Ruby's specification of flags, options, and aliases, from DATA
135
133
 
136
134
  USAGE: flag_and_option_specifications.from_DATA(.rb) [... flags/options ...] <dir-1> [ <dir-2> ]
@@ -18,7 +18,7 @@ climate = LibCLImate::Climate.load DATA do |cl|
18
18
  cl.on_option('--verbosity') { |o, a| options[:verbosity] = o.value }
19
19
  end
20
20
 
21
- r = climate.run ARGV
21
+ r = climate.parse_and_verify ARGV
22
22
 
23
23
 
24
24
  # Program-specific processing of flags/options
@@ -78,6 +78,6 @@ libclimate:
78
78
  -
79
79
  version:
80
80
  - 0
81
- - 1
82
- - "2"
81
+ - 2
82
+ - "0"
83
83
 
@@ -30,7 +30,7 @@ climate = LibCLImate::Climate.new do |cl|
30
30
  end
31
31
  cl.add_alias('--verbosity=chatty', '-c')
32
32
 
33
- cl.version = [ 0, 0, 1 ]
33
+ cl.version = [ 0, 1, 0 ]
34
34
 
35
35
  cl.info_lines = [
36
36
 
@@ -39,9 +39,17 @@ climate = LibCLImate::Climate.new do |cl|
39
39
  "Illustrates use of libCLImate.Ruby's specification of flags, options, and specifications",
40
40
  '',
41
41
  ]
42
+
43
+ cl.constrain_values = 1..2
44
+ cl.usage_values = "<dir-1> [ <dir-2> ]"
45
+ cl.value_names = [
46
+
47
+ "first directory",
48
+ "second directory",
49
+ ]
42
50
  end
43
51
 
44
- r = climate.run ARGV
52
+ r = climate.parse_and_verify ARGV
45
53
 
46
54
 
47
55
 
@@ -56,6 +64,10 @@ if options[:debug]
56
64
 
57
65
  $stdout.puts 'Debug mode is specified'
58
66
  end
67
+
68
+ # some notional output
69
+
70
+ $stdout.puts "processing in '#{r.values[0]}'" + (r.values.size > 1 ? " and '#{r.values[1]}'" : '')
59
71
  ```
60
72
 
61
73
  ## Usage
@@ -77,6 +89,7 @@ or (in a Unix shell):
77
89
  it gives the output:
78
90
 
79
91
  ```
92
+ flag_and_option_specifications(.rb): first directory not specified; use --help for usage
80
93
  ```
81
94
 
82
95
  ### Show usage
@@ -91,7 +104,7 @@ it gives the output:
91
104
 
92
105
  ```
93
106
  libCLImate.Ruby examples
94
- flag_and_option_specifications.rb 0.0.1
107
+ flag_and_option_specifications.rb 0.1.0
95
108
  Illustrates use of libCLImate.Ruby's specification of flags, options, and specifications
96
109
 
97
110
  USAGE: flag_and_option_specifications.rb [ ... flags and options ... ]
@@ -124,7 +137,7 @@ flags/options:
124
137
  If executed with the arguments
125
138
 
126
139
  ```
127
- ruby examples/flag_and_option_specifications.rb --debug --verbosity=silent
140
+ ruby examples/flag_and_option_specifications.rb dir-1 dir-2 --debug --verbosity=silent
128
141
  ```
129
142
 
130
143
  it gives the output:
@@ -132,6 +145,7 @@ it gives the output:
132
145
  ```
133
146
  verbosity is specified as: silent
134
147
  Debug mode is specified
148
+ processing in 'dir-1' and 'dir-2'
135
149
  ```
136
150
 
137
151
  ### Specify flags and options in short-form
@@ -21,7 +21,7 @@ climate = LibCLImate::Climate.new do |cl|
21
21
  end
22
22
  cl.add_alias('--verbosity=chatty', '-c')
23
23
 
24
- cl.version = [ 0, 0, 1 ]
24
+ cl.version = [ 0, 1, 0 ]
25
25
 
26
26
  cl.info_lines = [
27
27
 
@@ -30,9 +30,17 @@ climate = LibCLImate::Climate.new do |cl|
30
30
  "Illustrates use of libCLImate.Ruby's specification of flags, options, and specifications",
31
31
  '',
32
32
  ]
33
+
34
+ cl.constrain_values = 1..2
35
+ cl.usage_values = "<dir-1> [ <dir-2> ]"
36
+ cl.value_names = [
37
+
38
+ "first directory",
39
+ "second directory",
40
+ ]
33
41
  end
34
42
 
35
- r = climate.run ARGV
43
+ r = climate.parse_and_verify ARGV
36
44
 
37
45
 
38
46
 
@@ -49,5 +57,7 @@ if options[:debug]
49
57
  end
50
58
 
51
59
 
60
+ # some notional output
52
61
 
62
+ $stdout.puts "processing in '#{r.values[0]}'" + (r.values.size > 1 ? " and '#{r.values[1]}'" : '')
53
63
 
@@ -19,7 +19,7 @@ require 'libclimate'
19
19
 
20
20
  climate = LibCLImate::Climate.new do |cl|
21
21
 
22
- cl.version = [ 0, 0, 1 ]
22
+ cl.version = [ 0, 1, 0 ]
23
23
 
24
24
  cl.info_lines = [
25
25
 
@@ -30,7 +30,7 @@ climate = LibCLImate::Climate.new do |cl|
30
30
  ]
31
31
  end
32
32
 
33
- climate.run ARGV
33
+ climate.parse_and_verify ARGV
34
34
 
35
35
 
36
36
 
@@ -71,7 +71,7 @@ it gives the output:
71
71
 
72
72
  ```
73
73
  libCLImate.Ruby examples
74
- show_usage_and_version.rb 0.0.1
74
+ show_usage_and_version.rb 0.1.0
75
75
  Illustrates use of libCLImate.Ruby's automatic support for '--help' and '--version'
76
76
 
77
77
  USAGE: show_usage_and_version.rb [ ... flags and options ... ]
@@ -96,7 +96,7 @@ If executed with the arguments
96
96
  it gives the output:
97
97
 
98
98
  ```
99
- show_usage_and_version.rb 0.0.1
99
+ show_usage_and_version.rb 0.1.0
100
100
  ```
101
101
 
102
102
  ### Unknown option
@@ -114,4 +114,3 @@ show_usage_and_version.rb: unrecognised flag/option: --unknown=value
114
114
  ```
115
115
 
116
116
  with an exit code of 1
117
-
@@ -10,7 +10,7 @@ require 'libclimate'
10
10
 
11
11
  climate = LibCLImate::Climate.new do |cl|
12
12
 
13
- cl.version = [ 0, 0, 1 ]
13
+ cl.version = [ 0, 1, 0 ]
14
14
 
15
15
  cl.info_lines = [
16
16
 
@@ -21,7 +21,7 @@ climate = LibCLImate::Climate.new do |cl|
21
21
  ]
22
22
  end
23
23
 
24
- climate.run ARGV
24
+ climate.parse_and_verify ARGV
25
25
 
26
26
 
27
27
 
@@ -5,7 +5,7 @@
5
5
  # Purpose: Definition of the ::LibCLImate::Climate class
6
6
  #
7
7
  # Created: 13th July 2015
8
- # Updated: 15th April 2019
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
- return f if f.respond_to? :action
81
+ unless f.respond_to? :action
81
82
 
82
- class << f
83
+ class << f
83
84
 
84
- attr_accessor :action
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
- return o if o.respond_to? :action
110
+ unless o.respond_to? :action
109
111
 
110
- class << o
112
+ class << o
111
113
 
112
- attr_accessor :action
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, a|
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_ msg
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_ ctxt
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 source, options = (options_defaulted_ = {}), &blk
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
- @specifications << CLASP::Flag.Help(handle: proc { show_usage_ }) unless options[:no_help_flag]
496
- @specifications << CLASP::Flag.Version(handle: proc { show_version_ }) unless options[:no_version_flag]
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 name
869
+ def set_program_name(name)
505
870
 
506
- @program_name = 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
- # Executes the prepared Climate instance
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 argv = ARGV # :yields: customised +::Hash+
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 = CLASP::Arguments.new argv, specifications
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_ argv, arguments # :nodoc:
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
- al = specifications.detect do |a|
1026
+ spec = specifications.detect do |sp|
607
1027
 
608
- a.kind_of?(::CLASP::Flag) && f.name == a.name
1028
+ sp.kind_of?(::CLASP::FlagSpecification) && f.name == sp.name
609
1029
  end
610
1030
 
611
- if al
1031
+ if spec
612
1032
 
613
1033
  selector = :unhandled
614
1034
 
615
- # see if it has an :action attribute (which will have been
616
- # monkey-patched to CLASP.Flag()
1035
+ if spec.respond_to?(:action) && !spec.action.nil?
617
1036
 
618
- if al.respond_to?(:action) && !al.action.nil?
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
- al = specifications.detect do |a|
1073
+ spec = specifications.detect do |sp|
669
1074
 
670
- a.kind_of?(::CLASP::Option) && o.name == a.name
1075
+ sp.kind_of?(::CLASP::OptionSpecification) && o.name == sp.name
671
1076
  end
672
1077
 
673
- if al
1078
+ if spec
674
1079
 
675
1080
  selector = :unhandled
676
1081
 
677
- # see if it has an :action attribute (which will have been
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
- al.action.call(o, al)
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
- # now police any required options
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
- required_specifications.each do |k, a|
1120
+ # 3. Check values
741
1121
 
742
- unless given_options.has_key? k
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 message, options={}
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::Flag === name_or_flag
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::Option === name_or_option
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::Flag
1300
+ when ::CLASP::FlagSpecification
1026
1301
 
1027
1302
  self.specifications << name_or_specification
1028
- when ::CLASP::Option
1303
+ when ::CLASP::OptionSpecification
1029
1304
 
1030
1305
  self.specifications << name_or_specification
1031
1306
  else