como 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/README.rdoc +11 -5
  3. data/Rakefile +6 -1
  4. data/doc/Como/ArgsParseState.html +912 -0
  5. data/doc/Como/ComoCommon.html +305 -0
  6. data/doc/Como/MainOpt.html +636 -0
  7. data/doc/Como/MasterOpt.html +636 -0
  8. data/doc/Como/Opt/ErrorWithData.html +304 -0
  9. data/doc/Como/Opt/InvalidOption.html +158 -0
  10. data/doc/Como/Opt/MissingArgument.html +158 -0
  11. data/doc/Como/Opt.html +6098 -0
  12. data/doc/Como/RuleCheck.html +933 -0
  13. data/doc/Como/RuleDisplay.html +1193 -0
  14. data/doc/Como/Spec.html +1750 -0
  15. data/doc/Como.html +625 -0
  16. data/doc/_index.html +242 -0
  17. data/doc/class_list.html +53 -0
  18. data/doc/css/common.css +1 -0
  19. data/doc/css/full_list.css +57 -0
  20. data/doc/css/style.css +338 -0
  21. data/doc/file.CHANGELOG.html +90 -0
  22. data/doc/file.README.html +94 -0
  23. data/doc/file.como.html +1962 -0
  24. data/doc/file_list.html +58 -0
  25. data/doc/frames.html +28 -0
  26. data/doc/index.html +94 -0
  27. data/doc/js/app.js +214 -0
  28. data/doc/js/full_list.js +178 -0
  29. data/doc/js/jquery.js +4 -0
  30. data/doc/method_list.html +838 -0
  31. data/doc/top-level-namespace.html +112 -0
  32. data/lib/como.rb +1660 -661
  33. data/test/como_compatible +37 -0
  34. data/test/como_config +44 -0
  35. data/test/como_options +36 -0
  36. data/test/como_queries +44 -0
  37. data/test/como_rule_1 +28 -0
  38. data/test/como_rule_2 +28 -0
  39. data/test/como_subcmd +72 -0
  40. data/test/como_subcmd_config +88 -0
  41. data/test/golden/compatible.txt +438 -0
  42. data/test/golden/config.txt +319 -0
  43. data/test/golden/options.txt +438 -0
  44. data/test/golden/queries.txt +78 -0
  45. data/test/golden/rule_1.txt +454 -0
  46. data/test/golden/rule_2.txt +476 -0
  47. data/test/golden/subcmd.txt +360 -0
  48. data/test/golden/subcmd_config.txt +534 -0
  49. data/test/test_como.rb +22 -328
  50. data/test/test_compatible +28 -0
  51. data/test/test_config +12 -0
  52. data/test/test_options +28 -0
  53. data/test/test_queries +7 -0
  54. data/test/test_rule_1 +27 -0
  55. data/test/test_rule_2 +27 -0
  56. data/test/test_subcmd +30 -0
  57. data/test/test_subcmd_config +31 -0
  58. metadata +62 -6
data/lib/como.rb CHANGED
@@ -1,76 +1,108 @@
1
1
  # = Como
2
2
  #
3
3
  # == Introduction
4
+ #
4
5
  # Como provides low manifest command line option parsing and
5
- # handling. Command line options are described in a compact table
6
+ # deployment. The command line options are described in compact table
6
7
  # 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).
8
+ # properties. Como builds command usage information based on the
9
+ # option table (+ generic program info) and displays it automatically
10
+ # if necessary. Como supports also subcommands and checking for option
11
+ # combinations using a simple DSL.
12
+ #
13
+ #
14
+ #
15
+ # == Usage Examples
9
16
  #
10
- # == Simple example
11
- # Below is a small example program ("como_test") that demonstrates
17
+ # Two simple examples are presented in this section. First one
18
+ # includes a straight forward command definition and the second is a
19
+ # bit more complicated with subcommand feature in use.
20
+ #
21
+ # === Simple example
22
+ #
23
+ # Below is a small example program ("como_simple") that demonstrates
12
24
  # typical usage.
13
25
  #
14
- # === Program listing
26
+ # ==== Program listing
27
+ #
15
28
  # require "como"
16
29
  # include Como
17
30
  #
18
31
  # # Define command line arguments:
19
- # Spec.defineCheckHelp( "como_test", "Programmer", "2013",
32
+ # Spec.command( "como_simple", "Programmer", "2013",
20
33
  # [
21
- # [ :silent, "help", "-h", "Display usage info." ],
22
- # [ :single, "file", "-f", "File argument." ],
34
+ # [ :single, "file", "-f", "File argument." ],
23
35
  # [ :switch, "debug", "-d", "Enable debugging." ],
24
36
  # ] )
25
37
  #
26
- # puts "File option: #{Opt['file'].value}"
27
- # puts "Debugging selected!" if Opt['debug'].given
38
+ # puts " File option: #{Opt['file'].value}"
39
+ # puts " Debugging selected!" if Opt['debug'].given
40
+ #
28
41
  #
29
- # "Spec.defineCheckHelp" method takes 4 arguments:
42
+ # First Como is required and Como module is included.
43
+ #
44
+ # "Spec.command" method takes 4 arguments:
30
45
  # [progname] Name of the program (or command).
31
46
  # [author] Author of the program.
32
47
  # [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.
48
+ # [option table] Description of the command options.
35
49
  #
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).
50
+ # Each option table entry (row/sub-array) includes 4 fields and
51
+ # specifies one option:
52
+ # [ type, name, mnemonic, doc ]
53
+ #
54
+ # Two different types are present in the example:
55
+ # [:single] Single means that the option requires one argument (and
56
+ # only one).
42
57
  # [:switch] Switch is an optional flag (default value is false).
43
58
  #
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.
59
+ # Option name is used to reference the option value that user has
60
+ # given. The command line option values are stored automatically. For
61
+ # example the file option value is returned by:
62
+ # Opt['file'].value
63
+ # The option name also doubles as long option format, i.e. one could
64
+ # use "--file <filename>" on the command line.
50
65
  #
51
66
  # 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.
67
+ # method. For example
68
+ # Opt['debug'].given
69
+ # would return "true" if "-d" was given on the command line.
70
+ #
71
+ # Mnemonic is the short form option specification e.g. "-f". If short
72
+ # form is replaced with "nil", the long option format is only
73
+ # available.
74
+ #
75
+ # Doc includes documentation for the option. It is displayed when
76
+ # "help" ("-h") option is given. Help option is added to the command
77
+ # automatically as default behavior.
78
+ #
79
+ # ==== Simple example executions
54
80
  #
55
- # === Example executions
56
81
  # Normal behavior would be achieved by executing:
57
- # shell> como_test -f example -d
82
+ # shell> como_simple -f example -d
58
83
  #
59
84
  # The program would execute with the following output:
60
- # File option: example
61
- # Debugging selected!
85
+ # File option: example
86
+ # Debugging selected!
87
+ #
88
+ # Same output would be achieved with:
89
+ # shell> como_simple --file example --debug
90
+ #
91
+ # Since option name doubles as long option.
62
92
  #
63
- # Como includes certain "extra" behavior out-of-box. For example
64
- # given the command:
65
- # shell> como_test
93
+ # Como includes certain "extra" behavior out-of-box. Required
94
+ # arguments are checked for existence and error is displayed if
95
+ # arguments are not given.
96
+ #
97
+ # For example given the command:
98
+ # shell> como_simple
66
99
  #
67
100
  # The following is displayed on the screen:
68
- #
69
- # como_test error: Option "-f" missing...
70
- #
101
+ #
102
+ # como_simple error: Option "-f" missing for "como_simple"...
71
103
  #
72
104
  # Usage:
73
- # como_test -f <file> [-d]
105
+ # como_simple -f <file> [-d]
74
106
  #
75
107
  # -f File argument.
76
108
  # -d Enable debugging.
@@ -78,64 +110,236 @@
78
110
  #
79
111
  # Copyright (c) 2013 by Programmer
80
112
  #
113
+ #
81
114
  # Missing option error is displayed since "file" is a mandatory
82
- # option. The error display is followed by "usage" display.
115
+ # option. The error message is followed by "usage" display (Usage
116
+ # Help). Documentation string is taken from the option specification to
117
+ # "usage" display.
118
+ #
119
+ # Given the command:
120
+ # shell> como_simple -h
121
+ #
122
+ # would display the same "usage" screen except without the error
123
+ # line.
124
+ #
125
+ # === Subccommand example
126
+ #
127
+ # Subcmd example includes a program which has subcommands. Subcommands
128
+ # can have their own command line switches and options.
129
+ #
130
+ # ==== Program listing
131
+ #
132
+ # require "como"
133
+ # include Como
134
+ #
135
+ # Spec.program( "Programmer", "2013" ) do
136
+ #
137
+ # subcmd( "como_subcmd", [
138
+ # [ :subcmd, "add", nil, "Add file." ],
139
+ # [ :subcmd, "rm", nil, "Remove file." ],
140
+ # ], )
141
+ #
142
+ # subcmd( "add", [
143
+ # [ :switch, "force", "-fo", "Force operation." ],
144
+ # [ :opt_single, "password", "-p", "User password." ],
145
+ # [ :opt_single, "username", "-u", "Username." ],
146
+ # [ :single, "file", "-f", "File." ],
147
+ # ] )
148
+ #
149
+ # checkRule do
150
+ # one(
151
+ # '-fo',
152
+ # all( 'password', 'username' )
153
+ # )
154
+ # end
155
+ #
156
+ # subcmd( "rm", [
157
+ # [ :single, "file", "-f", "File." ],
158
+ # ] )
159
+ #
160
+ # end
161
+ #
162
+ # subcmd = Opt.main.givenSubcmd
163
+ #
164
+ # case subcmd.name
165
+ # when 'add'; puts " Adding file \"#{subcmd['file'].value}\"..."
166
+ # when 'rm'; puts " Removing file \"#{subcmd['file'].value}\"..."
167
+ # end
168
+ #
169
+ # "Spec.program" method defines a program (command) with
170
+ # subcommands. The author and date are provided as parameters, and the
171
+ # program and subcommand options are defined in block.
172
+ #
173
+ # The first "subcmd" method call defines the main command
174
+ # ("Opt.main") which represents the program. It has two "subcmd"
175
+ # options.
176
+ #
177
+ # The rest of the "subcmd" methods define subcommands for the parent
178
+ # command. This example includes one subcommand level, but multiple
179
+ # levels are allowed.
180
+ #
181
+ # The "checkRule" method defines option combination (RuleCheck) for
182
+ # the previous subcommand definition. In this case the definition
183
+ # allows "add" to have either the "-fo" option defined or "password"
184
+ # and "username" in combination.
185
+ #
186
+ # Main (root) commands can be referenced through
187
+ # Opt.main
188
+ # or alternatively
189
+ # Opt['como_subcmd']
190
+ #
191
+ # The subcommands can be referenced through "Opt.main" (etc.)
192
+ # Opt.main['add']
193
+ # Opt['como_subcmd']['add']
194
+ #
195
+ # or directly from "Opt" if subcommand names do not collide:
196
+ # Opt['add']
197
+ #
198
+ # The given subcommand can be accessed with "givenSubcmd" method from
199
+ # each parent command.
200
+ #
201
+ # ==== Subcommand example executions
202
+ #
203
+ # Normal behavior would be achieved by executing:
204
+ # shell> como_subcmd add -fo -f example
205
+ #
206
+ # The program would execute with the following output:
207
+ # Adding file "example"...
208
+ #
209
+ # The option combinations for "add" subcommand are automatically
210
+ # checked. Thus executing:
211
+ # shell> como_subcmd add -f example
212
+ #
213
+ # Would result to:
214
+ # como_subcmd error: Option combination mismatch!
215
+ #
216
+ # Subcommand "add" usage:
217
+ # como_subcmd add [-fo] [-p <password>] [-u <username>] -f <file>
218
+ #
219
+ # -fo Force operation.
220
+ # -p User password.
221
+ # -u Username.
222
+ # -f File.
223
+ #
224
+ #
225
+ # Option Combinations:
226
+ # |--# One of:
227
+ # | |--<-fo>
228
+ # | |--# All of:
229
+ # | | |--<password>
230
+ # | | |--<username>
231
+ #
232
+ # Since the combination rule requires either "-fo" or both "password"
233
+ # and "username" in a pair.
234
+ #
235
+ # Help is automatically provided on each command level, thus these are
236
+ # both valid.
237
+ # shell> como_subcmd -h
238
+ # and
239
+ # shell> como_subcmd rm -h
83
240
  #
