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:



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
# File 'lib/commandline/optionparser/optionparser.rb', line 59

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
  }

  add_names(@options)

  yield self if block_given?
end

Instance Attribute Details

#body_indentObject

Returns the value of attribute body_indent.



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

def body_indent
  @body_indent
end

#columnsObject

Returns the value of attribute columns.



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

def columns
  @columns
end

#optionsObject (readonly)

Returns the value of attribute options.



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

def options
  @options
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.



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

def tag_paragraph
  @tag_paragraph
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



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

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

#add_names(*options) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/commandline/optionparser/optionparser.rb', line 172

def add_names(*options)
  options.flatten.each { |option|
raise "Wrong data type '#{option.name}." unless Option === 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

#add_option(*h) ⇒ Object



166
167
168
169
170
# File 'lib/commandline/optionparser/optionparser.rb', line 166

def add_option(*h)
  opt = Option.new(*h)
  @options << opt
  add_names(opt)
end

#get_opt_args(opt, user_option, _args) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/commandline/optionparser/optionparser.rb', line 275

def get_opt_args(opt, user_option, _args)
  min, max = *opt.arity
  size     = _args.size

  if (min == max && max > 0 && size < max) || (size < min)
    raise(MissingRequiredOptionArgumentError,
      "Insufficient arguments #{_args.inspect}for option '#{user_option}' "+
      "with :arity #{opt.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



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/commandline/optionparser/optionparser.rb', line 293

def get_posix_re
  flags  = []
  nflags = []
  @options.each { |o| 
    if [0,0] == o.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



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
269
270
271
272
273
# File 'lib/commandline/optionparser/optionparser.rb', line 202

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]

      opt_args = get_opt_args(opt, user_option, _args)
      opts[opt_key] = 
        if Proc === opt.opt_found
          # Take the arguments depending upon arity
          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}'"+
        "#{$DEBUG ? ' in ' + @opt_lookup_by_any_name.keys.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"]]


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
444
445
446
447
448
# File 'lib/commandline/optionparser/optionparser.rb', line 391

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



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
374
375
376
377
378
# File 'lib/commandline/optionparser/optionparser.rb', line 311

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



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
481
482
483
484
485
486
487
# File 'lib/commandline/optionparser/optionparser.rb', line 454

def to_s(sep="\n")
  return "" if @options.empty?

  require '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



450
451
452
# File 'lib/commandline/optionparser/optionparser.rb', line 450

def to_str
  to_s
end

#validate_parse_options(h) ⇒ Object



184
185
186
187
188
189
190
191
192
193
# File 'lib/commandline/optionparser/optionparser.rb', line 184

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