OptionParser 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005
6
+ #
7
+ # =DESCRIPTION
8
+ # Loader
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 2005/06/14 Birthday
12
+ #
13
+
14
+ require 'commandline/optionparser/option'
15
+ require 'commandline/optionparser/optionparser'
16
+ require 'commandline/optionparser/optiondata'
@@ -0,0 +1,183 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005 Jim Freeze
6
+ #
7
+ # =DESCRIPTION
8
+ # A very flexible commandline parser
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 04/01/2005 Birthday
12
+ #
13
+
14
+ module CommandLine
15
+
16
+ class Option
17
+ class OptionError < StandardError; end
18
+ class InvalidOptionNameError < OptionError; end
19
+ class InvalidArgumentError < OptionError; end
20
+ class MissingOptionNameError < OptionError; end
21
+ class InvalidArgumentArityError < OptionError; end
22
+ class MissingPropertyError < OptionError; end
23
+ class InvalidPropertyError < OptionError; end
24
+ class InvalidConstructionError < OptionError; end
25
+
26
+ attr_accessor :posix
27
+
28
+ #
29
+ GENERAL_OPT_EQ_ARG_RE = /^(-{1,2}[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/ # :nodoc:
30
+ GNU_OPT_EQ_ARG_RE = /^(--[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/
31
+ #OPTION_RE = /^-{1,2}([a-zA-Z]+\w*)(.*)/
32
+ #UNIX_OPT_EQ_ARG_RE = /^(-[a-zA-Z])=(.*)$/
33
+ #UNIX_OPT_EQorSP_ARG_RE = /^(-[a-zA-Z])(=|\s+)(.*)$/
34
+
35
+ POSIX_OPTION_RE = /^-[a-zA-Z]?$/
36
+ # need to change this to support - and --
37
+ NON_POSIX_OPTION_RE = /^(-|-{1,2}[a-zA-Z_]+[-_a-zA-Z0-9]*)/
38
+
39
+ PROPERTIES = [ :arg_arity, :opt_description, :arg_description,
40
+ :opt_found, :opt_not_found, :posix
41
+ ]
42
+
43
+ FLAG_BASE_OPTS = {
44
+ :arg_arity => [0,0],
45
+ # :opt_description => nil,
46
+ :arg_description => "",
47
+ :opt_found => true,
48
+ :opt_not_found => false
49
+ }
50
+
51
+ # You get these without asking for them
52
+ DEFAULT_OPTS = {
53
+ :arg_arity => [1,1],
54
+ :opt_description => "",
55
+ :arg_description => "",
56
+ :opt_found => true,
57
+ :opt_not_found => false
58
+ }
59
+
60
+ #
61
+ # Option.new(:posix => true, :names => %w(--opt), :flag)
62
+ #
63
+ # TODO: Should we test and raise key is not one of :names, opt_description, ...
64
+ # This will prevent typos. Can users add properties to an Option that are their own?
65
+ def initialize(*all)
66
+ @posix = false
67
+
68
+ raise(MissingPropertyError,
69
+ "No properties specified for new #{self.class}.") if all.empty?
70
+
71
+ until Hash === all[0]
72
+ case (prop = all.shift)
73
+ when :flag then @flag = true
74
+ when :posix then @posix = true
75
+ else
76
+ raise(InvalidPropertyError, "Unknown option setting '#{prop}'.")
77
+ end
78
+ end
79
+
80
+ properties = all[0]
81
+
82
+ type = @flag.nil? ? :default : :flag
83
+ merge_hash =
84
+ case type
85
+ when :flag then FLAG_BASE_OPTS
86
+ when :default then DEFAULT_OPTS
87
+ else raise(InvalidConstructionError,
88
+ "Invalid arguments to Option.new. Must be a property hash with "+
89
+ "keys [:names, :arg_arity, :opt_description, :arg_description, "+
90
+ ":opt_found, :opt_not_found] or "+
91
+ "an option type [:flag, :default].")
92
+ end
93
+
94
+ raise(InvalidPropertyError,
95
+ "Don't understand argument of type '#{properties.class}' => "+
96
+ "#{properties.inspect} passed to #{self.class}.new. Looking "+
97
+ "for type Hash.") unless properties.kind_of?(Hash)
98
+
99
+ if properties.has_key?(:posix)
100
+ @posix = properties[:posix]
101
+ properties.delete(:posix)
102
+ end
103
+ @properties = merge_hash.merge(properties)
104
+
105
+ @properties[:names] = [@properties[:names]] unless
106
+ @properties[:names].kind_of?(Array)
107
+
108
+ arg_arity = @properties[:arg_arity]
109
+ @properties[:arg_arity] = [arg_arity, arg_arity] unless
110
+ arg_arity.kind_of?(Array)
111
+
112
+ raise "Invalid value for arg_arity '#{arg_arity}'." unless
113
+ arg_arity.kind_of?(Array) || arg_arity.kind_of?(Fixnum)
114
+
115
+ raise(InvalidArgumentArityError,
116
+ "Conflicting value given to new option: :flag "+
117
+ "and :arg_arity = #{properties[:arg_arity].inspect}.") if
118
+ :flag == type && [0,0] != @properties[:arg_arity]
119
+
120
+ names = @properties[:names]
121
+ raise(MissingOptionNameError,
122
+ "Attempt to create an Option without :names defined.") if
123
+ names.nil? || names.empty?
124
+
125
+ names.each { |name| check_option_name(name) }
126
+ validate_arity @properties[:arg_arity]
127
+
128
+ create_opt_description if :flag == type
129
+ end
130
+
131
+ def create_opt_description
132
+ return if @properties.has_key?(:opt_description)
133
+ word = @properties[:names].grep(/^--\w.+/)
134
+ if word.empty?
135
+ @properties[:opt_description] = ""
136
+ else
137
+ @properties[:opt_description] = "Sets #{word.first[2..-1]} to true."
138
+ end
139
+ end
140
+
141
+ def check_option_name(name)
142
+ raise(InvalidOptionNameError,
143
+ "Option name '#{name}' contains invalid space.") if /\s+/.match(name)
144
+
145
+ if @posix
146
+ raise(InvalidOptionNameError,
147
+ "Option name '#{name}' is invalid.") unless POSIX_OPTION_RE.match(name)
148
+ else
149
+ raise(InvalidOptionNameError,
150
+ "Option name '#{name}' is invalid.") unless NON_POSIX_OPTION_RE.match(name)
151
+ end
152
+ end
153
+
154
+ def validate_arity(arity)
155
+ raise ":arg_arity is nil" if arity.nil?
156
+ min, max = *arity
157
+
158
+ raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
159
+ "greater than or equal to 0.") unless min >= 0
160
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
161
+ "greater than or equal to -1.") if max < -1
162
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
163
+ "greater than minimum arg_arity '#{min}'.") if max < min && max != -1
164
+ if @posix
165
+ raise(InvalidArgumentArityError, "Posix options only support :arg_arity "+
166
+ "of [0,0] or [1,1].") unless ([0,0] == arity) || ([1,1] == arity)
167
+ end
168
+ end
169
+
170
+ def method_missing(sym, *args)
171
+ raise "Unknown property '#{sym}' for option
172
+ #{@properties[:names].inspect unless @properties[:names].nil?}." unless
173
+ @properties.has_key?(sym) || PROPERTIES.include?(sym)
174
+ @properties[sym, *args]
175
+ end
176
+
177
+ def to_hash
178
+ Marshal.load(Marshal.dump(@properties))
179
+ end
180
+
181
+ end#class Option
182
+
183
+ end#module CommandLine
@@ -0,0 +1,54 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005 Jim Freeze
6
+ #
7
+ # =DESCRIPTION
8
+ # A very flexible commandline parser
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 04/01/2005 Birthday
12
+ #
13
+ #
14
+
15
+ module CommandLine
16
+
17
+ #
18
+ # Data resulting from parsing a command line (Array)
19
+ # using a particular OptionParser object
20
+ #
21
+ class OptionData
22
+ attr_reader :argv, :unknown_options, :args, :not_parsed, :cmd
23
+
24
+ class OptionDataError < StandardError; end
25
+ class UnknownOptionError < OptionDataError; end
26
+
27
+ def initialize(argv, opts, unknown_options, args, not_parsed, cmd)
28
+ @opts = {}
29
+ opts.each { |k,v|
30
+ @opts[k] =
31
+ begin
32
+ Marshal.load(Marshal.dump(v))
33
+ rescue
34
+ v
35
+ end
36
+ }
37
+ @unknown_options = Marshal.load(Marshal.dump(unknown_options))
38
+ @not_parsed = Marshal.load(Marshal.dump(not_parsed))
39
+ @argv = Marshal.load(Marshal.dump(argv))
40
+ @args = Marshal.load(Marshal.dump(args))
41
+ @cmd = Marshal.load(Marshal.dump(cmd))
42
+ end
43
+
44
+ def [](key)
45
+ if @opts.has_key?(key)
46
+ @opts[key]
47
+ else
48
+ raise(UnknownOptionError, "Unknown option '#{key}'.")
49
+ end
50
+ end
51
+
52
+ end#class OptionData
53
+
54
+ end#module CommandLine
@@ -0,0 +1,511 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005 Jim Freeze
6
+ #
7
+ # =DESCRIPTION
8
+ # A very flexible commandline parser
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 04/01/2005 Birthday
12
+ #
13
+ # :include: README
14
+ #
15
+
16
+ module CommandLine
17
+
18
+ class OptionParser
19
+ attr_reader :posix, :unknown_options, :unknown_options_action
20
+
21
+ attr_accessor :columns, :body_indent, :tag_paragraph
22
+
23
+ DEFAULT_CONSOLE_WIDTH = 70
24
+ MIN_CONSOLE_WIDTH = 10
25
+ DEFAULT_BODY_INDENT = 4
26
+
27
+ #
28
+ # These helper lambdas are here because OptionParser is the object
29
+ # that calls them and hence knows the parameter order.
30
+ #
31
+
32
+ OPT_NOT_FOUND_BUT_REQUIRED = lambda { |opt|
33
+ raise(MissingRequiredOptionError,
34
+ "Missing required parameter '#{opt.names[0]}'.")
35
+ }
36
+
37
+ GET_ARG_ARRAY = lambda { |opt, user_opt, args| args }
38
+
39
+ GET_ARGS = lambda { |opt, user_opt, args|
40
+ return true if args.empty?
41
+ return args[0] if 1 == args.size
42
+ args
43
+ }
44
+
45
+ #
46
+ # Option Errors. Not the oxymoron below for MissingRequiredOptionError.
47
+ # The user can make an option required by adding the OPT_NOT_FOUND_BUT_REQUIRED
48
+ # lambda for the #opt_not_found action.
49
+ #
50
+ class OptionParserError < StandardError; end
51
+ class DuplicateOptionNameError < OptionParserError; end
52
+ class MissingRequiredOptionError < OptionParserError; end
53
+ class MissingRequiredOptionArgumentError < OptionParserError; end
54
+ class UnknownOptionError < OptionParserError; end
55
+ class UnknownPropertyError < OptionParserError; end
56
+ class PosixMismatchError < OptionParserError; end
57
+
58
+ def initialize(*opts_and_props)
59
+ @posix = false
60
+ @unknown_options_action = :raise
61
+ @unknown_options = []
62
+ @opt_lookup_by_any_name = {}
63
+ @command_options = nil
64
+
65
+ #
66
+ # Formatting defaults
67
+ #
68
+ console_width = ENV["COLUMNS"]
69
+ @columns =
70
+ if console_width.nil?
71
+ DEFAULT_CONSOLE_WIDTH
72
+ elsif console_width < MIN_CONSOLE_WIDTH
73
+ console_width
74
+ else
75
+ console_width - DEFAULT_BODY_INDENT
76
+ end
77
+ @body_indent = DEFAULT_BODY_INDENT
78
+ @tag_paragraph = false
79
+ @order = :index # | :alpha
80
+
81
+ props = []
82
+ keys = {}
83
+ opts_and_props.flatten!
84
+ opts_and_props.delete_if { |op|
85
+ if Symbol === op
86
+ props << op; true
87
+ elsif Hash === op
88
+ keys.update(op); true
89
+ else
90
+ false
91
+ end
92
+ }
93
+
94
+ props.each { |p|
95
+ case p
96
+ when :posix then @posix = true
97
+ else
98
+ raise(UnknownPropertyError, "Unknown property '#{p.inspect}'.")
99
+ end
100
+ }
101
+
102
+ keys.each { |k,v|
103
+ case k
104
+ when :unknown_options_action
105
+ if [:collect, :ignore, :raise].include?(v)
106
+ @unknown_options_action = v
107
+ else
108
+ raise(UnknownPropertyError, "Unknown value '#{v}' for "+
109
+ ":unknown_options property.")
110
+ end
111
+ when :command_options
112
+ @command_options = v
113
+ @commands = v.keys
114
+ else
115
+ raise(UnknownPropertyError, "Unknown property '#{k.inspect}'.")
116
+ end
117
+ }
118
+ # :unknown_options => :collect
119
+ # :unknown_options => :ignore
120
+ # :unknown_options => :raise
121
+
122
+ opts = opts_and_props
123
+
124
+ @options = []
125
+ opts.each { |opt|
126
+ # If user wants to parse posix, then ensure all options are posix
127
+ raise(PosixMismatchError,
128
+ "Posix types do not match. #{opt.inspect}") if @posix && !opt.posix
129
+ @options << opt
130
+ }
131
+
132
+ #p "options-"*5
133
+ #p @options
134
+ add_names(@options)
135
+
136
+ yield self if block_given?
137
+ end
138
+
139
+ #
140
+ # add_option :names => %w{--file --use-this-file -f},
141
+ # :
142
+ #
143
+ # add_option :names => %w(--version -v),
144
+ # :arg_arity => [0,0], # default
145
+ # :option_description => "Returns Version"
146
+ # add_option :names => %w(--file -f),
147
+ # :arg_arity => [1,:unlimited],
148
+ # :opt_description => "Define the output filename.",
149
+ # :arg_description => "Output file"
150
+ # :opt_exists => lambda {}
151
+ # :opt_not_exists => lambda {}
152
+ # :option_found
153
+ # :no_option_found
154
+ # :opt_found => lambda {}
155
+ # :no_opt_found => lambda {}
156
+ #
157
+
158
+ #
159
+ # Add an option
160
+ #
161
+ def <<(option)
162
+ @options << option
163
+ add_names(option)
164
+ self
165
+ end
166
+
167
+ def add_names(*options)
168
+ options.flatten.each { |option|
169
+ option.names.each { |name|
170
+ raise(DuplicateOptionNameError,
171
+ "Duplicate option name '#{name}'.") if
172
+ @opt_lookup_by_any_name.has_key?(name)
173
+ @opt_lookup_by_any_name[name] = option
174
+ }
175
+ }
176
+ end
177
+
178
+ def validate_parse_options(h)
179
+ h[:names].each { |name| check_option_name(name) }
180
+
181
+ #if @posix
182
+ # all are single-dash:single-char OR double-dash:multi-char
183
+ #else if unix compliant
184
+ # single-dash only
185
+ #else any - does not support combination - try to on single/single
186
+ #end
187
+ end
188
+
189
+ # def [](opt)
190
+ # @options[@opt_lookup_by_any_name[opt][0]]
191
+ # end
192
+
193
+ #
194
+ # Parse the command line
195
+ #
196
+ def parse(argv=ARGV)
197
+ argv = [argv] unless Array === argv
198
+
199
+ #
200
+ # Holds the results of each option. The key used is
201
+ # the first in the :names Array.
202
+ #
203
+ opts = Hash.new( :not_found )
204
+
205
+ #
206
+ # A command is the first non-option free argument on the command line.
207
+ # This is a user selection and is the first argument in args.
208
+ # cmd = args.shift
209
+ # Example:
210
+ # cvs -v cmd --cmd-option arg
211
+ #
212
+ cmd = nil
213
+ cmd_options = {}
214
+
215
+ #
216
+ # #parse_argv yields an array containing the option and its arguments.
217
+ # [opts, array_args]
218
+ # How do we collect all the arguments when OptionParser deal with an
219
+ # empty option list
220
+ #
221
+ parse_argv(argv) { |optarg|
222
+ user_option = optarg[0]
223
+ args = optarg[1]
224
+
225
+ m = nil
226
+ if @opt_lookup_by_any_name.has_key?(user_option) ||
227
+ 1 == (m = @opt_lookup_by_any_name.keys.grep(/^#{user_option}/)).size
228
+ user_option = m[0] if m
229
+ opt = @opt_lookup_by_any_name[user_option]
230
+ opt_key = opt.names[0]
231
+
232
+ opts[opt_key] =
233
+ if Proc === opt.opt_found
234
+ # Take the arguments depending upon arity
235
+ opt_args = get_opt_args(opt, user_option, args)
236
+ opt.opt_found.call(opt, user_option, opt_args)
237
+ else
238
+ opt.opt_found
239
+ end
240
+ # Collect any remaining args
241
+ @args += args
242
+
243
+ elsif :collect == @unknown_options_action
244
+ @unknown_options << user_option
245
+ elsif :ignore == @unknown_options_action
246
+ else
247
+ raise(UnknownOptionError, "Unknown option '#{user_option}' in "+
248
+ "#{@opt_lookup_by_any_name.inspect}.")
249
+ end
250
+ }
251
+
252
+ #
253
+ # Call :not_found for all the options not on the command line.
254
+ #
255
+ @options.each { |opt|
256
+ name = opt.names[0]
257
+ if :not_found == opts[name]
258
+ opts[name] =
259
+ if Proc === opt.opt_not_found
260
+ opt.opt_not_found.call(opt)
261
+ else
262
+ opt.opt_not_found
263
+ end
264
+ end
265
+ }
266
+
267
+ OptionData.new(argv, opts, @unknown_options, @args, @not_parsed, cmd)
268
+ end
269
+
270
+ def get_opt_args(opt, user_option, args)
271
+ min, max = *opt.arg_arity
272
+ size = args.size
273
+
274
+ if (min == max && max > 0 && size < max) || (size < min)
275
+ raise(MissingRequiredOptionArgumentError,
276
+ "Insufficient arguments #{args.inspect}for option '#{user_option}' "+
277
+ "with :arg_arity #{opt.arg_arity.inspect}")
278
+ end
279
+
280
+ if 0 == min && 0 == max
281
+ []
282
+ else
283
+ max = size if -1 == max
284
+ args.slice!(0..[min, [max, size].min].max - 1)
285
+ end
286
+ end
287
+
288
+ def get_posix_re
289
+ flags = []
290
+ nflags = []
291
+ @options.each { |o|
292
+ if [0,0] == o.arg_arity
293
+ flags << o.names[0][1..1]
294
+ else
295
+ nflags << o.names[0][1..1]
296
+ end
297
+ }
298
+ flags = flags.join
299
+ flags = flags.empty? ? "" : "[#{flags}\]+"
300
+ nflags = nflags.join
301
+ nflags = nflags.empty? ? "" : "[#{nflags}\]"
302
+ Regexp.new("^-(#{flags})(#{nflags})(.*)\$")
303
+ end
304
+
305
+ #######################################################################
306
+ def parse_posix_argv(argv)
307
+ re = @posix ? get_posix_re : Option::GENERAL_OPT_EQ_ARG_RE
308
+ p re if $DEBUG
309
+ tagged = []
310
+
311
+ #
312
+ # A Posix command line must have all the options precede
313
+ # non option arguments. For example
314
+ # :names => -h -e -l -p -s
315
+ # where -p can take an argument
316
+ # Command line can read:
317
+ # -helps => -h -e -l -p s
318
+ # -p fred non-opt-arg
319
+ # -p fred non-opt-arg -h # not ok
320
+ # -he -popt-arg1 -popt-arg2 non-opt-arg
321
+ # -p=fred # this is not legal?
322
+ # -pfred === -p fred
323
+ #
324
+
325
+ #"-helps" "-pfred" "-p" "fred"
326
+ #-h -e -l -p [s] -p [fred] -p [fred]
327
+ #[-h, []], [-e []], [-l, []], [-p, [s]], -p
328
+
329
+ argv.each { |e|
330
+ m = re.match(e)
331
+ if m.nil?
332
+ tagged << [:arg, e]
333
+ else
334
+ raise "houston, we have a problem" if m.nil?
335
+ unless m[1].empty?
336
+ m[1].split(//).each { |e| tagged << [:opt, "-#{e}"] }
337
+ end
338
+
339
+ unless m[2].empty?
340
+ tagged << [:opt, "-#{m[2]}"]
341
+ tagged << [:arg, m[3]] unless m[3].empty?
342
+ end
343
+ end
344
+ }
345
+
346
+ if $DEBUG
347
+ print "Tagged:"
348
+ p tagged
349
+ end
350
+ #
351
+ # Now, combine any adjacent args such that
352
+ # [[:arg, "arg1"], [:arg, "arg2"]]
353
+ # becomes
354
+ # [[:args, ["arg1", "arg2"]]]
355
+ # and the final result should be
356
+ # [ "--file", ["arg1", "arg2"]]
357
+ #
358
+
359
+ parsed = []
360
+ @args = []
361
+ tagged.each { |e|
362
+ if :opt == e[0]
363
+ parsed << [e[1], []]
364
+ else
365
+ if Array === parsed[-1]
366
+ parsed[-1][-1] += [e[1]]
367
+ else
368
+ @args << e[1]
369
+ end
370
+ end
371
+ }
372
+ parsed.each { |e| yield e }
373
+ end
374
+
375
+ #
376
+ # Seperates options from arguments
377
+ # Does not look for valid options ( or should it? )
378
+ #
379
+ # %w(-fred file1 file2) => ["-fred", ["file1", "file2"]]
380
+ # %w(--fred -t -h xyz) => ["--fred", []] ["-t", []] ["-h", ["xyz"]]
381
+ # %w(-f=file) => ["-f", ["file"]]
382
+ # %w(--file=fred) => ["--file", ["fred"]]
383
+ # %w(-file=fred) => ["-file", ["fred"]]
384
+ # ['-file="fred1 fred2"'] => ["-file", ["fred1", "fred2"]]
385
+ #
386
+ def parse_argv(argv, &block)
387
+ return parse_posix_argv(argv, &block) if @posix
388
+
389
+ @not_parsed = []
390
+ tagged = []
391
+ argv.each_with_index { |e,i|
392
+ if "--" == e
393
+ @not_parsed = argv[(i+1)..(argv.size+1)]
394
+ break
395
+ elsif "-" == e
396
+ tagged << [:arg, e]
397
+ elsif ?- == e[0]
398
+ m = Option::GENERAL_OPT_EQ_ARG_RE.match(e)
399
+ if m.nil?
400
+ tagged << [:opt, e]
401
+ else
402
+ tagged << [:opt, m[1]]
403
+ tagged << [:arg, m[2]]
404
+ end
405
+ else
406
+ tagged << [:arg, e]
407
+ end
408
+ }
409
+
410
+ #
411
+ # The tagged array has the form:
412
+ # [
413
+ # [:opt, "-a"], [:arg, "filea"],
414
+ # [:opt, "-b"], [:arg, "fileb"],
415
+ # #[:not_parsed, ["-z", "-y", "file", "file2", "-a", "-b"]]
416
+ # ]
417
+
418
+ #
419
+ # Now, combine any adjacent args such that
420
+ # [[:arg, "arg1"], [:arg, "arg2"]]
421
+ # becomes
422
+ # [[:args, ["arg1", "arg2"]]]
423
+ # and the final result should be
424
+ # [ "--file", ["arg1", "arg2"]]
425
+ #
426
+
427
+ parsed = []
428
+ @args = []
429
+ tagged.each { |e|
430
+ if :opt == e[0]
431
+ parsed << [e[1], []]
432
+ elsif :arg == e[0]
433
+ if Array === parsed[-1]
434
+ parsed[-1][-1] += [e[1]]
435
+ else
436
+ @args << e[1]
437
+ end
438
+ else
439
+ raise "How did we get here?"
440
+ end
441
+ }
442
+ parsed.each { |e| block.call(e) }
443
+ end
444
+
445
+ def to_str
446
+ to_s
447
+ end
448
+
449
+ def to_s(sep="\n")
450
+ require 'commandline/text/format'
451
+ @f = Text::Format.new
452
+ @f.columns = @columns
453
+ @f.first_indent = 4
454
+ @f.body_indent = 8
455
+ @f.tag_paragraph = false
456
+
457
+ header = ["OPTIONS\n"]
458
+ s = []
459
+ @options.each { |opt|
460
+ opt_str = []
461
+ if block_given?
462
+ result = yield(opt.names, opt.opt_description, opt.arg_description)
463
+ if result.kind_of?(String)
464
+ opt_str << result unless result.empty?
465
+ elsif result.nil?
466
+ opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
467
+ elsif result.kind_of?(Array) && 3 == result.size
468
+ opt_str << format_option(*result)
469
+ else
470
+ raise "Invalid return value #{result.inspect} from yield block "+
471
+ "attached to #to_s."
472
+ end
473
+ else
474
+ opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
475
+ end
476
+ s << opt_str.join unless opt_str.empty?
477
+ }
478
+ #s.collect! { |i| i.kind_of?(Array) && /\n+/ =~ i[0] ? i.join : f.paragraphs(i) }
479
+ [header, s].flatten.join(sep)
480
+ end
481
+
482
+ def format_option(names, opt_desc, arg_desc)
483
+ # TODO: Clean up the magic numbers
484
+
485
+ f = Text::Format.new
486
+ f.columns = @columns
487
+ f.first_indent = 4
488
+ f.body_indent = 8
489
+ f.tabstop = 4
490
+ s = ""
491
+ s << f.format("#{names.join(",")} #{arg_desc}")
492
+ #if 7 == s.last.size
493
+ if 7 == s.size
494
+ f.first_indent = 4 - 2
495
+ s.rstrip!
496
+ s << f.format(opt_desc)
497
+ #elsif 8 == s.last.size
498
+ elsif 8 == s.size
499
+ f.first_indent = 4 - 3
500
+ s.rstrip!
501
+ s << f.format(opt_desc)
502
+ else
503
+ f.first_indent = 2 * 4
504
+ s << f.format(opt_desc)
505
+ end
506
+ end
507
+ private :format_option
508
+
509
+ end#class OptionParser
510
+
511
+ end#module CommandLine