como 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +24 -0
- data/lib/como.rb +1041 -0
- data/test/test_como.rb +344 -0
- 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
|