Class: CommandLine::OptionParser

Inherits:
Object
  • Object
show all
Defined in:
lib/commandline/optionparser/optionparser.rb

Defined Under Namespace

Classes: DuplicateOptionNameError, MissingRequiredOptionArgumentError, MissingRequiredOptionError, OptionParserError, PosixMismatchError, UnknownOptionError, UnknownPropertyError

Constant Summary collapse

DEFAULT_CONSOLE_WIDTH =
70
MIN_CONSOLE_WIDTH =
10
DEFAULT_BODY_INDENT =
4
OPT_NOT_FOUND_BUT_REQUIRED =

These helper lambdas are here because OptionParser is the object that calls them and hence knows the parameter order.

lambda { |opt|  
  raise(MissingRequiredOptionError, 
  "Missing required parameter '#{opt.names[0]}'.") 
}
GET_ARG_ARRAY =
lambda { |opt, user_opt, args| args }
GET_ARGS =
lambda { |opt, user_opt, args| 
  return true if args.empty?
  return args[0] if 1 == args.size
  args
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*opts_and_props) {|_self| ... } ⇒ OptionParser

Returns a new instance of OptionParser.

Yields:

  • (_self)

Yield Parameters:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/commandline/optionparser/optionparser.rb', line 58

def initialize(*opts_and_props)
  @posix = false
  @unknown_options_action = :raise
  @unknown_options         = []
  @opt_lookup_by_any_name  = {}
  @command_options         = nil

  #
  # Formatting defaults
  #
  console_width = ENV["COLUMNS"]
  @columns = 
    if console_width.nil?
      DEFAULT_CONSOLE_WIDTH
    elsif console_width < MIN_CONSOLE_WIDTH
      console_width
    else
      console_width - DEFAULT_BODY_INDENT
    end
  @body_indent   = DEFAULT_BODY_INDENT
  @tag_paragraph = false
  @order         = :index  # | :alpha

  props = []
  keys  = {}
  opts_and_props.flatten!
  opts_and_props.delete_if { |op| 
    if Symbol === op
      props << op; true
    elsif Hash === op
      keys.update(op); true
    else
      false
    end
  }

  props.each { |p|
    case p
    when :posix then @posix = true
    else
      raise(UnknownPropertyError, "Unknown property '#{p.inspect}'.")
    end
  }

  keys.each { |k,v|
    case k
    when :unknown_options_action
      if [:collect, :ignore, :raise].include?(v)
        @unknown_options_action = v
      else
        raise(UnknownPropertyError, "Unknown value '#{v}' for "+
              ":unknown_options property.")
      end
    when :command_options
      @command_options = v
      @commands = v.keys
    else
      raise(UnknownPropertyError, "Unknown property '#{k.inspect}'.")
    end
  }
  # :unknown_options => :collect
  # :unknown_options => :ignore
  # :unknown_options => :raise

  opts = opts_and_props

  @options = []
  opts.each { |opt|
    # If user wants to parse posix, then ensure all options are posix
    raise(PosixMismatchError, 
      "Posix types do not match. #{opt.inspect}") if @posix && !opt.posix
    @options << opt
  }

#p "options-"*5
#p @options
  add_names(@options)

  yield self if block_given?
end

Instance Attribute Details

#body_indentObject

Returns the value of attribute body_indent.



21
22
23
# File 'lib/commandline/optionparser/optionparser.rb', line 21

def body_indent
  @body_indent
end

#columnsObject

Returns the value of attribute columns.



21
22
23
# File 'lib/commandline/optionparser/optionparser.rb', line 21

def columns
  @columns
end

#posixObject (readonly)

Returns the value of attribute posix.



19
20
21
# File 'lib/commandline/optionparser/optionparser.rb', line 19

def posix
  @posix
end

#tag_paragraphObject

Returns the value of attribute tag_paragraph.



21
22
23
# File 'lib/commandline/optionparser/optionparser.rb', line 21

def tag_paragraph
  @tag_paragraph
end

#unknown_optionsObject (readonly)

Returns the value of attribute unknown_options.



19
20
21
# File 'lib/commandline/optionparser/optionparser.rb', line 19

def unknown_options
  @unknown_options
end

#unknown_options_actionObject (readonly)

Returns the value of attribute unknown_options_action.



19
20
21
# File 'lib/commandline/optionparser/optionparser.rb', line 19

def unknown_options_action
  @unknown_options_action
end

Instance Method Details

#<<(option) ⇒ Object

Add an option



161
162
163
164
165
# File 'lib/commandline/optionparser/optionparser.rb', line 161

def <<(option)
  @options << option
  add_names(option)
  self
end

#add_names(*options) ⇒ Object



167
168
169
170
171
172
173
174
175
176
# File 'lib/commandline/optionparser/optionparser.rb', line 167