84
- # shell> como_test -h
85
241
  #
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
242
  #
243
+ # == Option specification
244
+ #
245
+ # === Overview
246
+ #
247
+ # Option specification includes the minimum set of information
248
+ # required for command line parsing. It is used to:
249
+ # * Parse the command line.
250
+ # * Check for wrong options and report.
251
+ # * Check for mandatory arguments and report.
252
+ # * Set the options given/non-given state.
253
+ # * Set the options value. Array/String for all except true/false for
254
+ # switches.
255
+ # * Generate Usage Help printout.
90
256
  #
91
- # == Option types
257
+ # === Option types
92
258
  #
93
259
  # The following types can be defined for the command line options:
260
+ # [:subcmd] Subcmd option. Subcmd specific options are provided
261
+ # separately.
94
262
  # [:switch] Single switch option (no arguments).
95
263
  # [:single] Mandatory single argument option.
96
- # [:multi] Mandatory multiple argument option. Option values in array.
264
+ # [:multi] Mandatory multiple argument option (one or many). Option
265
+ # values in array.
97
266
  # [: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".
267
+ # [:opt_multi] Optional multiple argument option (one or many). Option
268
+ # values in array.
269
+ # [:opt_any] Optional multiple argument option (also none
270
+ # accepted). Option values in array.
271
+ # [:default] Default option (no switch associated). Name and option
272
+ # String values can be left out, since only the document
273
+ # string is used. Default option is referred with
274
+ # ":default" or "nil".
105
275
  # [:exclusive] Option that does not coexist with other options.
106
276
  # [:silent] Option that does not coexist with other options and is not
107
- # displayed as an option in "usage" display. In effect a
277
+ # displayed as an option in Usage Help display. In effect a
108
278
  # sub-option of :exclusive.
109
279
  #
280
+ # Options use typically all the 4 option fields:
281
+ # [ type, name, mnemonic, doc ]
282
+ #
283
+ # "type" field is mandatory for all options.
284
+ #
285
+ # "name" field is also mandatory for all options. "mnemonic" can be
286
+ # left out, but then option accepts only long option format.
110
287
  #
111
- # == Specification method options
288
+ # ":default" uses only "doc" and ":subcmd" doesn't use the "mnemonic"
289
+ # field.
290
+ #
291
+ # ":multi", ":opt_multi", and ":opt_any" option arguments are
292
+ # terminated only when an option specifier is found. This can be a
293
+ # problem if ":default" option follows. The recommended solution is to
294
+ # use a ":silent" option that can be used to terminate the argument
295
+ # list. For example:
296
+ # [ :silent, "terminator", "-", "The terminator." ],
297
+ #
298
+ #
299
+ # === Option specification method configuration
112
300
  #
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.
301
+ # Option behavior can be controlled with several configuration options.
118
302
  #
119
- # Automatic "help" option processing can be avoided using
120
- # "Spec.defineCheck" instead.
303
+ # The configuration options are provided in a Hash. These are the
304
+ # passed as the last regular parameter for both "Spec.command" and
305
+ # "Spec.program" methods. Setting the configuration at "Spec.program"
306
+ # will propagate the config options to all the subcommands as
307
+ # well. Configuration can be given to each subcommand separately to
308
+ # override the inherited config values. Subcommand settings are not
309
+ # inherited, but apply only in the subcommand.
121
310
  #
122
- # Both methods above accept additional parameters passed in a
123
- # Hash. The usable hash keys:
311
+ # The usable configuration Hash keys:
312
+ # [:autohelp] Add help option automatically (default: true). Custom
313
+ # help option can be provided and it can be made also
314
+ # visible to user.
315
+ # [:rulehelp] Include RuleCheck help to Usage Help (default: false).
124
316
  # [:header] Header lines before standard usage printout.
125
317
  # [: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).
318
+ # [:subcheck] Automatically check that a subcommand is provided
319
+ # (default: true).
320
+ # [:check_missing] Check for missing arguments (default: true).
321
+ # [:tab] Tab stop column for option documentation (default: 12).
322
+ # [:help_exit] Exit program if help displayed (default: true).
323
+ # [:error_exit] Exit program if error in options (default: true).
324
+ #
129
325
  #
130
326
  #
131
- # == Using Opt class
327
+ # == Option referencing
132
328
  #
329
+ # === Existence and values
330
+ #
133
331
  # 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"
332
+ # tested whether they are specified on the command line using:
333
+ # Opt['name'].given
334
+ #
335
+ # The "given" method takes optionally a block argument. When block
336
+ # argument is used, the block is supplied with option value and the
337
+ # block is executed if the option has been set (See: Opt#given).
136
338
  #
137
- # "Opt['name'].value" returns the provided option value. For
138
- # ":switch" type it is true/false value and for the other types a
339
+ # Provided value is returned by:
340
+ # Opt['name'].value
341
+ #
342
+ # For ":switch" type it is true/false value and for the other types a
139
343
  # String or an Array of Strings.
140
344
  #
141
345
  # If an option takes multiple arguments, the value for the option is
@@ -144,71 +348,77 @@
144
348
  # puts val
145
349
  # end
146
350
  #
351
+ # Short syntax for value referencing is performed with unary operator
352
+ # "~". Thus
353
+ # ~Opt['files']
354
+ # is equal to
355
+ # Opt['files'].value
356
+ #
147
357
  # With ":opt_any" type, the user should first check if the option was given:
148
358
  # Opt['many_files_or_none'].given
149
359
  # Then check how many arguments where given:
150
360
  # Opt['many_files_or_none'].value.length
151
361
  # And finally decide what to do.
152
362
  #
153
- # If the user gives the "--" option, the arguments after that option
154
- # is returned as an Array with "Opt.external"
363
+ # === Options including parameters
155
364
  #
365
+ # Sometimes it is convenient for the program to use an option to
366
+ # include multiple parameter settings. These settings can be parsed
367
+ # and mapped to a Hash. Como performs automatic conversion to numeric
368
+ # values if possible. For example with option:
369
+ # --set rounds=10 length=5
370
+ # Como can be used extract the parameter values with the "params" method:
371
+ # Opt['set'].params
372
+ # And a Hash is returned:
373
+ # { 'rounds' => 10, 'length' => 5 }
156
374
  #
157
- # == Customization
375
+ # === Subcommand options
158
376
  #
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.
377
+ # The given subcommand for the parent command is return by
378
+ # "givenSubcmd". Commonly the program creator should just check
379
+ # directly which subcommand has been selected and check for any
380
+ # subcommand options set. For example:
381
+ # if Opt['como_subcmd']['add'].given
382
+ # ...
162
383
  #
384
+ # === Program external options
163
385
  #
164
- # == Additional checks
386
+ # If the user gives the "--" option (double-dash), the arguments after
387
+ # that option is returned as an Array with "Opt.external".
165
388
  #
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
389
  #
390
+ #
391
+ # == Option combination checks
392
+ #
393
+ # Como provides a facility to create relations between options using
394
+ # RuleCheck DSL. This is needed since sometimes options have to be
395
+ # used in combination to make sense for the program. Also options
396
+ # might be mutually exclusive.
397
+ #
200
398
  # The following rules can be used (in combination):
201
399
  # [all] All options in the list.
202
400
  # [one] One and only one from the list.
203
401
  # [any] At least one of the list is given.
204
402
  # [none] No options are required.
403
+ # [inv] Logical negation for existence.
205
404
  # [incr] Incremental options in order i.e. have to have previous to
206
405
  # have later.
207
406
  # [follow] Incremental options in order i.e. have to have all later if
208
407
  # had first.
408
+ # [meh] Dont care, always succeeds.
409
+ #
410
+ # Examples can be found above.
411
+ #
412
+ #
413
+ # == Customization
414
+ #
415
+ # If the default behavior is not satisfactory, changes can be
416
+ # implemented simply by overloading the existing functions. Some
417
+ # knowledge of the internal workings of Como is required though.
209
418
 
210
419
  module Como
211
420
 
421
+
212
422
  # IO stream options for Como classes.
213
423
  class ComoCommon
214
424
 
@@ -230,206 +440,494 @@ module Como
230
440
  # User interface for Como.
231
441
  class Spec < ComoCommon
232
442
 
