Usage 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/Usage.rb ADDED
@@ -0,0 +1,858 @@
1
+ require "rubygems"
2
+ require_gem "SimpleTrace", "<= 0.1.0"
3
+
4
+ #
5
+ # Use module to hide usage classes
6
+ #
7
+ module UsageMod
8
+
9
+ # ---------------------- Usage Exception Classes ---------------------------
10
+ #
11
+ # Base exception class for all usage exceptions
12
+ #
13
+ class Error < StandardError
14
+ end
15
+
16
+ # ----------------------- Runtime Errors -----------------------------------
17
+
18
+ #
19
+ # Too few arguments were given to the program when it was run
20
+ #
21
+ class TooFewArgError < Error
22
+ end
23
+
24
+ #
25
+ # Extra arguments were given to the program when it was run
26
+ #
27
+ class ExtraArgError < Error
28
+ end
29
+
30
+ #
31
+ # The option given by the user when the program was run was not allowed
32
+ #
33
+ class UnknownOptionError < Error
34
+ end
35
+
36
+ #
37
+ # The user has requested the long version of the usage (by --help)
38
+ #
39
+ class LongUsageRequested < Error
40
+ end
41
+
42
+ #
43
+ # Certain required options were not specified when the program was run
44
+ #
45
+ class MissingOptionsError < Error
46
+ def initialize(missing_options)
47
+ super("missing option(s) '" + missing_options.uniq.map{|opt| opt.name}.join(",") + "'")
48
+ end
49
+ end
50
+
51
+ #
52
+ # A required argument for an option was omitted when the program was run
53
+ #
54
+ class MissingOptionArgumentError < Error
55
+ def initialize(option)
56
+ super("option '#{option.name}' is missing its argument")
57
+ end
58
+ end
59
+
60
+ #
61
+ # The user specified an invalid choice when the program was run
62
+ #
63
+ class InvalidChoiceError < Error
64
+ attr_reader :option, :choice
65
+ def initialize(option, choice)
66
+ @option, @choice = option, choice
67
+ super("invalid choice:'#{choice}', it should be one of these:" + option.choices.join(","))
68
+ end
69
+ end
70
+
71
+ #
72
+ # The user specified a non integer when an integer argument was expected when the
73
+ # program was run
74
+ #
75
+ class InvalidIntegerError < Error
76
+ attr_reader :integer_str
77
+ def initialize(integer_str)
78
+ @integer_str = integer_str
79
+ super("invalid integer value: '#{integer_str}'")
80
+ end
81
+ end
82
+
83
+ #
84
+ # The user specified a invalid date/time format when the program was run
85
+ #
86
+ class InvalidTimeError < Error
87
+ attr_reader :time_str
88
+ def initialize(time_str)
89
+ @time_str = time_str
90
+ super("invalid date/time: '#{time_str}'")
91
+ end
92
+ end
93
+
94
+ # ------------------------------- Parsing Errors -----------------------------
95
+
96
+ #
97
+ # The infinite argument was not the last in the argument list in the usage string
98
+ #
99
+ class InfiniteArgNotLast < Error
100
+ def initialize
101
+ super("the infinite argument (the one ending in ...) must be the last argument")
102
+ end
103
+ end
104
+
105
+ #
106
+ # Too many closing parens
107
+ #
108
+ class NestingUnderflow < Error
109
+ def initialize
110
+ super("unbalanced parenthesis: too many ')' and not enough '('")
111
+ end
112
+ end
113
+
114
+ #
115
+ # Too many open parens
116
+ #
117
+ class NestingOverflow < Error
118
+ def initialize
119
+ super("unbalanced parenthesis: too many '(' and not enough ')'")
120
+ end
121
+ end
122
+
123
+ #
124
+ # Mismatched number of square braces
125
+ #
126
+ class MismatchedBracesError < Error
127
+ def initialize
128
+ super("mismatched braces")
129
+ end
130
+ end
131
+
132
+ #
133
+ # doing a choice with an option or typed parameter is an error
134
+ #
135
+ class ChoiceNoTypeError < Error
136
+ def initialize
137
+ super("doing a choice with an option or typed parameter is not allowed")
138
+ end
139
+ end
140
+
141
+ #
142
+ # choices are are only allowed as option arguments
143
+ #
144
+ class ChoicesNotOnOptionError < Error
145
+ def initialize
146
+ super("choices are only allowed as option arguments")
147
+ end
148
+ end
149
+
150
+ # ---------------------- Usage Classes -------------------------------------
151
+
152
+ #
153
+ # This class represents an option (something that starts with a '-' or '--')
154
+ # and its characteristics, such as:
155
+ # * whether it is required or not
156
+ # * its short and long names
157
+ # * its type if it has one (default is string)
158
+ # * the choices for it if it is an option with fixed number of choices
159
+ # * its description
160
+ #
161
+ class Option
162
+ attr_reader :name
163
+ attr_accessor :long_name, :arg_name, :arg_type, :is_required
164
+ attr_accessor :has_argument, :choices, :description
165
+ def initialize(name)
166
+ @name = name
167
+ @long_name = nil
168
+ @arg_name = nil
169
+ @arg_type = nil
170
+ @has_argument = nil
171
+ @is_required = true
172
+ @choices = Choices.new([])
173
+ end
174
+
175
+ #
176
+ # allow it to be used in a hash
177
+ #
178
+ def hash
179
+ @name.hash
180
+ end
181
+
182
+ #
183
+ # allow it to be compared with other Option objects
184
+ #
185
+ def ==(other)
186
+ @name = other.name
187
+ end
188
+
189
+ #
190
+ # the option name as a symbol (ie. :dash_x if -x is the option)
191
+ #
192
+ def name_as_symbol
193
+ "dash_#{@name}"
194
+ end
195
+
196
+ #
197
+ # the long option name as a symbol (ie. --long_name is :long_name)
198
+ #
199
+ def long_name_as_symbol
200
+ @long_name.gsub(/-/, "_")
201
+ end
202
+
203
+ def to_s
204
+ "OPTION:[#{@name}][#{@long_name}][#{@arg_type}][required=#{@is_required}]"
205
+ end
206
+ end
207
+
208
+ #
209
+ # This class is an array to hold the choices for an option
210
+ #
211
+ class Choices < Array
212
+ def initialize(array)
213
+ array.each {|a| self.push(a)}
214
+ end
215
+ end
216
+
217
+ #
218
+ # This class describes arguments (not options) which can include:
219
+ # * the name of the argument
220
+ # * its type (default is String)
221
+ # * whether it is an infinite argument or not
222
+ # * whether it is optional or not
223
+ #
224
+ class Argument
225
+ SINGLE = 1
226
+ INFINITE = 2
227
+
228
+ attr_reader :name, :arg_type, :infinite, :optional
229
+
230
+ def initialize(name, arg_type, infinite, optional=false)
231
+ @name = name
232
+ @arg_type = arg_type
233
+ @infinite = infinite
234
+ @optional = optional
235
+ end
236
+
237
+ #
238
+ # allow it to be used in a hash
239
+ #
240
+ def hash
241
+ @name.hash
242
+ end
243
+
244
+ #
245
+ # allow it to be compared with other Argument objects
246
+ #
247
+ def ==(other)
248
+ @name = other.name
249
+ end
250
+
251
+ def to_s
252
+ "ARG:[#{@name}][#{@arg_type}][#{@infinite}]"
253
+ end
254
+ end
255
+
256
+ #
257
+ # This class holds the parsed representation of the usage string. It holds the
258
+ # options (both required and optional) and the arguments (both required and optional)
259
+ #
260
+ class ArgumentList
261
+ attr_accessor :infinite_argument, :required_arguments, :options, :optional_arguments
262
+
263
+ def initialize()
264
+ @required_arguments = []
265
+ @optional_arguments = []
266
+ @options_hash = {}
267
+ @infinite_argument = nil
268
+ end
269
+
270
+ #
271
+ # give the last argument in the list
272
+ #
273
+ def last_arg
274
+ @required_arguments[@required_arguments.size - 1]
275
+ end
276
+
277
+ #
278
+ # is there an infinite argument and is it last
279
+ #
280
+ def has_infinite_arg
281
+ @required_arguments.size > 0 && last_arg.infinite
282
+ end
283
+
284
+ #
285
+ # get the next argument
286
+ #
287
+ def get_next_arg( index )
288
+ if index < @required_arguments.size then
289
+ arg = @required_arguments[index]
290
+ elsif index < (@required_arguments.size + @optional_arguments.size) then
291
+ arg = @optional_arguments[index-@required_arguments.size]
292
+ else
293
+ arg = nil
294
+ end
295
+
296
+ arg
297
+ end
298
+
299
+ #
300
+ # add and argument to the list
301
+ #
302
+ def push_arg(aArgument)
303
+ if aArgument.optional then
304
+ $TRACE.debug 5, "push_arg: pushing optional argument #{aArgument.inspect}"
305
+ @optional_arguments.push(aArgument)
306
+ else
307
+ # FIXME: raise "Required arguments cannot follow optional arguments" if @optional_arguments.size > 0
308
+ $TRACE.debug 5, "push_arg: pushing required argument #{aArgument.inspect}"
309
+ @required_arguments.push(aArgument)
310
+ end
311
+ $TRACE.debug 5, "push_arg: @optional_arguments.size = #{@optional_arguments.size}"
312
+ $TRACE.debug 5, "push_arg: @optional_arguments = #{@optional_arguments.inspect}"
313
+ end
314
+
315
+ #
316
+ # add an option to the list
317
+ #
318
+ def push_option(aOption)
319
+ @options_hash[aOption.name] = aOption
320
+ end
321
+
322
+ #
323
+ # update the options_hash with their long name equivalents
324
+ #
325
+ def update_with_long_names
326
+ $TRACE.debug 9, "before long names: options_hash = #{@options_hash.inspect}"
327
+ @options_hash.values.each do |option|
328
+ @options_hash[option.long_name] = option if option.long_name
329
+ end
330
+ $TRACE.debug 9, "after long names: options_hash = #{@options_hash.inspect}"
331
+ end
332
+
333
+ #
334
+ # lookup an option and return Option object based on the name
335
+ #
336
+ def lookup_option(name)
337
+ @options_hash[name]
338
+ end
339
+
340
+ #
341
+ # returns the options that are required
342
+ #
343
+ def required_options
344
+ ar = @options_hash.values
345
+ $TRACE.debug 9, "options_hash = #{@options_hash.inspect}"
346
+ $TRACE.debug 9, "ar = #{ar.inspect}"
347
+ ar.select{|opt| opt.is_required}
348
+ end
349
+
350
+ #
351
+ # check to make sure the argument list is correct?
352
+ #
353
+ def process
354
+ @required_arguments.each_with_index do |arg, i|
355
+ if arg.infinite then
356
+ if i != required_arguments.size - 1 then
357
+ raise InfiniteArgNotLast.new("infinite argument #{arg.name} is not last")
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ #
365
+ # This is the class that does the heavy lifting for the Usage class. It parses the
366
+ # usage string, options string and makes the information available by using
367
+ # method_missing.
368
+ #
369
+ # see the README document for how to format the usage string and options string
370
+ #
371
+ class Base
372
+ #
373
+ # create a UsageMod::Base object. usageString specifies the arguments expected. The
374
+ # optionsString specifies further information about the arguments. userArguments
375
+ # defaults to the program arguments but allows the user to get the arguments from
376
+ # somewhere else.
377
+ #
378
+ def initialize(usageString, optionsString="", userArguments=ARGV)
379
+ @argHash = {}
380
+ @usageString = usageString
381
+ @optionsString = optionsString
382
+ @userArguments = userArguments
383
+
384
+ # parse the usage string
385
+ @argList = parse_usage(usageString)
386
+
387
+ # parse the description
388
+ @descriptions = parse_option_descriptions
389
+
390
+ # update options hash now that some long names may have been parsed
391
+ @argList.update_with_long_names
392
+ end
393
+
394
+
395
+ DESC_PARSE_REGEX = /^-(\w),--(\S+)\s+(.*)$/
396
+
397
+ OPTION_NAME = 1
398
+ OPTION_LONG_NAME = 2
399
+ OPTION_DESCRIPTION = 3
400
+
401
+ #
402
+ # parse the option descriptions
403
+ #
404
+ def parse_option_descriptions
405
+ @maxOptionNameLen = 0
406
+ @optionsString.split("\n").each do |line|
407
+ if m = DESC_PARSE_REGEX.match(line) then
408
+ $TRACE.debug 5, "[#{m[1]}][#{m[2]}][#{m[3]}]"
409
+ len = m[OPTION_NAME].size + m[OPTION_LONG_NAME].size + 4
410
+ @maxOptionNameLen = len if len > @maxOptionNameLen
411
+ if option = @argList.lookup_option(m[OPTION_NAME]) then
412
+ option.long_name = m[OPTION_LONG_NAME]
413
+ option.description = m[OPTION_DESCRIPTION]
414
+ end
415
+ end
416
+ end
417
+ end
418
+
419
+ # @ : date
420
+ # % : integer
421
+ # # : float
422
+ # - : option
423
+ # ([ @%#- name ... )]
424
+ PARSE_REGEX = /^([\(\[])?([\@%#-])?([^.\)\]]*)(\.\.\.)?([\)\]])?$/
425
+
426
+ OPEN_PAREN_OR_BRACKET = 1
427
+ DASH_OR_TYPE = 2
428
+ ARG_OR_OPTION_NAME = 3
429
+ INFINITE = 4
430
+ CLOSE_PAREN_OR_BRACKET = 5
431
+
432
+
433
+ #
434
+ # this parses the usage string and returns an ArgumentList object that
435
+ # describes the arguments by their characteristics (type, optional/required, etc)
436
+ #
437
+ def parse_usage(usageString)
438
+ arg_list = ArgumentList.new
439
+ option_stack = []
440
+ processing_options = true
441
+ option = nil
442
+ choices = nil
443
+ usageString.split(/\s/).each do |arg|
444
+ $TRACE.debug 5, "arg = '#{arg}'"
445
+ m = PARSE_REGEX.match(arg)
446
+ infinite = false
447
+ optional_arg = false
448
+ option = nil
449
+ if m then
450
+ $TRACE.debug 5, "got match, opening='#{m[OPEN_PAREN_OR_BRACKET]}' " +
451
+ "dash_or_type='#{m[DASH_OR_TYPE]}' " +
452
+ "main='#{m[ARG_OR_OPTION_NAME]}' " +
453
+ "infinite = '#{m[INFINITE]}' " +
454
+ "closing='#{m[CLOSE_PAREN_OR_BRACKET]}'"
455
+ $TRACE.debug 9, "option = #{option.inspect}, option_stack = #{option_stack.inspect}"
456
+
457
+ choices = m[ARG_OR_OPTION_NAME].split(/\|/)
458
+ raise ChoicesNotOnOptionError.new if choices.size > 1 && option_stack.size == 0
459
+
460
+ if m[DASH_OR_TYPE] then
461
+ raise ChoiceNoTypeError.new if choices.size > 1
462
+
463
+ case m[DASH_OR_TYPE]
464
+ when "%"
465
+ arg_type = Integer
466
+ $TRACE.debug 5, "is integer"
467
+ when "#"
468
+ arg_type = Float
469
+ $TRACE.debug 5, "is float"
470
+ # when "<"
471
+ # when ">"
472
+ # arg_type = Time
473
+
474
+ when "@"
475
+ arg_type = Time
476
+ $TRACE.debug 5, "is time"
477
+ when "-"
478
+ option = Option.new(m[ARG_OR_OPTION_NAME])
479
+ if m[OPEN_PAREN_OR_BRACKET] then
480
+ option.is_required = (m[OPEN_PAREN_OR_BRACKET] == "(")
481
+ option_stack.push(option)
482
+ else
483
+ option.arg_type = TrueClass
484
+ end
485
+ end
486
+ else
487
+ arg_type = String
488
+ end
489
+
490
+ if m[CLOSE_PAREN_OR_BRACKET] then
491
+ if option_stack.size == 0 then
492
+ if m[OPEN_PAREN_OR_BRACKET] && m[DASH_OR_TYPE] != '-' then
493
+ # Assume this is an optional parameter
494
+ optional_arg = true
495
+ $TRACE.debug 9, "parse_usage: Optional paramater found: #{m[ARG_OR_OPTION_NAME]}"
496
+ else
497
+ raise NestingUnderflow.new
498
+ end
499
+ else
500
+ option = option_stack.pop
501
+ if option.is_required != (m[CLOSE_PAREN_OR_BRACKET] == ")") then
502
+ raise MismatchedBracesError.new
503
+ end
504
+ # it has an argument if this token didn't also have a open bracket
505
+ option.has_argument = !m[OPEN_PAREN_OR_BRACKET]
506
+ if option.has_argument then
507
+ if choices.size > 1 then
508
+ option.choices = Choices.new(choices)
509
+ option.arg_type = Choices
510
+ else
511
+ option.arg_name = m[ARG_OR_OPTION_NAME]
512
+ option.arg_type = arg_type
513
+ end
514
+ end
515
+ end
516
+ end
517
+
518
+ if m[INFINITE] != nil then
519
+ $TRACE.debug 5, "is infinite"
520
+ infinite = true
521
+ end
522
+
523
+ $TRACE.debug 9, "option = #{option.inspect}, option_stack = #{option_stack.inspect}"
524
+ unless option || option_stack.size > 0
525
+ $TRACE.debug 9, "no longer processing options"
526
+ processing_options = false
527
+ end
528
+ else
529
+ arg_type = String
530
+ $TRACE.debug 5, "is string"
531
+ end
532
+
533
+ if processing_options then
534
+ if option_stack.size == 0 then
535
+ $TRACE.debug 5, "adding option #{option.name}"
536
+ arg_list.push_option(option)
537
+ end
538
+ else
539
+ arg = m[ARG_OR_OPTION_NAME]
540
+ if infinite then
541
+ @argHash[arg] = []
542
+ else
543
+ @argHash[arg] = nil
544
+ end
545
+ $TRACE.debug 5, "adding argument '#{arg}'"
546
+ arg_list.push_arg(Argument.new(arg, arg_type, infinite, optional_arg))
547
+
548
+ arg_list.process
549
+ end
550
+ end
551
+
552
+ raise NestingOverflow.new if option_stack.size > 0
553
+
554
+ $TRACE.debug 9, "arg_list = #{arg_list.inspect}\n"
555
+ arg_list
556
+ end
557
+
558
+ #
559
+ # set up @argHash for all the options based on two things:
560
+ #
561
+ # # the parse usage string description in @argList (which is an ArgumentList object)
562
+ # # the arguments that the user ran the program with which is in @userArguments (which is an array of strings)
563
+ #
564
+ # this allows method_missing to return the correct values when it is called
565
+ #
566
+ def parse_options()
567
+ last_index = @userArguments.size
568
+ found_options = []
569
+ option_waiting_for_argument = nil
570
+ $TRACE.debug 5, "parse_options: user arguments = #{@userArguments.inspect}"
571
+ @userArguments.each_with_index do |userarg, index|
572
+ $TRACE.debug 7, "arg = '#{userarg}'"
573
+ # if we found an option
574
+ if m = /^-{1,2}(.*)$/.match(userarg) then
575
+ # break out if we are waiting for an argument
576
+ break if option_waiting_for_argument
577
+
578
+ # handle requests for long usage string
579
+ if m[1] == "help" || m[1] == "?" then
580
+ raise LongUsageRequested
581
+ end
582
+
583
+ # look up the option
584
+ if option = @argList.lookup_option(m[1]) then
585
+ @argHash[option.name_as_symbol] = true
586
+ @argHash[option.long_name_as_symbol] = true if option.long_name
587
+ found_options.push(option)
588
+ if option.has_argument then
589
+ option_waiting_for_argument = option
590
+ end
591
+ else
592
+ raise UnknownOptionError.new("unknown option '#{userarg}'")
593
+ end
594
+
595
+ # else its either a option or regular argument
596
+ else
597
+ # if it is an option argument
598
+ if option = option_waiting_for_argument then
599
+ # process the argument
600
+ value = convert_value(option.arg_type, userarg)
601
+ if option.arg_type == Choices then
602
+ if !option.choices.include?(userarg) then
603
+ raise InvalidChoiceError.new(option, userarg)
604
+ end
605
+ @argHash[option.name_as_symbol] = value
606
+ @argHash[option.long_name_as_symbol] = value if option.long_name
607
+ else
608
+ @argHash[option.arg_name] = value
609
+ end
610
+
611
+ option_waiting_for_argument = nil
612
+
613
+ # otherwise its a regular argument
614
+ else
615
+ # we need to leave this loop
616
+ last_index = index
617
+ break
618
+ end
619
+ end
620
+ end
621
+
622
+ if option_waiting_for_argument then
623
+ raise MissingOptionArgumentError.new(option_waiting_for_argument)
624
+ end
625
+
626
+ missing_options = @argList.required_options - found_options
627
+ if missing_options.size > 0 then
628
+ raise MissingOptionsError.new(missing_options)
629
+ end
630
+
631
+ $TRACE.debug 5, "parse_options: last_index = #{last_index}"
632
+ @userArguments = @userArguments[last_index..-1]
633
+ end
634
+
635
+ #
636
+ # set up @argHash for the options and arguments based on two things (this calls
637
+ # UsageMod::Base#parse_options to parse the options:
638
+ #
639
+ # # the parse usage string description in @argList (which is an ArgumentList object)
640
+ # # the arguments that the user ran the program with which is in @userArguments (which is an array of strings)
641
+ #
642
+ # this allows method_missing to return the correct values when it is called
643
+ #
644
+ def parse_args()
645
+ parse_options
646
+ $TRACE.debug 5, "parse_args: user arguments = #{@userArguments.inspect}"
647
+ user_args_size = @userArguments.size
648
+ req_args_size = @argList.required_arguments.size
649
+ opt_args_size = @argList.optional_arguments.size
650
+ $TRACE.debug 5, "user_args_size = #{user_args_size}, req_args_size = #{req_args_size}\n"
651
+ if user_args_size < req_args_size then
652
+ $TRACE.debug 5, "parse_args: required_arguments = #{@argList.required_arguments.inspect}"
653
+ $TRACE.debug 5, "parse_args: optional_arguments = #{@argList.optional_arguments.inspect}"
654
+ raise TooFewArgError.new("too few arguments #{req_args_size} expected, #{user_args_size} given")
655
+ elsif user_args_size > (req_args_size + opt_args_size)
656
+ if !@argList.has_infinite_arg then
657
+ raise ExtraArgError.new("too many arguments #{req_args_size} expected, #{user_args_size} given")
658
+ end
659
+ end
660
+
661
+ @userArguments.each_with_index do |userarg, index|
662
+ arg = @argList.get_next_arg(index)
663
+ $TRACE.debug 5, "userarg = '#{userarg}', arg = '#{arg}'"
664
+
665
+ if @argList.has_infinite_arg && index + 1 >= req_args_size then
666
+ @argHash[@argList.last_arg.name].push(userarg)
667
+ else
668
+ @argHash[arg.name] = convert_value(arg.arg_type, userarg)
669
+ end
670
+ end
671
+
672
+ self
673
+ end
674
+
675
+ #
676
+ # parse a date/time string, validate it and return a Time object. If there is
677
+ # an error, then throw a InvalidTimeError exception.
678
+ #
679
+ def parse_date_time(str)
680
+ if /(\d+)\/(\d+)\/(\d+)-(\d+):(\d+)/.match(str) then
681
+ month = $1.to_i
682
+ day = $2.to_i
683
+ if $3.size < 3 then
684
+ year = $3.to_i + 2000
685
+ else
686
+ year = $3.to_i
687
+ end
688
+ hour = $4.to_i
689
+ minute = $5.to_i
690
+ return Time.local(year, month, day, hour, minute)
691
+ else
692
+ raise InvalidTimeError.new(str)
693
+ end
694
+ end
695
+
696
+ #
697
+ # parse and integer argument, validate it, and return a Integer object. If there
698
+ # is an error, throw a InvalidIntegerError exception.
699
+ #
700
+ def parse_integer(str)
701
+ if /^-?\d+/.match(str) then
702
+ return str.to_i
703
+ else
704
+ raise InvalidIntegerError.new(str)
705
+ end
706
+ end
707
+
708
+ #
709
+ # convert a value to its typed equivalent
710
+ #
711
+ def convert_value(arg_type, value)
712
+ $TRACE.debug 5, "convert_value: #{value} to #{arg_type}"
713
+ case arg_type.to_s
714
+ when "Integer"
715
+ return parse_integer(value)
716
+ when "Float"
717
+ return value.to_f
718
+ when "Time"
719
+ return parse_date_time(value)
720
+ else
721
+ return value
722
+ end
723
+ end
724
+
725
+ #
726
+ # return the short usage string
727
+ #
728
+ def usage
729
+ @usageString.gsub(/[%#()]/, "") + "\n"
730
+ end
731
+
732
+ #
733
+ # return the long usage string
734
+ #
735
+ def long_usage
736
+ @optionsString.split("\n").map do |line|
737
+ if m = DESC_PARSE_REGEX.match(line) then
738
+ opt_str = "-#{m[OPTION_NAME]},--#{m[OPTION_LONG_NAME]}"
739
+ opt_str + (" " * (@maxOptionNameLen + 2 - opt_str.size)) + m[OPTION_DESCRIPTION]
740
+ elsif /^\s+(\S.*)$/.match(line) then
741
+ (" " * (@maxOptionNameLen + 2)) + $1
742
+ else
743
+ line
744
+ end
745
+ end.join("\r\n")
746
+ end
747
+
748
+ #
749
+ # dump the argument hash
750
+ #
751
+ def dump
752
+ @argHash.keys.sort.each do |k|
753
+ puts "#{k} = #{@argHash[k].inspect}"
754
+ end
755
+ end
756
+
757
+ #
758
+ # uses the argHash to return data specified about the command line arguments
759
+ # by the symbol passed in.
760
+ #
761
+ def method_missing(symbol, *args)
762
+ $TRACE.debug 5, "UsageMod::Base method missing: #{symbol}"
763
+ if @argHash.has_key?(symbol.to_s) then
764
+ @argHash[symbol.to_s]
765
+ end
766
+ end
767
+ end
768
+
769
+ end # module UsageMod
770
+
771
+ #
772
+ # This is the main interface to this usage functionality. It operates by
773
+ # using method_missing to extract information about the command line arguments.
774
+ # Most all the functionality is implemented in the UsageMod::Base class with the Usage
775
+ # class being a wrapper around it to handle errors in a nice graceful fashion.
776
+ #
777
+ # The main way to use it is to give the usage string as the argument to build the
778
+ # usage object like below:
779
+ #
780
+ # usage = Usage.new "input_file output_file"
781
+ #
782
+ # see the README for a more complete description of what can appear in that string.
783
+ #
784
+ class Usage
785
+ #
786
+ # create a new usage object. It is forwarded on to UsageMod::Base#initialize. If an
787
+ # error occurs in UsageMod::Base, this displays the error to the user and displays the
788
+ # usage string as well.
789
+ #
790
+ def initialize(*args)
791
+ @usageBase = UsageMod::Base.new(*args)
792
+ begin
793
+ @usageBase.parse_args()
794
+ rescue LongUsageRequested
795
+ die_long
796
+ rescue UsageMod::Error => usageError
797
+ $TRACE.debug 1, usageError.backtrace.join("\n")
798
+ die(usageError.message)
799
+ rescue
800
+ raise
801
+ end
802
+ end
803
+
804
+ #
805
+ # Allows the program to die in a graceful manner. It displays
806
+ #
807
+ # ERROR: the error message
808
+ #
809
+ # It then calls exit (so it doesn't return)
810
+ #
811
+ def die(message)
812
+ print_program_name
813
+ print "ERROR: #{message}\n"
814
+ print "\n"
815
+ print_short_usage
816
+ exit 1
817
+ end
818
+
819
+ #
820
+ # Allows the program to die in a graceful manner. It displays
821
+ #
822
+ # ERROR: the error message
823
+ #
824
+ # followed by the usage string. It then calls exit (so it doesn't return)
825
+ #
826
+ def die_long
827
+ print_program_name
828
+ print_short_usage
829
+ print @usageBase.long_usage + "\n"
830
+ exit 1
831
+ end
832
+
833
+ #
834
+ # Prints out the program's name in a graceful manner.
835
+ #
836
+ # PROGRAM: program name
837
+ #
838
+ def print_program_name
839
+ print "PROGRAM: #{$0}\n"
840
+ end
841
+
842
+ #
843
+ # Print out the short usage string (without the options)
844
+ #
845
+ def print_short_usage
846
+ print "USAGE: #{File.basename($0)} #{@usageBase.usage}\n"
847
+ end
848
+
849
+ #
850
+ # forward all other calls to UsageMod::Base class
851
+ #
852
+ def method_missing(symbol, *args)
853
+ $TRACE.debug 5, "Usage method missing: #{symbol}"
854
+ @usageBase.send(symbol, *args)
855
+ end
856
+ end
857
+
858
+ # ================= if running this file ==============================================================