def add_names(*options)
  options.flatten.each { |option|
    option.names.each { |name|
      raise(DuplicateOptionNameError,
        "Duplicate option name '#{name}'.") if 
          @opt_lookup_by_any_name.has_key?(name)
      @opt_lookup_by_any_name[name] = option
    }
  }
end

#get_opt_args(opt, user_option, args) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/commandline/optionparser/optionparser.rb', line 270

def get_opt_args(opt, user_option, args)
  min, max = *opt.arg_arity
  size     = args.size

  if (min == max && max > 0 && size < max) || (size < min)
    raise(MissingRequiredOptionArgumentError,
      "Insufficient arguments #{args.inspect}for option '#{user_option}' "+
      "with :arg_arity #{opt.arg_arity.inspect}")
  end

  if 0 == min && 0 == max
    []
  else
    max = size if -1 == max
    args.slice!(0..[min, [max, size].min].max - 1)
  end
end

#get_posix_reObject



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/commandline/optionparser/optionparser.rb', line 288

def get_posix_re
  flags  = []
  nflags = []
  @options.each { |o| 
    if [0,0] == o.arg_arity 
      flags << o.names[0][1..1] 
    else
      nflags << o.names[0][1..1]
    end
  }
  flags  = flags.join
  flags  = flags.empty? ? "" : "[#{flags}\]+"
  nflags = nflags.join
  nflags = nflags.empty? ? "" : "[#{nflags}\]"
  Regexp.new("^-(#{flags})(#{nflags})(.*)\$")
end

#parse(argv = ARGV) ⇒ Object

Parse the command line



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/commandline/optionparser/optionparser.rb', line 196

