OptionParser 0.5.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.
@@ -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