233
- # Command line options source.
234
- @@argv = ARGV
443
+ # Create specification for program with subcmds.
444
+ #
445
+ # @param author [String] Program author.
446
+ # @param year [String] Year (or dates) for program.
447
+ # @yield [] Subcmd definitions.
448
+ def Spec.program( author, year, config = nil, &defs )
449
+ if config
450
+ Opt.configOverlay( config )
451
+ end
452
+ spec = Spec.new( author, year )
453
+ spec.instance_eval( &defs )
454
+ Opt.main.check( ArgsParseState.new( @@argv ) )
455
+ end
235
456
 
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
457
 
245
- # Set command line options source, i.e. @@argv (default: ARGV).
246
- def Spec.setArgv( newArgv )
247
- @@argv = newArgv
458
+ # The primary entry point to Como. Defines the command
459
+ # switches and parses the command line. Performs "usage"
460
+ # display if "help" was selected.
461
+ #
462
+ # @param prog [String] Program (i.e. command) name.
463
+ # @param author [String] Author of the program.
464
+ # @param year [String] Year (or dates) for program.
465
+ # @param defs [Array<Array>] Option definitions.
466
+ # @param config [Hash] Option definition's behavioral config
467
+ # (changes @@config defaults).
468
+ def Spec.command( prog, author, year, defs, config = {} )
469
+ Spec.defineCheck( prog, author, year, defs, config )
470
+ Spec.usage if Opt['help'].given
248
471
  end
249
472
 
250
- # Display program usage (and optionally exit).
251
- def Spec.usage
252
- @@io.puts Spec.usageNormal
253
- exit( 1 ) if @@options[ :help_exit ]
473
+ # Alias to Spec.command.
474
+ def Spec.defineCheckHelp( prog, author, year, defs, config = {} )
475
+ Spec.command( prog, author, year, defs, config )
254
476
  end
255
-
256
477
 
257
- # Usage info for Opt:s.
258
- def Spec.usageNormal
259
- str = ""
478
+ # Same as "defineCheckHelp" except without automatic "help"
479
+ # option processing.
480
+ def Spec.defineCheck( prog, author, year, defs, config = {} )
481
+ spec = Spec.new( author, year )
482
+ spec.subcmd( prog, defs, config )
483
+ Opt.main.check( ArgsParseState.new( @@argv ) )
484
+ end
260
485
 
261
- if @@options[ :header ]
262
- str += @@options[ :header ]
263
- str += "\n"
264
- end
265
486
 
266
- str += "
487
+ # Create Spec object that can handle subcmd definitions.
488
+ #
489
+ # @param author [String] Program author.
490
+ # @param year [String] Year (or dates) for program.
491
+ def initialize( author, year )
492
+ @author = author
493
+ @year = year
267
494
 
268
- Usage:
269
- #{Opt.progname} #{Opt.cmdline.join(" ")}
495
+ Spec.ArgCheck( author.class == String, "Author name is not a String" )
496
+ Spec.ArgCheck( year.class == String, "Year is not a String" )
497
+ end
270
498
 
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
499
 
278
- Copyright (c) #{Opt.year} by #{Opt.author}
500
+ # Define subcommand options.
501
+ #
502
+ # @param cmd [String] Subcmd name.
503
+ # @param defs [Array<Array>] Option definition table.
504
+ # @param config [] Configuration options.
505
+ def subcmd( cmd, defs, config = {} )
279
506
 
280
- "
507
+ unless Opt.main
508
+
509
+ main = MainOpt.new( @author, @year,
510
+ cmd, nil, :subcmd, nil )
511
+ Opt.setMain( main )
512
+ subcmd = main
513
+
514
+ else
515
+
516
+ subcmd = Opt.findOpt( cmd )
517
+
518
+ Opt.setSubcmd( subcmd )
519
+
520
+ Spec.ArgCheck( false, "Subcommand \"#{cmd}\" not defined." ) unless subcmd
281
521
 
282
- if @@options[ :footer ]
283
- str += @@options[ :footer ]
284
- str += "\n"
285
522
  end
286
523
 
287
- str
288
- end
524
+ # Overlay user config on top of default.
525
+ subcmd.applyConfig( config )
289
526
 
527
+ if subcmd.config[ :autohelp ]
528
+ # Automatically add the help option.
529
+ defs.insert( 0, [ :silent, "help", "-h", "Display usage info." ] )
530
+ end
290
531
 
291
- # Set optional header for "usage".
292
- def Spec.setUsageHeader( str )
293
- @@options[ :header ] = str
294
- end
532
+ subcmd.setSubopt( *Spec.specify( defs ) )
533
+ subcmd
295
534
 
296
- # Set optional footer for "usage".
297
- def Spec.setUsageFooter( str )
298
- @@options[ :footer ] = str
299
535
  end
300
536
 
301
537
 
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
538
+ # Specify and check options spec.
539
+ #
540
+ # @param table [Array<Array>] Option definition table.
541
+ def Spec.specify( table )
314
542
 
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 ]
543
+ options = {}
544
+ subcmds = {}
545
+
546
+ # Type checks for valid user input.
547
+ Spec.ArgCheck( table.class == Array, "Option table is not an Array" )
548
+
549
+ table.each_index do |idx|
550
+
551
+ i = table[ idx ]
552
+
553
+ Spec.ArgCheck( i.class == Array, "Option table entry is not an Array" )
554
+
555
+ if i[0] == :default && i.length == 2
556
+
557
+ # Add 2 dummy entries for :default type if needed.
558
+ table[ idx ] = [ i[0], nil, nil, i[1] ]
559
+
560
+ elsif i[0] == :subcmd && i.length == 3
561
+
562
+ # Add 1 dummy entry for :subcmd type if needed.
563
+ table[ idx ] = [ i[0], i[1], nil, i[2] ]
564
+ end
565
+
566
+ Spec.ArgCheck( table[ idx ].length == 4, "Option table entry length not 4" )
567
+ end
568
+
569
+
570
+ table.each do |e|
571
+
572
+ if e[0] == :subcmd
573
+
574
+ subcmds[ e[1] ] = Opt.subcmd( e[1], e[3] )
575
+
576
+ else
577
+
578
+ option = nil
579
+
580
+ case e[0]
581
+
582
+ when :switch
583
+ option = Opt.switch( e[1], e[2], e[3] )
584
+
585
+ when :exclusive
586
+ option = Opt.exclusive( e[1], e[2], e[3] )
587
+
588
+ when :silent
589
+ option = Opt.exclusive( e[1], e[2], e[3], true )
590
+
591
+ when :single, :multi, :opt_single, :opt_multi, :opt_any
592
+ option = Opt.full( e[1], e[2], e[0], e[3] )
593
+
594
+ when :default
595
+ option = Opt.defaultOpt( e[3] )
596
+
597
+ else
598
+ raise "Unknown option type: \"#{e[0]}\"..."
599
+ end
600
+
601
+ options[ option.name ] = option
602
+
603
+ end
604
+
328
605
  end
606
+
607
+ [ options.values, subcmds.values ]
329
608
  end
330
609
 
331
610
 
332
- # Check only.
333
- def Spec.check
334
- Opt.parse( @@argv, @@options[ :check ] )
335
- Opt.checkMissing
611
+ # Command line options source.
612
+ @@argv = ARGV
613
+
614
+ # Set command line options source, i.e. @@argv (default: ARGV).
615
+ def Spec.setArgv( newArgv )
616
+ @@argv = newArgv
336
617
  end
337
618
 
619
+ # Display program usage (and optionally exit).
620
+ def Spec.usage
621
+ Opt.main.usage
622
+ end
338
623
 
339
- # Overlay "option" on top of options defaults (@@options).
340
- def Spec.applyOptionDefaults( option )
341
- option.replace( @@options.merge( option ) )
624
+ # Set optional header for "usage".
625
+ def Spec.setUsageHeader( str )
626
+ Opt.main.setUsageHeader( str )
342
627
  end
343
628
 
344
629
 
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 )
630
+ # Set optional footer for "usage".
631
+ def Spec.setUsageFooter( str )
632
+ Opt.main.setUsageFooter( str )
633
+ end
353
634
 
354
- # Display the possible combination:
355
- @@io.puts "\n Option combination rules:\n\n"
356
- Opt::RuleDisplay.new.evalAndDisplay( &rule )
357
635
 
358
- Spec.usage
636
+ # Check option combination rules.
637
+ #
638
+ # @param opt [String] Opt name to which rules are set. If not
639
+ # given, Opt.current is used.
640
+ # @param rule [Proc] Rules to check.
641
+ def Spec.checkRule( opt = nil, &rule )
642
+ if opt
643
+ opt = Opt[ opt ]
644
+ else
645
+ opt = Opt.current
359
646
  end
647
+ opt.setRuleCheck( &rule )
648
+ opt.checkRule
360
649
  end
361
650
 
362
651
 
652
+ # Check option combination rules.
653
+ #
654
+ # @param opt [String] Opt name to which rules are set. If not
655
+ # given, Opt.current is used.
656
+ # @param rule [Proc] Rules to check.
657
+ def checkRule( opt = nil, &rule )
658
+ if opt
659
+ opt = Opt[ opt ]
660
+ else
661
+ opt = Opt.current
662
+ end
663
+ opt.setRuleCheck( &rule )
664
+ end
665
+
363
666
  # Additional option check.
364
667
  # @param opt [String] Option name.
365
668
  # @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.
669
+ # @param check [Proc] Checker proc run for the option. Either
670
+ # @return false or generate an exception when errors found.
367
671
  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
672
+ Opt.main.checkAlso( opt, error, &check )
378
673
  end
379
674
 
675
+
676
+ private
677
+
678
+ def Spec.ArgCheck( cond, str )
679
+ raise( ArgumentError, str ) unless cond
680
+ end
681
+
682
+
380
683
  end
381
684
 
382
685
 
383
686
 
687
+
384
688
  # Opt includes all options spec information and parsed options
385
689
  # and their values. Option instance is accessed with
386
690
  # "Opt['name']". The option status (Opt instance) can be
387
691
  # queried with for example "given" and "value" methods.
388
-
389
692
  class Opt < ComoCommon
390
693
 
391
- class Error < StandardError; end
392
- class MissingArgument < Error; end
393
- class InvalidOption < Error; end
394
694
 
695
+ # Create exception with capability to pass arbitrary data
696
+ class ErrorWithData < StandardError
697
+ attr_reader :data
698
+ def initialize( message = nil, data = nil )
699
+ super( message )
700
+ @data = data
701
+ end
702
+ end
395
703
 
