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.
- 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
|