Parent

CommandLine::OptionParser

Constants

DEFAULT_BODY_INDENT
DEFAULT_CONSOLE_WIDTH
GET_ARGS
GET_ARG_ARRAY
MIN_CONSOLE_WIDTH
OPT_NOT_FOUND_BUT_REQUIRED

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

Attributes

body_indent[RW]
columns[RW]
options[R]
posix[R]
tag_paragraph[RW]
unknown_options_action[R]

Public Class Methods

new(*opts_and_props) click to toggle source
# 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

Public Instance Methods

<<(option) click to toggle source

Add an option

# File lib/commandline/optionparser/optionparser.rb, line 160
def <<(option)
  @options << option
  add_names(option)
  self
end
add_names(*options) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_re() click to toggle source
# 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) click to toggle source

Parse the command line

# 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) click to toggle source

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"]]
# 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) click to toggle source
# 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") click to toggle source
# 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_str() click to toggle source
# File lib/commandline/optionparser/optionparser.rb, line 450
def to_str
  to_s
end
validate_parse_options(h) click to toggle source
# 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

Private Instance Methods

format_option(names, opt_desc, arg_desc) click to toggle source
# File lib/commandline/optionparser/optionparser.rb, line 489
def format_option(names, opt_desc, arg_desc)
  # TODO: Clean up the magic numbers

  f = Text::Format.new
  f.columns      = @columns
  f.first_indent = 4
  f.body_indent  = 8
  f.tabstop      = 4
  s = ""
  s << f.format("#{names.join(",")} #{arg_desc}")
  #if 7 == s.last.size
  if 7 == s.size
    f.first_indent = f.first_indent - 2
    s.rstrip!
    s << f.format(opt_desc)
  #elsif 8 == s.last.size
  elsif 8 == s.size
    f.first_indent = f.first_indent - 3
    s.rstrip!
    s << f.format(opt_desc)
  else
    f.first_indent = 2 * f.first_indent
    s << f.format(opt_desc)
  end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.