396
- # Option name.
397
- attr_accessor :name
398
704
 
399
- # Short option string.
400
- attr_accessor :opt
705
+ # Missing argument exception.
706
+ class MissingArgument < ErrorWithData; end
401
707
 
402
- # Long option string.
403
- attr_accessor :longOpt
708
+ # Invalid (non-existing) option exception.
709
+ class InvalidOption < ErrorWithData; end
404
710
 
405
- # Option type.
406
- attr_accessor :type
407
711
 
408
- # Option value.
409
- attr_accessor :value
410
712
 
411
- # Option documentation string.
412
- attr_accessor :doc
713
+ # ------------------------------------------------------------
714
+ # Option specification:
413
715
 
414
- # Is option specified?
415
- attr_writer :given
416
716
 
417
- # Is option hidden (usage).
418
- attr_accessor :silent
717
+ # Program i.e. highest level subcommand.
718
+ @@main = nil
419
719
 
420
- # Parsed option specs and option values.
720
+ # List of parsed option specs and option values.
421
721
  @@opts = []
422
722
 
423
- # Program external arguments (e.g. subprogram args)
424
- @@external = nil
723
+ # Current subcommand recorded.
724
+ @@subcmd = nil
725
+
726
+
727
+ # Set of default configs for printout.
728
+ @@config = {
729
+ :autohelp => true,
730
+ :rulehelp => false,
731
+ :header => nil,
732
+ :footer => nil,
733
+ :subcheck => true,
734
+ :check_missing => true,
735
+ :tab => 12,
736
+ :help_exit => true,
737
+ :error_exit => true,
738
+ }
425
739
 
426
740
 
427
- # Create Opt object:
428
- # [name] Option name string.
429
- # [opt] Switch string.
430
- # [type] Option type. One of:
431
- # * :switch
432
- # * :single
741
+ # Set main option.
742
+ def Opt.setMain( main )
743
+ @@main = main
744
+ Opt.setSubcmd( main )
745
+ end
746
+
747
+ # Get main option.
748
+ def Opt.main
749
+ @@main
750
+ end
751
+
752
+
753
+ # Add option to options list.
754
+ def Opt.addOpt( opt )
755
+ @@opts.push opt
756
+ end
757
+
758
+
759
+ # Set current subcmd.
760
+ def Opt.setSubcmd( opt )
761
+ @@subcmd = opt
762
+ end
763
+
764
+
765
+ # Current subcmd processed.
766
+ def Opt.current
767
+ @@subcmd
768
+ end
769
+
770
+
771
+ # Find option by name.
772
+ def Opt.findOpt( name )
773
+ idx = @@opts.index do |i| i.name == name end
774
+ if idx
775
+ @@opts[ idx ]
776
+ else
777
+ nil
778
+ end
779
+ end
780
+
781
+
782
+ # Reset "dynamic" class members.
783
+ def Opt.reset
784
+ @@opts = []
785
+ end
786
+
787
+
788
+ # Select option object by name. Main is searched first and
789
+ # then the flattened list of all options.
790
+ def Opt.[](str)
791
+
792
+ # Search Main first.
793
+ ret = Opt.main.argByName( str )
794
+
795
+ unless ret
796
+ ret = Opt.findOpt( str )
797
+ unless ret
798
+ raise RuntimeError, "Option \"#{str}\" does not exist..."
799
+ end
800
+ end
801
+
802
+ ret
803
+ end
804
+
805
+
806
+ # Return program name.
807
+ def Opt.progname
808
+ @@main.name
809
+ end
810
+
811
+
812
+ # Return program year.
813
+ def Opt.year
814
+ @@main.year
815
+ end
816
+
817
+
818
+ # Return author.
819
+ def Opt.author
820
+ @@main.author
821
+ end
822
+
823
+
824
+ # Return arguments (options) that are specified as command
825
+ # external (i.e. after '--').
826
+ def Opt.external
827
+ Opt.main.external
828
+ end
829
+
830
+
831
+ # Return arguments (options) that have no switch.
832
+ def Opt.default
833
+ Opt.main.default
834
+ end
835
+
836
+
837
+ # Create option spec.
838
+ def Opt.full( name, opt, type, doc = "No doc." )
839
+ new( name, opt, type, doc )
840
+ end
841
+
842
+ # Create sub-command option spec.
843
+ def Opt.subcmd( name, doc = "No doc." )
844
+ new( name, nil, :subcmd, doc, false )
845
+ end
846
+
847
+ # Create switch option spec.
848
+ def Opt.switch( name, opt, doc = "No doc." )
849
+ new( name, opt, :switch, doc, false )
850
+ end
851
+
852
+ # Create exclusive option spec.
853
+ def Opt.exclusive( name, opt, doc = "No doc.", silent = false )
854
+ o = new( name, opt, :exclusive, doc, false )
855
+ o.silent = silent
856
+ o
857
+ end
858
+
859
+ # Create default option spec, no switch.
860
+ def Opt.defaultOpt( doc = "No doc." )
861
+ new( "<default>", "<args>", :default, doc, [] )
862
+ end
863
+
864
+ # Options iterator for all options.
865
+ def Opt.each( &blk )
866
+ Opt.main.each &blk
867
+ end
868
+
869
+ # Options iterator for given options.
870
+ def Opt.each_given( &blk )
871
+ Opt.main.each_given( &blk )
872
+ end
873
+
874
+ # Overlay Opt default configuration options.
875
+ def Opt.configOverlay( config )
876
+ @@config.merge!( config )
877
+ end
878
+
879
+
880
+ # ------------------------------------------------------------
881
+ # Opt properties:
882
+
883
+
884
+ # Subcommand parent (i.e. host).
885
+ attr_accessor :parent
886
+
887
+
888
+ # Option name.
889
+ attr_accessor :name
890
+
891
+ # Short option string.
892
+ attr_accessor :opt
893
+
894
+ # Long option string.
895
+ attr_accessor :longOpt
896
+
897
+ # Option type.
898
+ attr_accessor :type
899
+
900
+ # Option value.
901
+ attr_accessor :value
902
+
903
+ # Option documentation string.
904
+ attr_accessor :doc
905
+
906
+ # Is option specified?
907
+ attr_writer :given
908
+
909
+ # Is option hidden (usage).
910
+ attr_accessor :silent
911
+
912
+ # List of suboptions.
913
+ attr_reader :subopt
914
+
915
+ # List of subcommands.
916
+ attr_reader :subcmd
917
+
918
+ # Opt configuration.
919
+ attr_reader :config
920
+
921
+ # Opt rules.
922
+ attr_reader :rules
923
+
924
+
925
+ # Create Opt object:
926
+ # [name] Option name string.
927
+ # [opt] Switch string.
928
+ # [type] Option type. One of:
929
+ # * :switch
930
+ # * :single
433
931
  # * :multi
434
932
  # * :opt_single
435
933
  # * :opt_multi
@@ -441,6 +939,7 @@ module Como
441
939
  # [value] Default value.
442
940
 
443
941
  def initialize( name, opt, type, doc, value = nil )
942
+ @parent = nil
444
943
  @name = name
445
944
  @opt = opt
446
945
  @longOpt = "--#{name}"
@@ -450,36 +949,370 @@ module Como
450
949
  @silent = false
451
950
  # Whether option was set or not.
452
951
  @given = false
