como 0.0.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.
Files changed (6) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +15 -0
  3. data/Rakefile +24 -0
  4. data/lib/como.rb +1041 -0
  5. data/test/test_como.rb +344 -0
  6. metadata +56 -0
data/lib/como.rb ADDED
@@ -0,0 +1,1041 @@
1
+ # = Como
2
+ #
3
+ # == Introduction
4
+ # Como provides low manifest command line option parsing and
5
+ # handling. Command line options are described in a compact table
6
+ # format and option values are stored to conveniently named
7
+ # properties. Como displays the command usage information based on
8
+ # the option table (+ generic program info).
9
+ #
10
+ # == Simple example
11
+ # Below is a small example program ("como_test") that demonstrates
12
+ # typical usage.
13
+ #
14
+ # === Program listing
15
+ # require "como"
16
+ # include Como
17
+ #
18
+ # # Define command line arguments:
19
+ # Spec.defineCheckHelp( "como_test", "Programmer", "2013",
20
+ # [
21
+ # [ :silent, "help", "-h", "Display usage info." ],
22
+ # [ :single, "file", "-f", "File argument." ],
23
+ # [ :switch, "debug", "-d", "Enable debugging." ],
24
+ # ] )
25
+ #
26
+ # puts "File option: #{Opt['file'].value}"
27
+ # puts "Debugging selected!" if Opt['debug'].given?
28
+ #
29
+ # "Spec.defineCheckHelp" method takes 4 arguments:
30
+ # [progname] Name of the program (or command).
31
+ # [author] Author of the program.
32
+ # [year] Year (or any date) for the program.
33
+ # [option table] Description of the command options in format with 4
34
+ # entries in each sub-array.
35
+ #
36
+ # Each option table entry is an Array of 4 values: type, name,
37
+ # mnemonic, doc. Three different types are present in the example:
38
+ # [:silent] Silent is left out from the "usage" printout (see
39
+ # below). Also "help" is reserved as special option name to
40
+ # designate command line usage help.
41
+ # [:single] Single means that the option requires one argument (and only one).
42
+ # [:switch] Switch is an optional flag (default value is false).
43
+ #
44
+ # Option name is used to reference the option value from Opt class.
45
+ # The command line option values are stored to Opt class
46
+ # automatically. For example the file option value is returned by
47
+ # executing "Opt['file'].value". The option name also doubles as
48
+ # long option, i.e. one could use "--file <filename>" on the command
49
+ # line.
50
+ #
51
+ # Existence of optional options can be tested using the "given"
52
+ # method. For example "Opt['debug'].given" would return "true" if
53
+ # "-d" was given on the command line.
54
+ #
55
+ # === Example executions
56
+ # Normal behavior would be achieved by executing:
57
+ # shell> como_test -f example -d
58
+ #
59
+ # The program would execute with the following output:
60
+ # File option: example
61
+ # Debugging selected!
62
+ #
63
+ # Como includes certain "extra" behavior out-of-box. For example
64
+ # given the command:
65
+ # shell> como_test
66
+ #
67
+ # The following is displayed on the screen:
68
+ #
69
+ # como_test error: Option "-f" missing...
70
+ #
71
+ #
72
+ # Usage:
73
+ # como_test -f <file> [-d]
74
+ #
75
+ # -f File argument.
76
+ # -d Enable debugging.
77
+ #
78
+ #
79
+ # Copyright (c) 2013 by Programmer
80
+ #
81
+ # Missing option error is displayed since "file" is a mandatory
82
+ # option. The error display is followed by "usage" display.
83
+ #
84
+ # shell> como_test -h
85
+ #
86
+ # Would display the same "usage" screen except without the error
87
+ # line. Documentation string is taken from the specification to
88
+ # "usage" display.
89
+ #
90
+ #
91
+ # == Option types
92
+ #
93
+ # The following types can be defined for the command line options:
94
+ # [:switch] Single switch option (no arguments).
95
+ # [:single] Mandatory single argument option.
96
+ # [:multi] Mandatory multiple argument option. Option values in array.
97
+ # [:opt_single] Optional single argument option.
98
+ # [:opt_multi] Optional multiple argument option. Option values in array.
99
+ # [:opt_any] Optional multiple argument option (also none accepted).
100
+ # Option values in array.
101
+ # [:default] Default option (no switch associated). Any name and
102
+ # option String value can be supplied to the spec, since
103
+ # only the document string is used. Default option is
104
+ # referred with "nil".
105
+ # [:exclusive] Option that does not coexist with other options.
106
+ # [:silent] Option that does not coexist with other options and is not
107
+ # displayed as an option in "usage" display. In effect a
108
+ # sub-option of :exclusive.
109
+ #
110
+ #
111
+ # == Specification method options
112
+ #
113
+ # The common method for specifying the options is to use
114
+ # "Spec.defineCheckHelp". Method invocation includes definition
115
+ # of the options, parsing the command line, checking for missing
116
+ # mandatory options, and it will automatically display "usage" if
117
+ # "help" option is given.
118
+ #
119
+ # Automatic "help" option processing can be avoided using
120
+ # "Spec.defineCheck" instead.
121
+ #
122
+ # Both methods above accept additional parameters passed in a
123
+ # Hash. The usable hash keys:
124
+ # [:header] Header lines before standard usage printout.
125
+ # [:footer] Footer lines after standard usage printout.
126
+ # [:check] Check for missing arguments (default: true).
127
+ # [:help_exit] Exit program if help displayed (default: true).
128
+ # [:error_exit] Exit program if error in options (default: true).
129
+ #
130
+ #
131
+ # == Using Opt class
132
+ #
133
+ # Opt class includes the parsed option values. All options can be
134
+ # tested whether they are specified on the command line using
135
+ # "Opt['name'].given"
136
+ #
137
+ # "Opt['name'].value" returns the provided option value. For
138
+ # ":switch" type it is true/false value and for the other types a
139
+ # String or an Array of Strings.
140
+ #
141
+ # If an option takes multiple arguments, the value for the option is
142
+ # an Array. The values can be iterated simply by:
143
+ # Opt['files'].value.each do |val|
144
+ # puts val
145
+ # end
146
+ #
147
+ # With ":opt_any" type, the user should first check if the option was given:
148
+ # Opt['many_files_or_none'].given
149
+ # Then check how many arguments where given:
150
+ # Opt['many_files_or_none'].value.length
151
+ # And finally decide what to do.
152
+ #
153
+ # If the user gives the "--" option, the arguments after that option
154
+ # is returned as an Array with "Opt.external"
155
+ #
156
+ #
157
+ # == Customization
158
+ #
159
+ # If the default behavior is not satisfactory, changes can be
160
+ # implemented simply by overloading the existing functions. Some
161
+ # knowledge of the internal workings of Como is required though.
162
+ #
163
+ #
164
+ # == Additional checks
165
+ #
166
+ # Sometimes the options have to be used in combination to make sense
167
+ # for the program. Como provides a facility to create relations
168
+ # between options. Consider the following options spec:
169
+ # Spec.defineCheckHelp( "como_fulltest", "Programmer", "2013",
170
+ # [
171
+ # [ :silent, "help", "-h", "Display usage info." ],
172
+ # [ :single, "file", "-f", "File argument." ],
173
+ # [ :switch, "o1", "-o1", "o1" ],
174
+ # [ :opt_single, "o2", "-o2", "o2" ],
175
+ # [ :opt_single, "o3", "-o3", "o3" ],
176
+ # [ :opt_multi, "o4", "-o4", "o4" ],
177
+ # [ :opt_any, "o5", "-o5", "o5" ],
178
+ # [ :switch, "debug", "-d", "Enable debugging." ],
179
+ # ] )
180
+ #
181
+ # Spec.checkRule do
182
+ # all( 'file',
183
+ # one(
184
+ # all( 'o1', 'o2' ),
185
+ # one( 'o3', 'o4', 'o5' )
186
+ # )
187
+ # )
188
+ # end
189
+ #
190
+ # This spec includes multiple optional options ("o?"). The
191
+ # "Spec.checkRule" method accepts a block where option rule
192
+ # check DSL (Domain Specific Language) is used. The rule specifies
193
+ # that the "file" option has to be used in combination with some other
194
+ # options. These are "all( 'o1', 'o2' )" or "one( 'o3', 'o4', 'o5' )",
195
+ # i.e. either both "o1" and "o2", or one of ["o3","o4","o5]. The
196
+ # checker will validate this rule and error if for example the command
197
+ # line reads:
198
+ # shell> como_fulltest --file myfile -o3 black -o5
199
+ #
200
+ # The following rules can be used (in combination):
201
+ # [all] All options in the list.
202
+ # [one] One and only one from the list.
203
+ # [any] At least one of the list is given.
204
+ # [none] No options are required.
205
+ # [incr] Incremental options in order i.e. have to have previous to
206
+ # have later.
207
+ # [follow] Incremental options in order i.e. have to have later if has
208
+ # previous (combined options).
209
+
210
+ module Como
211
+
212
+ # IO stream options for Como classes.
213
+ class ComoCommon
214
+
215
+ # Default value for display output.
216
+ @@io = STDOUT
217
+
218
+ # Set @@io.
219
+ def ComoCommon.setIo( io )
220
+ @@io = io
221
+ end
222
+
223
+ # Get @@io.
224
+ def ComoCommon.getIo
225
+ @@io
226
+ end
227
+ end
228
+
229
+
230
+ # User interface for Como.
231
+ class Spec < ComoCommon
232
+
233
+ # Command line options source.
234
+ @@argv = ARGV
235
+
236
+ # Set of default options for prinout.
237
+ @@options = {
238
+ :header => nil,
239
+ :footer => nil,
240
+ :check => true,
241
+ :help_exit => true,
242
+ :error_exit => true,
243
+ }
244
+
245
+ # Set command line options source, i.e. @@argv (default: ARGV).
246
+ def Spec.setArgv( newArgv )
247
+ @@argv = newArgv
248
+ end
249
+
250
+ # Display program usage (and optionally exit).
251
+ def Spec.usage
252
+ @@io.puts Spec.usageNormal
253
+ exit( 1 ) if @@options[ :help_exit ]
254
+ end
255
+
256
+
257
+ # Usage info for Opt:s.
258
+ def Spec.usageNormal
259
+ str = ""
260
+
261
+ if @@options[ :header ]
262
+ str += @@options[ :header ]
263
+ str += "\n"
264
+ end
265
+
266
+ str += "
267
+
268
+ Usage:
269
+ #{Opt.progname} #{Opt.cmdline.join(" ")}
270
+
271
+ "
272
+ Opt.doc.each do |i|
273
+ str += ( " %-8s%s" % [ i[0], i[1..-1].join(" ") ] )
274
+ str += "\n"
275
+ end
276
+ str += "
277
+
278
+ Copyright (c) #{Opt.year} by #{Opt.author}
279
+
280
+ "
281
+
282
+ if @@options[ :footer ]
283
+ str += @@options[ :footer ]
284
+ str += "\n"
285
+ end
286
+
287
+ str
288
+ end
289
+
290
+
291
+ # Set optional header for "usage".
292
+ def Spec.setUsageHeader( str )
293
+ @@options[ :header ] = str
294
+ end
295
+
296
+ # Set optional footer for "usage".
297
+ def Spec.setUsageFooter( str )
298
+ @@options[ :footer ] = str
299
+ end
300
+
301
+
302
+ # The primary entry point to Como. Defines the command
303
+ # switches and parses the command line. Performs "usage"
304
+ # display if "help" was selected.
305
+ # @param prog [String] Program (i.e. command) name.
306
+ # @param author [String] Author of the program.
307
+ # @param year [String] Year (or dates) for program.
308
+ # @param defs [Array] Option definitions.
309
+ # @param option [Hash] Option definition's behavioral config (changes @@options defaults).
310
+ def Spec.defineCheckHelp( prog, author, year, defs, option = {} )
311
+ Spec.defineCheck( prog, author, year, defs, option )
312
+ Spec.usage if Opt['help'].given
313
+ end
314
+
315
+ # Same as "defineCheckHelp" except without automatic "help"
316
+ # option processing.
317
+ def Spec.defineCheck( prog, author, year, defs, option = {} )
318
+ begin
319
+ Spec.applyOptionDefaults( option )
320
+ @@options = option
321
+ Opt.specify( prog, author, year, defs )
322
+ Spec.check
323
+ rescue Opt::MissingArgument, Opt::InvalidOption => str
324
+ @@io.puts
325
+ Opt.error( str )
326
+ Spec.usage
327
+ exit( 1 ) if @@options[ :error_exit ]
328
+ end
329
+ end
330
+
331
+
332
+ # Check only.
333
+ def Spec.check
334
+ Opt.parse( @@argv, @@options[ :check ] )
335
+ Opt.checkMissing
336
+ end
337
+
338
+
339
+ # Overlay "option" on top of options defaults (@@options).
340
+ def Spec.applyOptionDefaults( option )
341
+ option.replace( @@options.merge( option ) )
342
+ end
343
+
344
+
345
+ # Check option combination rules.
346
+ def Spec.checkRule( &rule )
347
+ begin
348
+ raise( Opt::InvalidOption, "Option combination mismatch!" ) unless
349
+ Opt.checkRule( &rule )
350
+ rescue Opt::MissingArgument, Opt::InvalidOption => str
351
+ @@io.puts
352
+ Opt.error( str )
353
+
354
+ # Display the possible combination:
355
+ @@io.puts "\n Option combination rules:\n\n"
356
+ Opt::RuleDisplay.new.evalAndDisplay( &rule )
357
+
358
+ Spec.usage
359
+ end
360
+ end
361
+
362
+
363
+ # Additional option check.
364
+ # @param opt [String] Option name.
365
+ # @param error [String] Error string for false return values (from check).
366
+ # @param check [Proc] Checker proc run for the option. Either return false or generate an exception when errors found.
367
+ def Spec.checkAlso( opt, error, &check )
368
+ begin
369
+ if Opt[opt].check( &check ) != true
370
+ raise Opt::InvalidOption, error
371
+ end
372
+ rescue Opt::MissingArgument, Opt::InvalidOption => str
373
+ @@io.puts
374
+ Opt.error( str )
375
+ Spec.usage
376
+ exit( 1 )
377
+ end
378
+ end
379
+
380
+ end
381
+
382
+
383
+
384
+ # Opt includes all options spec information and parsed options
385
+ # and their values. Option instance is accessed with
386
+ # "Opt['name']". The option status (Opt instance) can be
387
+ # queried with for example "given" and "value" methods.
388
+
389
+ class Opt < ComoCommon
390
+
391
+ class Error < StandardError; end
392
+ class MissingArgument < Error; end
393
+ class InvalidOption < Error; end
394
+
395
+
396
+ # Option name.
397
+ attr_accessor :name
398
+
399
+ # Short option string.
400
+ attr_accessor :opt
401
+
402
+ # Long option string.
403
+ attr_accessor :longOpt
404
+
405
+ # Option type.
406
+ attr_accessor :type
407
+
408
+ # Option value.
409
+ attr_accessor :value
410
+
411
+ # Option documentation string.
412
+ attr_accessor :doc
413
+
414
+ # Is option specified?
415
+ attr_accessor :given
416
+
417
+ # Is option hidden (usage).
418
+ attr_accessor :silent
419
+
420
+ # Parsed option specs and option values.
421
+ @@opts = []
422
+
423
+ # Program external arguments (e.g. subprogram args)
424
+ @@external = nil
425
+
426
+
427
+ # Create Opt object:
428
+ # [name] Option name string.
429
+ # [opt] Switch string.
430
+ # [type] Option type. One of:
431
+ # * :switch
432
+ # * :single
433
+ # * :multi
434
+ # * :opt_single
435
+ # * :opt_multi
436
+ # * :opt_any
437
+ # * :default
438
+ # * :exclusive
439
+ # * :silent
440
+ # [doc] Option documentation.
441
+ # [value] Default value.
442
+
443
+ def initialize( name, opt, type, doc, value = nil )
444
+ @name = name
445
+ @opt = opt
446
+ @longOpt = "--#{name}"
447
+ @type = type
448
+ @value = value
449
+ @doc = doc
450
+ @silent = false
451
+ # Whether option was set or not.
452
+ @given = false
453
+ @@opts.push self
454
+ end
455
+
456
+
457
+ # Options list iterator.
458
+ def Opt.each
459
+ @@opts.each do |o|
460
+ yield o
461
+ end
462
+ end
463
+
464
+
465
+ # Options iterator for given options.
466
+ def Opt.each_given
467
+ @@opts.each do |o|
468
+ yield o if o.given
469
+ end
470
+ end
471
+
472
+
473
+ # Number of given options.
474
+ def Opt.givenCount
475
+ cnt = 0
476
+ Opt.each_given do |i|
477
+ cnt += 1
478
+ end
479
+ cnt
480
+ end
481
+
482
+
483
+ # Return option value if given otherwise the default.
484
+ # Example usage: fileName = Opt["file"].apply( "no_name.txt" )
485
+ def apply( default = nil )
486
+ if given
487
+ value
488
+ else
489
+ default
490
+ end
491
+ end
492
+
493
+
494
+ # Check for any non-given required arguments.
495
+ def Opt.checkMissing
496
+
497
+ # Check for any exclusive args first
498
+ @@opts.each do |o|
499
+ if o.type == :exclusive && o.given
500
+ return
501
+ end
502
+ end
503
+
504
+ @@opts.each do |o|
505
+ if o.isRequired
506
+ raise MissingArgument, "Option \"#{o.opt}\" missing..." if !o.given
507
+ end
508
+ end
509
+ end
510
+
511
+
512
+ # Reset "dynamic" class members.
513
+ def Opt.reset
514
+ @@opts = []
515
+ @@external = nil
516
+ end
517
+
518
+
519
+ # Select option object by name operator.
520
+ def Opt.[](str)
521
+ Opt.arg(str)
522
+ end
523
+
524
+
525
+ # Select option object by name.
526
+ def Opt.arg( str )
527
+ if str == nil
528
+ @@opts.each do |o|
529
+ if o.type == :default
530
+ return o
531
+ end
532
+ end
533
+ nil
534
+ else
535
+ @@opts.each do |o|
536
+ if str == o.name
537
+ return o
538
+ end
539
+ end
540
+ nil
541
+ end
542
+ end
543
+
544
+
545
+ # Return program name.
546
+ def Opt.progname
547
+ @@progname
548
+ end
549
+
550
+ # Return program year.
551
+ def Opt.year
552
+ @@year
553
+ end
554
+
555
+ # Return author.
556
+ def Opt.author
557
+ @@author
558
+ end
559
+
560
+
561
+ # Return options that are specified as command external
562
+ # (i.e. after '--').
563
+ def Opt.external
564
+ @@external
565
+ end
566
+
567
+ # Return document strings for options.
568
+ def Opt.doc
569
+ doc = []
570
+ @@opts.each do |o|
571
+ next if o.silent?
572
+ doc.push( [o.opt ? o.opt : o.longOpt, o.doc] )
573
+ end
574
+ doc
575
+ end
576
+
577
+
578
+
579
+ # Set of methods which represent option combination checking.
580
+ # In effect this is a meta language (DSL) for option
581
+ # combinations.
582
+ #
583
+ # Example:
584
+ # RuleCheck.checkRule do
585
+ # one(
586
+ # incr( "gcov", "exclude", "refreshed" ),
587
+ # "manifest",
588
+ # "pairs",
589
+ # "files"
590
+ # )
591
+ # end
592
+ class RuleCheck
593
+
594
+ # Get given count.
595
+ def getScore( *args )
596
+ score = 0
597
+ args.each do |i|
598
+ if i.class == TrueClass || i.class == FalseClass
599
+ score += 1 if i
600
+ else
601
+ score += 1 if Opt[i].given
602
+ end
603
+ end
604
+ score
605
+ end
606
+
607
+ # Special condition when no options are given.
608
+ def none
609
+ Opt.givenCount == 0
610
+ end
611
+
612
+ # Incremental options in order i.e. have to have previous
613
+ # to have later.
614
+ def incr( *args )
615
+ had = 0
616
+ add = true
617
+ scoreAll = 0
618
+ i = 0
619
+ while args[i]
620
+ score = getScore( args[i] )
621
+ had += 1 if ( score == 1 ) && add
622
+ add = false if ( score == 0 )
623
+ scoreAll += score
624
+ i += 1
625
+ end
626
+
627
+ ( had == scoreAll && had > 0 )
628
+ end
629
+
630
+ # Incremental options in order i.e. have to have all later
631
+ # if had first.
632
+ def follow( *args )
633
+ if getScore( args[0] )
634
+ getScore( *args ) == args.length
635
+ else
636
+ true
637
+ end
638
+ end
639
+
640
+ # One of list given.
641
+ def one( *args )
642
+ getScore( *args ) == 1
643
+ end
644
+
645
+ # At least one is given.
646
+ def any( *args )
647
+ getScore( *args ) > 0
648
+ end
649
+
650
+ # All are given.
651
+ def all( *args )
652
+ getScore( *args ) == args.length
653
+ end
654
+ end
655
+
656
+
657
+
658
+ # Display utility for RuleCheck. Same usage model.
659
+ #
660
+ # Example expansion of options:
661
+ #
662
+ # |--# One of:
663
+ # | |--# Adding in order:
664
+ # | | |--<gcov>
665
+ # | | |--<exclude>
666
+ # | | |--<refreshed>
667
+ # | |--<manifest>
668
+ # | |--<pairs>
669
+ # | |--<files>
670
+ #
671
+ class RuleDisplay < ComoCommon
672
+
673
+ def initialize
674
+ # Prefix string for lines. Rules add/rm from it.
675
+ @prefixStr = " "
676
+ self
677
+ end
678
+
679
+ # Eval rules to get an nested array and then display it.
680
+ def evalAndDisplay( &rule )
681
+ printRule( instance_eval( &rule ) )
682
+ end
683
+
684
+ # Increase prefix string.
685
+ def addPrefix( str )
686
+ @prefixStr += str
687
+ end
688
+
689
+ # Remove from prefix (either str or length ).
690
+ def rmPrefix( item )
691
+ if item.class == String
692
+ cnt = item.length
693
+ else
694
+ cnt = item
695
+ end
696
+ @prefixStr = @prefixStr[0..-(cnt+1)]
697
+ end
698
+
699
+ # Print prefix + str.
700
+ def p( str )
701
+ @@io.puts( @prefixStr + str )
702
+ end
703
+
704
+ # Recursively go through the nested array of rule items and
705
+ # print out rules.
706
+ def printRule( arr )
707
+ p( "|--# #{arr[0]}:" )
708
+ item = "| "
709
+ addPrefix( item )
710
+
711
+ arr[1..-1].each do |i|
712
+ if i.class == Array
713
+ printRule( i )
714
+ else
715
+ p( "|--<#{i}>" )
716
+ end
717
+ end
718
+ rmPrefix( item )
719
+ end
720
+
721
+ # Special condition where no arguments are given.
722
+ def none
723
+ [ "NONE" ]
724
+ end
725
+
726
+ # Incremental options in order i.e. have to have previous
727
+ # to have later.
728
+ def incr( *args )
729
+ [ "Adding in order", *args ]
730
+ end
731
+
732
+ # Incremental options in order i.e. have to have all later
733
+ # if had first.
734
+ def follow( *args )
735
+ [ "If first then rest", *args ]
736
+ end
737
+
738
+ # One of list given.
739
+ def one( *args )
740
+ [ "One of", *args ]
741
+ end
742
+
743
+ # At least one is given.
744
+ def any( *args )
745
+ [ "One or more of", *args ]
746
+ end
747
+
748
+ # All are given.
749
+ def all( *args )
750
+ [ "All of", *args ]
751
+ end
752
+
753
+ end
754
+
755
+
756
+
757
+ # Check against option combination rules.
758
+ def Opt.checkRule( &rule )
759
+ RuleCheck.new.instance_eval( &rule )
760
+ end
761
+
762
+
763
+ # Create option spec.
764
+ def Opt.full( name, opt, type, doc = "No doc." )
765
+ new( name, opt, type, doc )
766
+ end
767
+
768
+ # Create switch option spec.
769
+ def Opt.switch( name, opt, doc = "No doc." )
770
+ new( name, opt, :switch, doc, false )
771
+ end
772
+
773
+ # Create exclusive option spec.
774
+ def Opt.exclusive( name, opt, doc = "No doc.", silent = false )
775
+ new( name, opt, :exclusive, doc, false )
776
+ @@opts[-1].silent = silent
777
+ end
778
+
779
+ # Create default option spec, no switch.
780
+ def Opt.default( doc = "No doc." )
781
+ new( nil, "<arg>", :default, doc, [] )
782
+ end
783
+
784
+
785
+ # Specify and check options spec.
786
+ def Opt.specify( progname, author, year, table )
787
+
788
+ # Type checks for valid user input.
789
+ check = Proc.new do |cond,str| raise( ArgumentError, str ) unless cond end
790
+ check.call( progname.class == String, "Program name is not a String" )
791
+ check.call( author.class == String, "Author name is not a String" )
792
+ check.call( year.class == String, "Year is not a String" )
793
+ check.call( table.class == Array, "Option table is not an Array" )
794
+ table.each do |i|
795
+ check.call( i.class == Array, "Option table entry is not an Array" )
796
+ check.call( i.length == 4, "Option table entry length not 4" )
797
+ end
798
+
799
+
800
+ @@progname = progname
801
+ @@year = year
802
+ @@author = author
803
+
804
+ table.each do |e|
805
+
806
+ case e[0]
807
+ when :switch
808
+ Opt.switch( e[1], e[2], e[3] )
809
+ when :exclusive
810
+ Opt.exclusive( e[1], e[2], e[3] )
811
+ when :silent
812
+ Opt.exclusive( e[1], e[2], e[3], true )
813
+ when :single, :multi, :opt_single, :opt_multi, :opt_any
814
+ Opt.full( e[1], e[2], e[0], e[3] )
815
+ when :default
816
+ Opt.default( e[3] )
817
+ end
818
+ end
819
+ end
820
+
821
+
822
+ # Test whether str is an option.
823
+ def Opt.isOpt( str )
824
+ str[0..0] == "-"
825
+ end
826
+
827
+ # Test whether str is an option list terminator.
828
+ def Opt.isOptTerm( str )
829
+ str == "--"
830
+ end
831
+
832
+ # Format value string if escaped.
833
+ def Opt.toValue( str )
834
+ if str[0..0] == "\\"
835
+ str[1..-1]
836
+ else
837
+ str
838
+ end
839
+ end
840
+
841
+ # Option requires argument?
842
+ def hasArg
843
+ case @type
844
+ when :single, :multi, :opt_single, :opt_multi, :opt_any; true
845
+ else false
846
+ end
847
+ end
848
+
849
+ # Option requires many arguments?
850
+ def hasMany
851
+ case @type
852
+ when :multi, :opt_multi, :opt_any; true
853
+ else false
854
+ end
855
+ end
856
+
857
+ # Is mandatory argument?
858
+ def isRequired
859
+ case @type
860
+ when :single, :multi; true
861
+ else false
862
+ end
863
+ end
864
+
865
+ # Test if option is of switch type.
866
+ def isSwitch
867
+ case @type
868
+ when :switch, :exclusive, :default; true
869
+ else false
870
+ end
871
+ end
872
+
873
+ # Test if option is silent.
874
+ def silent?
875
+ @silent
876
+ end
877
+
878
+ # Custom check for the option. User have to know some Como
879
+ # internals.
880
+ def check( &check )
881
+ instance_eval &check
882
+ end
883
+
884
+
885
+ # Find option object by option str.
886
+ def Opt.findOpt( str )
887
+ if str == nil
888
+ ( @@opts.select { |i| i.type == :default } )[0]
889
+ elsif str[0..1] == "--"
890
+ ( @@opts.select { |i| i.longOpt == str } )[0]
891
+ else
892
+ ( @@opts.select { |i| i.opt == str } )[0]
893
+ end
894
+ end
895
+
896
+ # Como error printout.
897
+ def Opt.error( str )
898
+ @@io.puts "#{@@progname} error: #{str}"
899
+ end
900
+
901
+
902
+ # Parse cmdline options from args.
903
+ def Opt.parse( args, checkInvalids = true )
904
+ ind = 0
905
+
906
+ while args[ind]
907
+
908
+ if Opt.isOptTerm( args[ind] )
909
+
910
+ # Rest of the args do not belong to this program.
911
+ ind += 1
912
+ @@external = args[ind..-1]
913
+ break
914
+
915
+ elsif Opt.isOpt( args[ind] )
916
+
917
+ o = Opt.findOpt( args[ind] )
918
+
919
+ if !o
920
+ if checkInvalids
921
+ raise InvalidOption, \
922
+ "Unknown option (\"#{args[ind]}\")..."
923
+ else
924
+ o = Opt.findOpt( nil )
925
+ if !o
926
+ raise InvalidOption, "No default option specified for \"#{args[ind]}\"..."
927
+ else
928
+ # Default option.
929
+ o.value.push Opt.toValue( args[ind] )
930
+ ind += 1
931
+ end
932
+ end
933
+
934
+ elsif o && o.hasArg
935
+
936
+ ind += 1
937
+
938
+ if ( !args[ind] || Opt.isOpt( args[ind] ) ) &&
939
+ o.type != :opt_any
940
+
941
+ raise MissingArgument, \
942
+ "No argument given for \"#{o.opt}\"..."
943
+
944
+ else
945
+
946
+ if o.hasMany
947
+
948
+ # Get all argument for multi-option.
949
+ o.value = [] if !o.given
950
+ while ( args[ind] &&
951
+ !Opt.isOpt( args[ind] ) )
952
+ o.value.push Opt.toValue( args[ind] )
953
+ ind += 1
954
+ end
955
+
956
+ else
957
+
958
+ # Get one argument for single-option.
959
+
960
+ if o.given
961
+ raise InvalidOption, \
962
+ "Too many arguments for option (\"#{o.name}\")..."
963
+ else
964
+ o.value = Opt.toValue( args[ind] )
965
+ end
966
+ ind += 1
967
+ end
968
+ end
969
+
970
+ o.given = true
971
+
972
+ else
973
+
974
+ if !o
975
+ raise InvalidOption, "No valid options specified..."
976
+ else
977
+ o.value = !o.value if !o.given
978
+ o.given = true
979
+ ind += 1
980
+ end
981
+ end
982
+
983
+ else
984
+
985
+ # Default option.
986
+
987
+ o = Opt.findOpt( nil )
988
+
989
+ if !o
990
+ raise InvalidOption, "No default option specified for \"#{args[ind]}\"..."
991
+ else
992
+ while ( args[ind] && !Opt.isOpt( args[ind] ) )
993
+ o.value.push Opt.toValue( args[ind] )
994
+ ind += 1
995
+ end
996
+ end
997
+ end
998
+ end
999
+ end
1000
+
1001
+
1002
+ # Return cmdline usage strings for options in an Array.
1003
+ def Opt.cmdline
1004
+ opts = []
1005
+
1006
+ @@opts.each do |o|
1007
+
1008
+ next if o.silent?
1009
+
1010
+ prural = nil
1011
+ case o.type
1012
+ when :multi, :opt_multi; prural = "+"
1013
+ when :opt_any; prural = "*"
1014
+ else prural = ""
1015
+ end
1016
+
1017
+ if !( o.isSwitch )
1018
+ name = " <#{o.name}>#{prural}"
1019
+ else
1020
+ name = ""
1021
+ end
1022
+
1023
+ if o.opt == nil
1024
+ opt = o.longOpt
1025
+ else
1026
+ opt = o.opt
1027
+ end
1028
+
1029
+ if o.isRequired
1030
+ opts.push "#{opt}#{name}"
1031
+ else
1032
+ opts.push "[#{opt}#{name}]"
1033
+ end
1034
+ end
1035
+
1036
+ opts
1037
+ end
1038
+
1039
+ end
1040
+
1041
+ end