def parse(argv=ARGV)
  argv = [argv] unless Array === argv

  #
  # Holds the results of each option. The key used is 
  # the first in the :names Array.
  #
  opts = Hash.new( :not_found )

  #
  # A command is the first non-option free argument on the command line.
  # This is a user selection and is the first argument in args.
  #  cmd = args.shift
  # Example:
  #  cvs -v cmd --cmd-option arg
  #
  cmd = nil
  cmd_options = {}

  #
  # #parse_argv yields an array containing the option and its arguments.
  #   [opts, array_args]
  # How do we collect all the arguments when OptionParser deal with an 
  # empty option list
  #
  parse_argv(argv) { |optarg|
    user_option  = optarg[0]
    args         = optarg[1]

    m = nil
    if @opt_lookup_by_any_name.has_key?(user_option) ||
       1 == (m = @opt_lookup_by_any_name.keys.grep(/^#{user_option}/)).size
      user_option = m[0] if m
      opt         = @opt_lookup_by_any_name[user_option]
      opt_key     = opt.names[0]

      opts[opt_key] = 
        if Proc === opt.opt_found
          # Take the arguments depending upon arity
          opt_args = get_opt_args(opt, user_option, args)
          opt.opt_found.call(opt, user_option, opt_args)
        else
          opt.opt_found
        end
        # Collect any remaining args
        @args += args

    elsif :collect == @unknown_options_action
      @unknown_options << user_option
    elsif :ignore == @unknown_options_action
    else
      raise(UnknownOptionError, "Unknown option '#{user_option}' in "+
        "#{@opt_lookup_by_any_name.inspect}.") 
    end
  }

  #
  # Call :not_found for all the options not on the command line.
  #
  @options.each { |opt|
    name = opt.names[0]
    if :not_found == opts[name]
      opts[name] = 
      if Proc === opt.opt_not_found
        opt.opt_not_found.call(opt)
      else
        opt.opt_not_found
      end
    end
  }

  OptionData.new(argv, opts, @unknown_options, @args, @not_parsed, cmd)
end

#parse_argv(argv, &block) ⇒ Object

Seperates options from arguments Does not look for valid options ( or should it? )

%w(-fred file1 file2)    =>    ["-fred", ["file1", "file2"]]
%w(--fred -t -h xyz)     =>    ["--fred", []]   ["-t", []]   ["-h", ["xyz"]]
%w(-f=file)              =>    ["-f", ["file"]]
%w(--file=fred)          =>    ["--file", ["fred"]]
%w(-file=fred)           =>    ["-file", ["fred"]]
['-file="fred1 fred2"']  =>    ["-file", ["fred1", "fred2"]]


386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/commandline/optionparser/optionparser.rb', line 386

def parse_argv(argv, &block)
  return parse_posix_argv(argv, &block) if @posix

  @not_parsed = []
  tagged      = []
  argv.each_with_index { |e,i|
    if "--" == e
      @not_parsed = argv[(i+1)..(argv.size+1)]
      break
    elsif "-" == e
      tagged << [:arg, e] 
    elsif ?- == e[0]  
      m = Option::GENERAL_OPT_EQ_ARG_RE.match(e)
      if m.nil?
        tagged << [:opt, e] 
      else
        tagged << [:opt, m[1]]
        tagged << [:arg, m[2]]
      end
    else
      tagged << [:arg, e]
    end
  }

  #
  # The tagged array has the form:
  #   [
  #    [:opt, "-a"], [:arg, "filea"], 
  #    [:opt, "-b"], [:arg, "fileb"], 
  #    #[:not_parsed, ["-z", "-y", "file", "file2", "-a", "-b"]]
  #   ]

  #
  # Now, combine any adjacent args such that
  #   [[:arg, "arg1"], [:arg, "arg2"]]
  # becomes
  #   [[:args, ["arg1", "arg2"]]]
  # and the final result should be
  #   [ "--file", ["arg1", "arg2"]]
  #

  parsed = []
  @args  = []
  tagged.each { |e|
    if :opt == e[0]
      parsed << [e[1], []]
    elsif :arg == e[0]
      if Array === parsed[-1] 
        parsed[-1][-1] += [e[1]]
      else
        @args << e[1]
      end
    else
      raise "How did we get here?"
    end
  }
  parsed.each { |e| block.call(e) }
end

#parse_posix_argv(argv) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/commandline/optionparser/optionparser.rb', line 306

def parse_posix_argv(argv)
  re = @posix ? get_posix_re : Option::GENERAL_OPT_EQ_ARG_RE
  p re if $DEBUG
  tagged = []

  #
  # A Posix command line must have all the options precede
  # non option arguments. For example
  # :names => -h -e -l -p -s
  # where -p can take an argument
  # Command line can read:
  #   -helps  => -h -e -l -p s
  #   -p fred non-opt-arg
  #   -p fred non-opt-arg -h   # not ok
  #   -he -popt-arg1 -popt-arg2 non-opt-arg
  #   -p=fred  # this is not legal?
  #   -pfred  === -p fred
  #

  #"-helps" "-pfred" "-p" "fred"
  #-h -e -l -p [s] -p [fred] -p [fred]
  #[-h, []], [-e []], [-l, []], [-p, [s]], -p

  argv.each { |e| 
    m = re.match(e)
    if m.nil?
      tagged << [:arg, e]
    else
      raise "houston, we have a problem" if m.nil?
      unless m[1].empty?
        m[1].split(//).each { |e| tagged << [:opt, "-#{e}"] }
      end

      unless m[2].empty?
        tagged << [:opt, "-#{m[2]}"]
        tagged << [:arg, m[3]] unless m[3].empty?
      end
    end
  }

if $DEBUG
print "Tagged:" 
p tagged
end
  #
  # Now, combine any adjacent args such that
  #   [[:arg, "arg1"], [:arg, "arg2"]]
  # becomes
  #   [[:args, ["arg1", "arg2"]]]
  # and the final result should be
  #   [ "--file", ["arg1", "arg2"]]
  #

  parsed = []
  @args  = []
  tagged.each { |e|
    if :opt == e[0]
      parsed << [e[1], []]
    else
      if Array === parsed[-1] 
        parsed[-1][-1] += [e[1]]
      else
        @args << e[1]
      end
    end
  }
  parsed.each { |e| yield e }
end

#to_s(sep = "\n") ⇒ Object



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/commandline/optionparser/optionparser.rb', line 449

def to_s(sep="\n")
  require 'commandline/text/format'
  @f = Text::Format.new
  @f.columns = @columns
  @f.first_indent  = 4
  @f.body_indent   = 8
  @f.tag_paragraph = false

  header = ["OPTIONS\n"]
  s = []
  @options.each { |opt|
    opt_str = []
    if block_given?
      result = yield(opt.names, opt.opt_description, opt.arg_description) 
      if result.kind_of?(String)
        opt_str << result unless result.empty?
      elsif result.nil?
        opt_str << format_option(opt.names, opt.opt_description, opt.arg_description) 
      elsif result.kind_of?(Array) && 3 == result.size
        opt_str << format_option(*result)
      else
        raise "Invalid return value #{result.inspect} from yield block "+
              "attached to #to_s."
      end
    else
      opt_str << format_option(opt.names, opt.opt_description, opt.arg_description)
    end
    s << opt_str.join unless opt_str.empty?
  }
  #s.collect! { |i| i.kind_of?(Array) && /\n+/ =~ i[0] ? i.join : f.paragraphs(i) }
  [header, s].flatten.join(sep)
end

#to_strObject



445
446
447
# File 'lib/commandline/optionparser/optionparser.rb', line 445

def to_str
  to_s
end

#validate_parse_options(h) ⇒ Object



178
179
180
181
182
183
184
185
186
187
# File 'lib/commandline/optionparser/optionparser.rb', line 178

def validate_parse_options(h)
  h[:names].each { |name| check_option_name(name) }

  #if @posix
  #  all are single-dash:single-char OR double-dash:multi-char
  #else if unix compliant
  #  single-dash only
  #else any - does not support combination - try to on single/single
  #end
end