453
- @@opts.push self
952
+ @subopt = nil
953
+ @subcmd = nil
954
+ @rules = nil
955
+
956
+ @config = @@config.dup
957
+
958
+ Opt.addOpt( self )
959
+ end
960
+
961
+
962
+ # Set subcommand suboptions.
963
+ #
964
+ # @param opts [Array<Opt>]
965
+ def setSubopt( opts, subs )
966
+ opts.each do |i|
967
+ i.parent = self
968
+ end
969
+
970
+ subs.each do |i|
971
+ i.parent = self
972
+ end
973
+
974
+ @subopt = opts
975
+ @subcmd = subs
976
+ end
977
+
978
+
979
+ # Merge config to base config.
980
+ #
981
+ # @param config [Hash] Configuration Hash to merge.
982
+ def applyConfig( config )
983
+ @config.merge!( config )
984
+ end
985
+
986
+
987
+ # Set rule checks for the option.
988
+ #
989
+ # @param rule [Proc] Rule to check after command line parsing.
990
+ def setRuleCheck( &rule )
991
+ @rules = rule
992
+ end
993
+
994
+
995
+ # ------------------------------------------------------------
996
+ # Command line parsing and checking:
997
+
998
+ # Check provided args.
999
+ def check( argsState )
1000
+
1001
+ # Start at top.
1002
+ top = self
1003
+
1004
+ begin
1005
+
1006
+ # Parse and check for invalid arguments.
1007
+ begin
1008
+ top = top.parse( argsState, top.config[ :check_missing ] )
1009
+ end while( top )
1010
+
1011
+ # Check for any missing valid arguments.
1012
+ checkMissing
1013
+
1014
+ rescue Opt::MissingArgument, Opt::InvalidOption => err
1015
+
1016
+ @@io.puts
1017
+
1018
+ error( err.to_s )
1019
+
1020
+ # Display subcmd specific usage info.
1021
+ err.data.usage
1022
+
1023
+ exit( 1 ) if Opt.main.config[ :error_exit ]
1024
+
1025
+ end
1026
+
1027
+ # Revert back to top after hierarchy travelsal.
1028
+ usageIfHelp
1029
+
1030
+ # Check rules.
1031
+ cur = self
1032
+ while cur
1033
+ cur.checkRule
1034
+ cur = cur.givenSubcmd
1035
+ end
1036
+
1037
+ self
1038
+ end
1039
+
1040
+
1041
+
1042
+ # Parse cmdline options from args.
1043
+ def parse( args, checkInvalids = true )
1044
+
1045
+ while args.get
1046
+
1047
+ #puts "Opt.parse (#{@name}): #{args.get}"
1048
+
1049
+ if args.isOptTerm
1050
+
1051
+ # Rest of the args do not belong to this program.
1052
+ args.next
1053
+ Opt.main.external = args.rest
1054
+ break
1055
+
1056
+ elsif args.isOpt
1057
+
1058
+ o = findOpt( args.get )
1059
+
1060
+ if !o
1061
+ if checkInvalids
1062
+ raise \
1063
+ InvalidOption.new( "Unknown option \"#{args.get}\"...",
1064
+ self )
1065
+ else
1066
+ o = findOpt( nil )
1067
+ if !o
1068
+ raise \
1069
+ InvalidOption.new(
1070
+ "No default option specified for \"#{args.get}\"...",
1071
+ self )
1072
+ else
1073
+ # Default option.
1074
+ o.value.push args.toValue
1075
+ args.next
1076
+ end
1077
+ end
1078
+
1079
+ elsif o && o.hasArg
1080
+
1081
+ args.next
1082
+
1083
+ if ( !args.get || args.isOpt ) &&
1084
+ o.type != :opt_any
1085
+
1086
+ raise MissingArgument.new(
1087
+ "No argument given for \"#{o.opt}\"...",
1088
+ self )
1089
+
1090
+ else
1091
+
1092
+ if o.hasMany
1093
+
1094
+ # Get all argument for multi-option.
1095
+ o.value = [] if !o.given
1096
+ while args.get && !args.isOpt
1097
+ o.value.push args.toValue
1098
+ args.next
1099
+ end
1100
+
1101
+ else
1102
+
1103
+ # Get one argument for single-option.
1104
+
1105
+ if o.given
1106
+ raise \
1107
+ InvalidOption.new(
1108
+ "Too many arguments for option (\"#{o.name}\")...",
1109
+ self )
1110
+ else
1111
+ o.value = args.toValue
1112
+ end
1113
+ args.next
1114
+ end
1115
+ end
1116
+
1117
+ o.given = true
1118
+
1119
+ else
1120
+
1121
+ if !o
1122
+ raise InvalidOption.new( "No valid options specified...",
1123
+ self )
1124
+ else
1125
+ o.value = !o.value if !o.given
1126
+ o.given = true
1127
+ args.next
1128
+ end
1129
+ end
1130
+
1131
+ else
1132
+
1133
+ # Subcmd or default. Check for Subcmd first.
1134
+
1135
+ # Search for Subcmd.
1136
+ o = findOpt( args.get )
1137
+
1138
+ if !o
1139
+
1140
+ # Search for default option.
1141
+ o = findOpt( nil )
1142
+ if !o
1143
+ raise \
1144
+ InvalidOption.new(
1145
+ "No default option specified for \"#{args.get}\"...",
1146
+ self )
1147
+ else
1148
+ # Default option.
1149
+ o.given = true
1150
+ o.value.push args.toValue
1151
+ args.next
1152
+ end
1153
+
1154
+ else
1155
+
1156
+ o.given = true
1157
+ args.next
1158
+ return o
1159
+
1160
+ end
1161
+
1162
+ end
1163
+ end
1164
+
1165
+ nil
1166
+ end
1167
+
1168
+
1169
+ # Check for any non-given required arguments recursively
1170
+ # through hierarchy of subcommands. MissingArgument Exception
1171
+ # is generated if argument is missing.
1172
+ def checkMissing
1173
+
1174
+ # Full cmd name.
1175
+ cmd = ( getParents.map do |i| i.name end ).join( ' ' )
1176
+
1177
+ # Check for any exclusive args first.
1178
+ @subopt.each do |o|
1179
+ if o.type == :exclusive && o.given
1180
+ return
1181
+ end
1182
+ end
1183
+
1184
+
1185
+ # Check for required arguments for this level before
1186
+ # subcmds.
1187
+ @subopt.each do |o|
1188
+ if o.isRequired
1189
+ unless o.given
1190
+ raise MissingArgument.new(
1191
+ "Option \"#{o.opt}\" missing for \"#{cmd}\"...",
1192
+ self )
1193
+ end
1194
+ end
1195
+ end
1196
+
1197
+ if hasSubcmd
1198
+ if @config[ :subcheck ]
1199
+ # Compulsory Subcommand checking enabled.
1200
+ subcmdMissing = true
1201
+ else
1202
+ subcmdMissing = false
1203
+ end
1204
+ else
1205
+ subcmdMissing = false
1206
+ end
1207
+
1208
+ # Check for any subcmd args.
1209
+ sub = givenSubcmd
1210
+ if sub
1211
+ subcmdMissing = false
1212
+ # Use recursion to examine the next level.
1213
+ return sub.checkMissing
1214
+ end
1215
+
1216
+ # If no subcmds are given, issue error.
1217
+ raise MissingArgument.new(
1218
+ "Subcommand required for \"#{cmd}\"...",
1219
+ self ) if subcmdMissing
1220
+
1221
+ end
1222
+
1223
+
1224
+
1225
+ # Check option combination rules.
1226
+ def checkRule
1227
+
1228
+ return unless @rules
1229
+
1230
+ begin
1231
+ raise Opt::InvalidOption.new( "Option combination mismatch!", self ) unless
1232
+ RuleCheck.check( self, &@rules )
1233
+
1234
+ rescue Opt::MissingArgument, Opt::InvalidOption => err
1235
+ @@io.puts
1236
+ error( err.to_s )
1237
+
1238
+ usage( nil, true )
1239
+
1240
+ end
1241
+ end
1242
+
1243
+
1244
+ # Additional option check.
1245
+ # @param opt [String] Option name.
1246
+ # @param error [String] Error string for false return values (from check).
1247
+ # @param check [Proc] Checker proc run for the option. Either
1248
+ # @return false or generate an exception when errors found.
1249
+ def checkAlso( opt, error, &check )
1250
+ begin
1251
+ if self[opt].evalCheck( &check ) != true
1252
+ raise Opt::InvalidOption.new( error, self )
1253
+ end
1254
+ rescue Opt::MissingArgument, Opt::InvalidOption => err
1255
+ @@io.puts
1256
+ error( err.to_s )
1257
+ err.data.usage
1258
+ exit( 1 )
1259
+ end
1260
+ end
1261
+
1262
+
1263
+
1264
+
1265
+ # ------------------------------------------------------------
1266
+ # Opt query user interface:
1267
+
1268
+ # Select option object by name operator.
1269
+ def []( str )
1270
+ ret = argByName( str )
1271
+ unless ret
1272
+ raise RuntimeError, "Subopt \"#{str}\" does not exist for \"#{@name}\"!"
1273
+ end
1274
+ ret
1275
+ end
1276
+
1277
+
1278
+ # All subcommand options, options and subcommands.
1279
+ def suball
1280
+ @subopt + @subcmd
454
1281
  end
455
1282
 
456
1283
 
457
1284
  # Options list iterator.
458
- def Opt.each
459
- @@opts.each do |o|
1285
+ def each( &blk )
1286
+ suball.each do |o|
460
1287
  yield o
461
1288
  end
462
1289
  end
463
1290
 
464
1291
 
465
1292
  # Options iterator for given options.
466
- def Opt.each_given
467
- @@opts.each do |o|
1293
+ def each_given( &blk )
1294
+ suball.each do |o|
468
1295
  yield o if o.given
469
1296
  end
470
1297
  end
471
1298
 
472
1299
 
473
1300
  # Number of given options.
474
- def Opt.givenCount
1301
+ def givenCount
475
1302
  cnt = 0
476
- Opt.each_given do |i|
1303
+ each_given do |i|
477
1304
  cnt += 1
478
1305
  end
479
1306
  cnt
480
1307
  end
481
1308
 
482
1309
 
1310
+ # Short syntax for value reference. Example: "~Opt['file']".
1311
+ def ~()
1312
+ @value
1313
+ end
1314
+
1315
+
483
1316
  # Return option value if given otherwise the default.
484
1317
  # Example usage: fileName = Opt["file"].apply( "no_name.txt" )
485
1318
  def apply( default = nil )
@@ -494,10 +1327,17 @@ module Como
494
1327
  # Returns true if option is given, and block is not
495
1328
  # present. When block is present, the block is executed (with
496
1329
  # value as parameter) if option has been given.
497
- def given( &prog )
1330
+ #
1331
+ # @param yieldOpt [Boolean] Pass Opt to block instead of its
1332
+ # value.
1333
+ def given( yieldOpt = false, &prog )
498
1334
  if block_given?
499
1335
  if @given
500
- yield( @value )
1336
+ if yieldOpt
1337
+ yield( self )
1338
+ else
1339
+ yield( @value )
1340
+ end
501
1341
  else
502
1342
  false
503
1343
  end
@@ -507,346 +1347,488 @@ module Como
507
1347
  end
508
1348
 
509
1349
 
510
- # Check for any non-given required arguments.
511
- def Opt.checkMissing
1350
+ # Alias for given.
1351
+ alias given? given
512
1352
 
513
- # Check for any exclusive args first
514
- @@opts.each do |o|
515
- if o.type == :exclusive && o.given
516
- return
1353
+
1354
+ # Return the selected subcommand.
1355
+ def givenSubcmd
1356
+ ( @subcmd.select do |o| o.given end )[0]
1357
+ end
1358
+
1359
+
1360
+ # Returns Hash of option value parameters. Example command
1361
+ # line content:
1362
+ # -p rounds=10 length=5
1363
+ # Option value content in this case would be (Array of param
1364
+ # settings):
1365
+ # [ "rounds=10", "length=5" ]
1366
+ # @return [Hash] Parameter settings included in the option.
1367
+ def params
1368
+ map = {}
1369
+ @value.each do |i|
1370
+ name, value = i.split('=')
1371
+ value = str_to_num( value )
1372
+ map[ name ] = value
1373
+ end
1374
+ map
1375
+ end
1376
+
1377
+
1378
+ # Return default options.
1379
+ def default
1380
+ argByName( nil )
1381
+ end
1382
+
1383
+
1384
+ # Select option object by name.
1385
+ def argByName( str )
1386
+ if str == nil || str == :default
1387
+ @subopt.each do |o|
1388
+ if o.type == :default
1389
+ return o
1390
+ end
517
1391
  end
1392
+ nil
1393
+ else
1394
+ suball.each do |o|
1395
+ if str == o.name
1396
+ return o
1397
+ end
1398
+ end
1399
+ nil
518
1400
  end
1401
+ end
519
1402
 
520
- @@opts.each do |o|
521
- if o.isRequired
522
- raise MissingArgument, "Option \"#{o.opt}\" missing..." if !o.given
1403
+
1404
+ # Select option object by name/opt/longOpt.
1405
+ def argById( str )
1406
+ if str == nil || str == :default
1407
+ @subopt.each do |o|
1408
+ if o.type == :default
1409
+ return o
1410
+ end
1411
+ end
1412
+ nil
1413
+ else
1414
+ suball.each do |o|
1415
+ if str == o.name || str == o.opt || str == o.longOpt
1416
+ return o
1417
+ end
523
1418
  end
