Usage 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 ==============================================================