como 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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