1419
+ nil
524
1420
  end
525
1421
  end
526
1422
 
527
1423
 
528
- # Reset "dynamic" class members.
529
- def Opt.reset
530
- @@opts = []
531
- @@external = nil
1424
+ # Option requires argument?
1425
+ def hasArg
1426
+ case @type
1427
+ when :single, :multi, :opt_single, :opt_multi, :opt_any; true
1428
+ else false
1429
+ end
532
1430
  end
533
1431
 
534
1432
 
535
- # Select option object by name operator.
536
- def Opt.[](str)
537
- Opt.arg(str)
1433
+ # Option requires many arguments?
1434
+ def hasMany
1435
+ case @type
1436
+ when :multi, :opt_multi, :opt_any; true
1437
+ else false
1438
+ end
1439
+ end
1440
+
1441
+
1442
+ # Is mandatory argument?
1443
+ def isRequired
1444
+ case @type
1445
+ when :single, :multi; true
1446
+ else false
1447
+ end
1448
+ end
1449
+
1450
+
1451
+ # Test if option is silent.
1452
+ def silent?
1453
+ @silent
1454
+ end
1455
+
1456
+
1457
+ # Test if option is of switch type.
1458
+ def isSwitch
1459
+ case @type
1460
+ when :switch, :exclusive, :default; true
1461
+ else false
1462
+ end
1463
+ end
1464
+
1465
+
1466
+ # ------------------------------------------------------------
1467
+ # Opt "Usage Help" user interface:
1468
+
1469
+ # Set optional header for "usage".
1470
+ def setUsageHeader( str )
1471
+ @config[ :header ] = str
1472
+ end
1473
+
1474
+
1475
+ # Set optional footer for "usage".
1476
+ def setUsageFooter( str )
1477
+ @config[ :footer ] = str
1478
+ end
1479
+
1480
+
1481
+ # Display program usage (and optionally exit).
1482
+ #
1483
+ # @param doExit [Boolean] Exit program after help
1484
+ # display. Default to help_exit config
1485
+ # if nil.
1486
+ # @param ruleHelp [Boolean] Include rule help to help
1487
+ # display. Default to rulehelp config
1488
+ # if nil.
1489
+ def usage( doExit = nil, ruleHelp = nil )
1490
+
1491
+ doExit = @config[ :help_exit ] if doExit == nil
1492
+ ruleHelp = @config[ :rulehelp ] if ruleHelp == nil
1493
+
1494
+ @@io.puts usageNormal
1495
+
1496
+ if ruleHelp
1497
+ @@io.puts "\n Option Combinations:"
1498
+ @@io.puts RuleDisplay.print( &@rules )
1499
+ end
1500
+
1501
+ exit( 1 ) if doExit
1502
+ end
1503
+
1504
+
1505
+ # Display program usage (and optionally exit).
1506
+ def usageIfHelp
1507
+ if self['help'].given
1508
+ usage
1509
+ elsif hasSubcmd && givenSubcmd
1510
+ givenSubcmd.usageIfHelp
1511
+ end
1512
+ end
1513
+
1514
+
1515
+ # Usage printout for command.
1516
+ def usageCommand
1517
+ str = ""
1518
+ str += "
1519
+ Subcommand \"#{@name}\" usage:
1520
+ #{fullCommand} #{cmdline.join(" ")}
1521
+
1522
+ "
1523
+ str += suboptDoc
1524
+
1525
+ str += "\n"
1526
+ end
1527
+
1528
+ # Usage info for Opt:s.
1529
+ def usageNormal
1530
+ str = ""
1531
+
1532
+ if @config[ :header ]
1533
+ str += "\n"
1534
+ str += @config[ :header ]
1535
+ str += "\n"
1536
+ end
1537
+
1538
+ str += usageCommand
1539
+
1540
+ if @config[ :footer ]
1541
+ str += @config[ :footer ]
1542
+ str += "\n"
1543
+ end
1544
+
1545
+ str
1546
+ end
1547
+
1548
+
1549
+ # Return cmdline usage strings for options in an Array.
1550
+ def cmdline
1551
+ opts = []
1552
+
1553
+ @subopt.each do |o|
1554
+
1555
+ next if o.silent?
1556
+
1557
+ prural = nil
1558
+ case o.type
1559
+ when :multi, :opt_multi; prural = "+"
1560
+ when :opt_any; prural = "*"
1561
+ else prural = ""
1562
+ end
1563
+
1564
+
1565
+ if !( o.isSwitch )
1566
+ name = " <#{o.name}>#{prural}"
1567
+ else
1568
+ name = ""
1569
+ end
1570
+
1571
+ if o.opt == nil
1572
+ opt = o.longOpt
1573
+ else
1574
+ opt = o.opt
1575
+ end
1576
+
1577
+ if o.isRequired
1578
+ opts.push "#{opt}#{name}"
1579
+ else
1580
+ opts.push "[#{opt}#{name}]"
1581
+ end
1582
+ end
1583
+
1584
+
1585
+ if hasSubcmd
1586
+ opts.push "<<subcommand>>"
1587
+ end
1588
+
1589
+ opts
1590
+
1591
+ end
1592
+
1593
+
1594
+ # Return document strings for options.
1595
+ def suboptDoc
1596
+
1597
+ str = ""
1598
+ # format = Proc.new do |s,d| ( " %-#{@config[ :tab ]}s%s\n" % [ s, d ] ) end
1599
+
1600
+ str += suboptDocFormat( "Options:", "" ) if hasSubcmd && hasVisibleOptions
1601
+
1602
+ @subopt.each do |o|
1603
+ next if o.silent?
1604
+ str += suboptDocFormat( o.opt ? o.opt : o.longOpt, o.doc )
1605
+ end
1606
+
1607
+ str += "\n" + suboptDocFormat( "Subcommands:", "" ) if hasSubcmd
1608
+
1609
+ @subcmd.each do |o|
1610
+ str += suboptDocFormat( o.name, o.doc )
1611
+ end
1612
+
1613
+ str
538
1614
  end
539
1615
 
540
1616
 
541
- # Select option object by name.
542
- def Opt.arg( str )
1617
+ # Find option object by option str.
1618
+ def findOpt( str )
543
1619
  if str == nil
544
- @@opts.each do |o|
545
- if o.type == :default
546
- return o
547
- end
548
- end
549
- nil
1620
+ suball.detect { |i| i.type == :default }
1621
+ elsif str[0..1] == "--"
1622
+ suball.detect { |i| i.longOpt == str }
1623
+ elsif str[0..0] != "-"
1624
+ suball.detect { |i| i.name == str }
550
1625
  else
551
- @@opts.each do |o|
552
- if str == o.name
553
- return o
554
- end
555
- end
556
- nil
1626
+ suball.detect { |i| i.opt == str }
557
1627
  end
558
1628
  end
1629
+
559
1630
 
560
-
561
- # Return program name.
562
- def Opt.progname
563
- @@progname
564
- end
565
-
566
- # Return program year.
567
- def Opt.year
568
- @@year
569
- end
570
-
571
- # Return author.
572
- def Opt.author
573
- @@author
1631
+ # Como error printout.
1632
+ def error( str )
1633
+ @@io.puts "#{Opt.progname} error: #{str}"
574
1634
  end
575
1635
 
1636
+
576
1637
 
577
- # Return options that are specified as command external
578
- # (i.e. after '--').
579
- def Opt.external
580
- @@external
581
- end
582
1638
 
583
- # Return document strings for options.
584
- def Opt.doc
585
- doc = []
586
- @@opts.each do |o|
587
- next if o.silent?
588
- doc.push( [o.opt ? o.opt : o.longOpt, o.doc] )
589
- end
590
- doc
591
- end
1639
+ # ------------------------------------------------------------
1640
+ # Internal methods:
592
1641
 
1642
+ private
593
1643
 
594
1644
 
595
- # Set of methods which represent option combination checking.
596
- # In effect this is a meta language (DSL) for option
597
- # combinations.
598
- #
599
- # Example:
600
- # RuleCheck.checkRule do
601
- # one(
602
- # incr( "gcov", "exclude", "refreshed" ),
603
- # "manifest",
604
- # "pairs",
605
- # "files"
606
- # )
607
- # end
608
- class RuleCheck
609
-
610
- # Get given count.
611
- def getScore( *args )
612
- score = 0
613
- args.each do |i|
614
- if i.class == TrueClass || i.class == FalseClass
615
- score += 1 if i
616
- else
617
- score += 1 if Opt[i].given
618
- end
619
- end
620
- score
621
- end
1645
+ # Convert string to number if possible. The order of checks:
1646
+ # integer, float, and no-conversion.
1647
+ def str_to_num( str )
1648
+ begin return Integer( str ); rescue ; end
1649
+ begin return Float( str ); rescue ; end
1650
+ str
1651
+ end
622
1652
 
623
- # Special condition when no options are given.
624
- def none
625
- Opt.givenCount == 0
626
- end
627
1653
 
628
- # Incremental options in order i.e. have to have previous
629
- # to have later.
630
- def incr( *args )
631
- had = 0
632
- add = true
633
- scoreAll = 0
634
- i = 0
635
- while args[i]
636
- score = getScore( args[i] )
637
- had += 1 if ( score == 1 ) && add
638
- add = false if ( score == 0 )
639
- scoreAll += score
640
- i += 1
641
- end
1654
+ # Return list of parents.
1655
+ def getParents
1656
+ list = []
1657
+ cur = self
1658
+ begin
1659
+ list.push cur
1660
+ cur = cur.parent
1661
+ end while cur
642
1662
 
643
- ( had == scoreAll && had > 0 )
644
- end
1663
+ list.reverse
1664
+ end
645
1665
 
646
- # Incremental options in order i.e. have to have all later
647
- # if had first.
648
- def follow( *args )
649
- if getScore( args[0] )
650
- getScore( *args ) == args.length
651
- else
652
- true
653
- end
654
- end
655
1666
 
656
- # One of list given.
657
- def one( *args )
658
- getScore( *args ) == 1
659
- end
1667
+ # Full command name.
1668
+ def fullCommand
1669
+ ( getParents.map do |i| i.name end ).join( ' ' )
1670
+ end
660
1671
 
661
- # At least one is given.
662
- def any( *args )
663
- getScore( *args ) > 0
664
- end
665
1672
 
