commandline2 0.7.2

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