commandline2 0.7.2

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