666
- # All are given.
667
- def all( *args )
668
- getScore( *args ) == args.length
669
- end
1673
+ # Test if option is of subcmd type.
1674
+ def isSubcmd
1675
+ @type == :subcmd
670
1676
  end
671
1677
 
672
1678
 
1679
+ # Test if option is of subcmd type.
1680
+ def hasVisibleOptions
1681
+ # Count non-silent options.
1682
+ ( @subopt.select do |i| !i.silent? end ).length > 0
1683
+ end
673
1684
 
674
- # Display utility for RuleCheck. Same usage model.
675
- #
676
- # Example expansion of options:
677
- #
678
- # |--# One of:
679
- # | |--# Adding in order:
680
- # | | |--<gcov>
681
- # | | |--<exclude>
682
- # | | |--<refreshed>
683
- # | |--<manifest>
684
- # | |--<pairs>
685
- # | |--<files>
686
- #
687
- class RuleDisplay < ComoCommon
688
1685
 
689
- def initialize
690
- # Prefix string for lines. Rules add/rm from it.
691
- @prefixStr = " "
692
- self
693
- end
1686
+ # Test if option is of subcmd type.
1687
+ def hasSubcmd
1688
+ !@subcmd.empty?
1689
+ end
694
1690
 
695
- # Eval rules to get an nested array and then display it.
696
- def evalAndDisplay( &rule )
697
- printRule( instance_eval( &rule ) )
698
- end
699
1691
 
700
- # Increase prefix string.
701
- def addPrefix( str )
702
- @prefixStr += str
703
- end
1692
+ # Custom check for the option. User has to know some Como
1693
+ # internals.
1694
+ def evalCheck( &check )
1695
+ instance_eval &check
1696
+ end
704
1697
 
705
- # Remove from prefix (either str or length ).
706
- def rmPrefix( item )
707
- if item.class == String
708
- cnt = item.length
709
- else
710
- cnt = item
711
- end
712
- @prefixStr = @prefixStr[0..-(cnt+1)]
713
- end
1698
+ # Format option documentation. If newline is followed by tab,
1699
+ # rest of the documentation is aligned with the description of
1700
+ # previous line.
1701
+ def suboptDocFormat( switch, doc )
714
1702
 
715
- # Print prefix + str.
716
- def p( str )
717
- @@io.puts( @prefixStr + str )
718
- end
1703
+ # Format doc nicely to multiple lines if newline is
1704
+ # followed by tab stop.
719
1705
 
720
- # Recursively go through the nested array of rule items and
721
- # print out rules.
722
- def printRule( arr )
723
- p( "|--# #{arr[0]}:" )
724
- item = "| "
725
- addPrefix( item )
1706
+ parts = doc.split( "\n" )
1707
+ lines = [ ( " %-#{@config[ :tab ]}s%s\n" % [ switch, parts[0] ] ) ]
1708
+
1709
+ if parts[1]
1710
+ parts[1..-1].each do |p|
726
1711
 
727
- arr[1..-1].each do |i|
728
- if i.class == Array
729
- printRule( i )
1712
+ if p[0] == "\t"
1713
+ lines.push ( " #{' '*@config[ :tab ]}%s\n" % [ p[1..-1] ] )
730
1714
  else
731
- p( "|--<#{i}>" )
1715
+ lines.push p
732
1716
  end
1717
+
733
1718
  end
734
- rmPrefix( item )
735
1719
  end
736
1720
 
737
- # Special condition where no arguments are given.
738
- def none
739
- [ "NONE" ]
740
- end
1721
+ lines.join
1722
+ end
741
1723
 
742
- # Incremental options in order i.e. have to have previous
743
- # to have later.
744
- def incr( *args )
745
- [ "Adding in order", *args ]
746
- end
1724
+ end
747
1725
 
748
- # Incremental options in order i.e. have to have all later
749
- # if had first.
750
- def follow( *args )
751
- [ "If first then rest", *args ]
752
- end
753
1726
 
754
- # One of list given.
755
- def one( *args )
756
- [ "One of", *args ]
757
- end
758
1727
 
759
- # At least one is given.
760
- def any( *args )
761
- [ "One or more of", *args ]
762
- end
1728
+ # Specialized Opt class for program (i.e. highest level
1729
+ # subcommand).
1730
+ class MainOpt < Opt
763
1731
 
764
- # All are given.
765
- def all( *args )
766
- [ "All of", *args ]
767
- end
1732
+ # Program external arguments:
1733
+ attr_accessor :external
1734
+
1735
+ # Program author and year (date).
1736
+ attr_reader :author, :year
1737
+
1738
+
1739
+ def initialize( author, year,
1740
+ name, opt, type, doc, value = nil )
1741
+ @author = author
1742
+ @year = year
1743
+ @external = nil
1744
+ super( name, opt, type, doc, value = nil )
768
1745
 
769
1746
  end
1747
+
770
1748
 
1749
+ # Full command name.
1750
+ def fullCommand
1751
+ Opt.progname
1752
+ end
771
1753
 
772
1754
 
773
- # Check against option combination rules.
774
- def Opt.checkRule( &rule )
775
- RuleCheck.new.instance_eval( &rule )
776
- end
1755
+ # Usage printout for command.
1756
+ def usageCommand
1757
+ str = "
1758
+ Usage:
1759
+ #{fullCommand} #{cmdline.join(" ")}
777
1760
 
1761
+ "
1762
+ str += suboptDoc
778
1763
 
779
- # Create option spec.
780
- def Opt.full( name, opt, type, doc = "No doc." )
781
- new( name, opt, type, doc )
782
- end
1764
+ str += "
783
1765
 
784
- # Create switch option spec.
785
- def Opt.switch( name, opt, doc = "No doc." )
786
- new( name, opt, :switch, doc, false )
787
- end
1766
+ Copyright (c) #{Opt.year} by #{Opt.author}
788
1767
 
789
- # Create exclusive option spec.
790
- def Opt.exclusive( name, opt, doc = "No doc.", silent = false )
791
- new( name, opt, :exclusive, doc, false )
792
- @@opts[-1].silent = silent
1768
+ "
793
1769
  end
794
1770
 
795
- # Create default option spec, no switch.
796
- def Opt.default( doc = "No doc." )
797
- new( nil, "<arg>", :default, doc, [] )
1771
+ end
1772
+
1773
+
1774
+ # Command argument parsing state.
1775
+ class ArgsParseState
1776
+
1777
+ # @param list [Array<String>] List of Command Line Arguments
1778
+ # (default: ARGV).
1779
+ def initialize( list )
1780
+ set( list )
1781
+ @idx = 0
798
1782
  end
799
1783
 
1784
+ # Set list of arguments.
1785
+ def set( list )
1786
+ @args = list
1787
+ end
800
1788
 
801
- # Specify and check options spec.
802
- def Opt.specify( progname, author, year, table )
1789
+ # Step to next argument.
1790
+ def next
1791
+ @idx += 1
1792
+ end
803
1793
 
804
- # Type checks for valid user input.
805
- check = Proc.new do |cond,str| raise( ArgumentError, str ) unless cond end
806
- check.call( progname.class == String, "Program name is not a String" )
807
- check.call( author.class == String, "Author name is not a String" )
808
- check.call( year.class == String, "Year is not a String" )
809
- check.call( table.class == Array, "Option table is not an Array" )
810
- table.each do |i|
811
- check.call( i.class == Array, "Option table entry is not an Array" )
812
- check.call( i.length == 4, "Option table entry length not 4" )
813
- end
1794
+ # Step to previous argument.
1795
+ def prev
1796
+ @idx -= 1
1797
+ end
814
1798
 
1799
+ # Get current argument.
1800
+ def get( idx = @idx )
1801
+ @args[ idx ]
1802
+ end
815
1803
 
816
- @@progname = progname
817
- @@year = year
818
- @@author = author
1804
+ # Get last argument.
1805
+ def last( idx = @idx )
1806
+ idx == ( @args.length-1 )
1807
+ end
819
1808
 
820
- table.each do |e|
1809
+ # Get rest of the arguments.
1810
+ def rest( idx = @idx )
1811
+ @args[ idx..-1 ]
1812
+ end
821
1813
 
822
- case e[0]
823
- when :switch
824
- Opt.switch( e[1], e[2], e[3] )
825
- when :exclusive
826
- Opt.exclusive( e[1], e[2], e[3] )
827
- when :silent
828
- Opt.exclusive( e[1], e[2], e[3], true )
829
- when :single, :multi, :opt_single, :opt_multi, :opt_any
830
- Opt.full( e[1], e[2], e[0], e[3] )
831
- when :default
832
- Opt.default( e[3] )
833
- end
834
- end
1814
+ # Parser at argument list end?
1815
+ def done?
1816
+ @idx >= @list.length
835
1817
  end
836
1818
 
837
1819
 
838
1820
  # Test whether str is an option.
839
- def Opt.isOpt( str )
1821
+ def isOpt( str = get )
840
1822
  str[0..0] == "-"
841
1823
  end
842
1824
 
843
1825
  # Test whether str is an option list terminator.
844
- def Opt.isOptTerm( str )
1826
+ def isOptTerm( str = get )
845
1827
  str == "--"
846
1828
  end
847
1829
 
848
1830
  # Format value string if escaped.
849
- def Opt.toValue( str )
1831
+ def toValue( str = get )
850
1832
  if str[0..0] == "\\"
851
1833
  str[1..-1]
852
1834
  else
@@ -854,204 +1836,221 @@ module Como
854
1836
  end
855
1837
  end
856
1838
 
857
- # Option requires argument?
858
- def hasArg
859
- case @type
860
- when :single, :multi, :opt_single, :opt_multi, :opt_any; true
861
- else false
862
- end
863
- end
1839
+ end
864
1840
 
865
- # Option requires many arguments?
866
- def hasMany
867
- case @type
868
- when :multi, :opt_multi, :opt_any; true
869
- else false
870
- end
1841
+
1842
+
1843
+ # Set of methods which represent option combination checking.
1844
+ # In effect this is a meta language (DSL) for option
1845
+ # combinations.
1846
+ #
1847
+ # Example:
1848
+ # RuleCheck.check( opt ) do
1849
+ # one(
1850
+ # incr( "gcov", "exclude", "refreshed" ),
1851
+ # "manifest",
1852
+ # "pairs",
1853
+ # "files"
1854
+ # )
1855
+ # end
1856
+ class RuleCheck
1857
+
1858
+ def RuleCheck.check( opt, &rule )
1859
+ rc = RuleCheck.new( opt )
1860
+ rc.instance_eval( &rule )
871
1861
  end
872
1862
 
873
- # Is mandatory argument?
874
- def isRequired
875
- case @type
876
- when :single, :multi; true
877
- else false
878
- end
1863
+ def initialize( opt, &rule )
1864
+ @opt = opt
879
1865
  end
880
1866
 
881
- # Test if option is of switch type.
882
- def isSwitch
883
- case @type
884
- when :switch, :exclusive, :default; true
885
- else false
1867
+
1868
+ # Get given count.
1869
+ def getScore( *args )
1870
+ score = 0
1871
+ args.each do |i|
1872
+ if i.class == TrueClass || i.class == FalseClass
1873
+ score += 1 if i
1874
+ else
1875
+ score += 1 if @opt.argById(i).given
1876
+ end
886
1877
  end
1878
+ score
887
1879
  end
888
1880
 
889
- # Test if option is silent.
890
- def silent?
891
- @silent
1881
+ # Special condition when no options are given.
1882
+ def none
1883
+ @opt.givenCount == 0
892
1884
  end
893
1885
 
894
- # Custom check for the option. User have to know some Como
895
- # internals.
896
- def check( &check )
897
- instance_eval &check
898
- end
1886
+ # Incremental options in order i.e. have to have previous
1887
+ # to have later.
1888
+ def incr( *args )
1889
+ had = 0
1890
+ add = true
1891
+ scoreAll = 0
1892
+ i = 0
1893
+ while args[i]
1894
+ score = getScore( args[i] )
1895
+ had += 1 if ( score == 1 ) && add
1896
+ add = false if ( score == 0 )
1897
+ scoreAll += score
1898
+ i += 1
1899
+ end
899
1900
 
1901
+ ( had == scoreAll && had > 0 )
1902
+ end
900
1903
 
901
- # Find option object by option str.
902
- def Opt.findOpt( str )
903
- if str == nil
904
- ( @@opts.select { |i| i.type == :default } )[0]
905
- elsif str[0..1] == "--"
906
- ( @@opts.select { |i| i.longOpt == str } )[0]
1904
+ # Incremental options in order i.e. have to have all later
1905
+ # if had first.
1906
+ def follow( *args )
1907
+ if getScore( args[0] )
1908
+ getScore( *args ) == args.length
907
1909
  else
908
- ( @@opts.select { |i| i.opt == str } )[0]
1910
+ true
909
1911
  end
910
1912
  end
911
-
912
- # Como error printout.
913
- def Opt.error( str )
914
- @@io.puts "#{@@progname} error: #{str}"
915
- end
916
1913
 
1914
+ # One of list given.
1915
+ def one( *args )
1916
+ getScore( *args ) == 1
1917
+ end
917
1918
 
918
- # Parse cmdline options from args.
919
- def Opt.parse( args, checkInvalids = true )
920
- ind = 0
921
-
922
- while args[ind]
923
-
924
- if Opt.isOptTerm( args[ind] )
925
-
926
- # Rest of the args do not belong to this program.
927
- ind += 1
928
- @@external = args[ind..-1]
929
- break
930
-
931
- elsif Opt.isOpt( args[ind] )
932
-
933
- o = Opt.findOpt( args[ind] )
934
-
935
- if !o
936
- if checkInvalids
937
- raise InvalidOption, \
938
- "Unknown option (\"#{args[ind]}\")..."
939
- else
940
- o = Opt.findOpt( nil )
941
- if !o
942
- raise InvalidOption, "No default option specified for \"#{args[ind]}\"..."
943
- else
944
- # Default option.
945
- o.value.push Opt.toValue( args[ind] )
946
- ind += 1
947
- end
948
- end
1919
+ # At least one is given.
1920
+ def any( *args )
1921
+ getScore( *args ) > 0
1922
+ end
949
1923
 
950
- elsif o && o.hasArg
1924
+ # All are given.
1925
+ def all( *args )
1926
+ getScore( *args ) == args.length
1927
+ end
951
1928
 
952
- ind += 1
1929
+ # Logical inversion.
1930
+ def inv( *args )
1931
+ getScore( *args ) == 0
1932
+ end
953
1933
 
954
- if ( !args[ind] || Opt.isOpt( args[ind] ) ) &&
955
- o.type != :opt_any
1934
+ # Dont care.
1935
+ def meh( *args )
1936
+ true
1937
+ end
1938
+ end
956
1939
 
957
- raise MissingArgument, \
958
- "No argument given for \"#{o.opt}\"..."
959
1940
 
960
- else
961
1941
 
962
- if o.hasMany
963
-
964
- # Get all argument for multi-option.
965
- o.value = [] if !o.given
966
- while ( args[ind] &&
967
- !Opt.isOpt( args[ind] ) )
968
- o.value.push Opt.toValue( args[ind] )
969
- ind += 1
970
- end
1942
+ # Display utility for RuleCheck. Usage model.
1943
+ #
1944
+ # RuleDisplay.new.evalAndDisplay( &rule )
1945
+ #
1946
+ # Example expansion of options:
1947
+ #
1948
+ # |--# One of:
1949
+ # | |--# Adding in order:
1950
+ # | | |--<gcov>
1951
+ # | | |--<exclude>
1952
+ # | | |--<refreshed>
1953
+ # | |--<manifest>
1954
+ # | |--<pairs>
1955
+ # | |--<files>
1956
+ #
1957
+ class RuleDisplay < ComoCommon
1958
+
1959
+ # Eval rules to get an nested array and then display it.
1960
+ def RuleDisplay.print( prefixStr = " ", &rule )
1961
+ rd = RuleDisplay.new( prefixStr )
1962
+ rd.evalAndDisplay( &rule )
1963
+ end
971
1964
 
972
- else
1965
+ def initialize( prefixStr )
1966
+ # Prefix string for lines. Rules add/rm from it.
1967
+ @prefixStr = prefixStr
1968
+ end
973
1969
 
974
- # Get one argument for single-option.
1970
+ def evalAndDisplay( &rule )
1971
+ printRule( instance_eval( &rule ) )
1972
+ end
975
1973
 
976
- if o.given
977
- raise InvalidOption, \
978
- "Too many arguments for option (\"#{o.name}\")..."
979
- else
980
- o.value = Opt.toValue( args[ind] )
981
- end
982
- ind += 1
983
- end
984
- end
1974
+ # Increase prefix string.
1975
+ def addPrefix( str )
1976
+ @prefixStr += str
1977
+ end
985
1978
 
986
- o.given = true
1979
+ # Remove from prefix (either str or length ).
1980
+ def rmPrefix( item )
1981
+ if item.class == String
1982
+ cnt = item.length
1983
+ else
1984
+ cnt = item
1985
+ end
1986
+ @prefixStr = @prefixStr[0..-(cnt+1)]
1987
+ end
987
1988
 
988
- else
1989
+ # Print prefix + str.
1990
+ def p( str )
1991
+ @@io.puts( @prefixStr + str )
1992
+ end
989
1993
 
990
- if !o
991
- raise InvalidOption, "No valid options specified..."
992
- else
993
- o.value = !o.value if !o.given
994
- o.given = true
995
- ind += 1
996
- end
997
- end
1994
+ # Recursively go through the nested array of rule items and
1995
+ # print out rules.
1996
+ def printRule( arr )
1997
+ p( "|--# #{arr[0]}:" )
1998
+ item = "| "
1999
+ addPrefix( item )
998
2000
 
2001
+ arr[1..-1].each do |i|
2002
+ if i.class == Array
2003
+ printRule( i )
999
2004
  else
1000
-
1001
- # Default option.
1002
-
1003
- o = Opt.findOpt( nil )
1004
-
1005
- if !o
1006
- raise InvalidOption, "No default option specified for \"#{args[ind]}\"..."
1007
- else
1008
- while ( args[ind] && !Opt.isOpt( args[ind] ) )
1009
- o.value.push Opt.toValue( args[ind] )
1010
- ind += 1
1011
- end
1012
- end
2005
+ p( "|--<#{i}>" )
1013
2006
  end
1014
2007
  end
2008
+ rmPrefix( item )
1015
2009
  end
1016
2010
 
2011
+ # Special condition where no arguments are given.
2012
+ def none
2013
+ [ "NONE" ]
2014
+ end
1017
2015
 
1018
- # Return cmdline usage strings for options in an Array.
1019
- def Opt.cmdline
1020
- opts = []
1021
-
1022
- @@opts.each do |o|
2016
+ # Incremental options in order i.e. have to have previous
2017
+ # to have later.
2018
+ def incr( *args )
2019
+ [ "Adding in order", *args ]
2020
+ end
1023
2021
 
1024
- next if o.silent?
2022
+ # Incremental options in order i.e. have to have all later
2023
+ # if had first.
2024
+ def follow( *args )
2025
+ [ "If first then rest", *args ]
2026
+ end
1025
2027
 
1026
- prural = nil
1027
- case o.type
1028
- when :multi, :opt_multi; prural = "+"
1029
- when :opt_any; prural = "*"
1030
- else prural = ""
1031
- end
2028
+ # One of list given.
2029
+ def one( *args )
2030
+ [ "One of", *args ]
2031
+ end
1032
2032
 
1033
- if !( o.isSwitch )
1034
- name = " <#{o.name}>#{prural}"
1035
- else
1036
- name = ""
1037
- end
2033
+ # At least one is given.
2034
+ def any( *args )
2035
+ [ "One or more of", *args ]
2036
+ end
1038
2037
 
1039
- if o.opt == nil
1040
- opt = o.longOpt
1041
- else
1042
- opt = o.opt
1043
- end
2038
+ # All are given.
2039
+ def all( *args )
2040
+ [ "All of", *args ]
2041
+ end
1044
2042
 
1045
- if o.isRequired
1046
- opts.push "#{opt}#{name}"
1047
- else
1048
- opts.push "[#{opt}#{name}]"
1049
- end
1050
- end
2043
+ # Logical inversion.
2044
+ def inv( *args )
2045
+ [ "Not", *args ]
2046
+ end
1051
2047
 
1052
- opts
2048
+ # Dont care.
2049
+ def meh( *args )
2050
+ [ "Ignoring", *args ]
1053
2051
  end
1054
2052
 
1055
2053
  end
1056
2054
 
2055
+
1057
2056
  end