Class OptionParser
In: optparse.rb
Parent: Object

OptionParser

Introduction

OptionParser is a class for command-line option analysis. It is much more advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented solution.

Features

  1. The argument specification and the code to handle it are written in the same place.
  2. It can output an option summary; you don’t need to maintain this string separately.
  3. Optional and mandatory arguments are specified very gracefully.
  4. Arguments can be automatically converted to a specified class.
  5. Arguments can be restricted to a certain set.

All of these features are demonstrated in the example below.

Example

The following example is a complete Ruby program. You can run it and see the effect of specifying various options. This is probably the best way to learn the features of optparse.

  require 'optparse'
  require 'optparse/time'
  require 'ostruct'
  require 'pp'

  class OptparseExample

    CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
    CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }

    #
    # Return a structure describing the options.
    #
    def self.parse(args)
      # The options specified on the command line will be collected in *options*.
      # We set default values here.
      options = OpenStruct.new
      options.library = []
      options.inplace = false
      options.encoding = "utf8"
      options.transfer_type = :auto
      options.verbose = false

      opts = OptionParser.new do |opts|
        opts.banner = "Usage: example.rb [options]"

        opts.separator ""
        opts.separator "Specific options:"

        # Mandatory argument.
        opts.on("-r", "--require LIBRARY",
                "Require the LIBRARY before executing your script") do |lib|
          options.library << lib
        end

        # Optional argument; multi-line description.
        opts.on("-i", "--inplace [EXTENSION]",
                "Edit ARGV files in place",
                "  (make backup if EXTENSION supplied)") do |ext|
          options.inplace = true
          options.extension = ext || ''
          options.extension.sub!(/\A\.?(?=.)/, ".")  # Ensure extension begins with dot.
        end

        # Cast 'delay' argument to a Float.
        opts.on("--delay N", Float, "Delay N seconds before executing") do |n|
          options.delay = n
        end

        # Cast 'time' argument to a Time object.
        opts.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
          options.time = time
        end

        # Cast to octal integer.
        opts.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger,
                "Specify record separator (default \\0)") do |rs|
          options.record_separator = rs
        end

        # List of arguments.
        opts.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
          options.list = list
        end

        # Keyword completion.  We are specifying a specific set of arguments (CODES
        # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
        # the shortest unambiguous text.
        code_list = (CODE_ALIASES.keys + CODES).join(',')
        opts.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
                "  (#{code_list})") do |encoding|
          options.encoding = encoding
        end

        # Optional argument with keyword completion.
        opts.on("--type [TYPE]", [:text, :binary, :auto],
                "Select transfer type (text, binary, auto)") do |t|
          options.transfer_type = t
        end

        # Boolean switch.
        opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
          options.verbose = v
        end

        opts.separator ""
        opts.separator "Common options:"

        # No argument, shows at tail.  This will print an options summary.
        # Try it and see!
        opts.on_tail("-h", "--help", "Show this message") do
          puts opts
          exit
        end

        # Another typical switch to print the version.
        opts.on_tail("--version", "Show version") do
          puts OptionParser::Version.join('.')
          exit
        end
      end

      opts.parse!(args)
      options
    end  # parse()

  end  # class OptparseExample

  options = OptparseExample.parse(ARGV)
  pp options

Note: some bugs were fixed between 1.8.0 and 1.8.1. If you experience trouble with the above code, keep this in mind.

Further documentation

The methods are not individually documented at this stage. The above example should be enough to learn how to use this class. If you have any questions, email me (gsinclair@soyabean.com.au) and I will update this document.

Methods

abort   accept   accept   add_officious   banner   base   def_head_option   def_option   def_tail_option   define   define_head   define_tail   each_const   environment   help   inc   inc   load   make_switch   new   new   on   on_head   on_tail   order   order!   parse   parse!   permute   permute!   program_name   reject   reject   release   remove   search_const   separator   show_version   summarize   terminate   terminate   to_a   to_s   top   top   ver   version   warn   with  

Classes and Modules

Module OptionParser::Acceptables
Module OptionParser::Arguable
Module OptionParser::Completion
Class OptionParser::AmbiguousArgument
Class OptionParser::AmbiguousOption
Class OptionParser::CompletingHash
Class OptionParser::InvalidArgument
Class OptionParser::InvalidOption
Class OptionParser::List
Class OptionParser::MissingArgument
Class OptionParser::NeedlessArgument
Class OptionParser::OptionCaseMap
Class OptionParser::OptionMap
Class OptionParser::ParseError
Class OptionParser::Switch

Constants

DecimalInteger = /\A[-+]?#{decimal}/io
OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))/io
DecimalNumeric = floatpat # decimal integer is allowed as float also.

External Aliases

banner= -> set_banner
  for experimental cascading :-)
program_name= -> set_program_name
summary_width= -> set_summary_width
summary_indent= -> set_summary_indent

Attributes

banner  [W] 
program_name  [W] 
release  [W] 
summary_indent  [RW] 
summary_width  [RW] 
version  [W] 

Public Class methods

[Source]

# File optparse.rb, line 863
  def self.accept(*args, &blk) top.accept(*args, &blk) end

[Source]

# File optparse/version.rb, line 49
  def each_const(path, klass = ::Object)
    path.split(/::|\//).inject(klass) do |klass, name|
      raise NameError, path unless Module === klass
      klass.constants.grep(/#{name}/i) do |c|
        klass.const_defined?(c) or next
        c = klass.const_get(c)
      end
    end
  end

[Source]

# File optparse.rb, line 789
  def self.inc(arg, default = nil)
    case arg
    when Integer
      arg.nonzero?
    when nil
      default.to_i + 1
    end
  end

[Source]

# File optparse.rb, line 814
  def initialize(banner = nil, width = 32, indent = ' ' * 4)
    @stack = [DefaultList, List.new, List.new]
    @program_name = nil
    @banner = banner
    @summary_width = width
    @summary_indent = indent
    add_officious
    yield self if block_given?
  end

[Source]

# File optparse.rb, line 874
  def self.reject(*args, &blk) top.reject(*args, &blk) end

[Source]

# File optparse/version.rb, line 59
  def search_const(klass, name)
    klasses = [klass]
    while klass = klasses.shift
      klass.constants.each do |cname|
        klass.const_defined?(cname) or next
        const = klass.const_get(cname)
        yield klass, cname, const if name === cname
        klasses << const if Module === const and const != ::Object
      end
    end
  end

[Source]

# File optparse/version.rb, line 4
  def show_version(*pkg)
    progname = ARGV.options.program_name
    result = false
    show = proc do |klass, cname, version|
      str = "#{progname}"
      unless klass == ::Object and cname == :VERSION
        version = version.join(".") if Array === version
        str << ": #{klass}" unless klass == Object
        str << " version #{version}"
      end
      [:Release, :RELEASE].find do |rel|
        if klass.const_defined?(rel)
          str << " (#{klass.const_get(rel)})"
        end
      end
      puts str
      result = true
    end
    if pkg.size == 1 and pkg[0] == "all"
      self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version|
        unless cname[1] == ?e and klass.const_defined?(:Version)
          show.call(klass, cname.intern, version)
        end
      end
    else
      pkg.each do |pkg|
        begin
          pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)}
          v = case
              when pkg.const_defined?(:Version)
                pkg.const_get(n = :Version)
              when pkg.const_defined?(:VERSION)
                pkg.const_get(n = :VERSION)
              else
                n = nil
                "unknown"
              end
          show.call(pkg, n, v)
        rescue NameError
        end
      end
    end
    result
  end

[Source]

# File optparse.rb, line 843
  def self.terminate(arg = nil)
    throw :terminate, arg
  end

[Source]

# File optparse.rb, line 848
  def self.top() DefaultList end

[Source]

# File optparse.rb, line 778
  def self.with(*args, &block)
    opts = new(*args)
    opts.instance_eval(&block)
    opts
  end

Public Instance methods

[Source]

# File optparse.rb, line 946
  def abort(mesg = $!)
    super("#{program_name}: #{mesg}")
  end

[Source]

# File optparse.rb, line 862
  def accept(*args, &blk) top.accept(*args, &blk) end

:nodoc:

[Source]

# File optparse.rb, line 825
  def add_officious
    list = base()
    Officious.each_pair do |opt, block|
      list.long[opt] ||= block.call(self)
    end
  end

[Source]

# File optparse.rb, line 899
  def banner
    @banner ||= "Usage: #{program_name} [options]"
  end

[Source]

# File optparse.rb, line 962
  def base
    @stack[1]
  end
def_head_option(*opts, &block)

Alias for define_head

def_option(*opts, &block)

Alias for define

def_tail_option(*opts, &block)

Alias for define_tail

[Source]

# File optparse.rb, line 1213
  def define(*opts, &block)
    top.append(*(sw = make_switch(*opts, &block)))
    sw[0]
  end

[Source]

# File optparse.rb, line 1223
  def define_head(*opts, &block)
    top.prepend(*(sw = make_switch(*opts, &block)))
    sw[0]
  end

[Source]

# File optparse.rb, line 1233
  def define_tail(*opts, &block)
    base.append(*(sw = make_switch(*opts, &block)))
    sw[0]
  end

[Source]

# File optparse.rb, line 1481
  def environment(env = File.basename($0, '.*'))
    env = ENV[env] || ENV[env.upcase] or return
    parse(*Shellwords.shellwords(env))
  end

[Source]

# File optparse.rb, line 1013
  def help; summarize(banner.to_s.sub(/\n?\z/, "\n")) end

[Source]

# File optparse.rb, line 797
  def inc(*args)
    self.class.inc(*args)
  end

[Source]

# File optparse.rb, line 1459
  def load(filename = nil)
    begin
      filename ||= File.expand_path(File.basename($0, '.*'), '~/.options')
    rescue
      return false
    end
    begin
      parse(*IO.readlines(filename).each {|s| s.chomp!})
      true
    rescue Errno::ENOENT, Errno::ENOTDIR
      false
    end
  end

[Source]

# File optparse.rb, line 1085
  def make_switch(*opts, &block)
    short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
    ldesc, sdesc, desc, arg = [], [], []
    default_style = Switch::NoArgument
    default_pattern = nil
    klass = nil
    o = nil
    n, q, a = nil

    opts.each do |o|
      # argument class

      next if search(:atype, o) do |pat, c|
        klass = notwice(o, klass, 'type')
        if not_style and not_style != Switch::NoArgument
          not_pattern, not_conv = pat, c
        else
          default_pattern, conv = pat, c
        end
      end

      # directly specified pattern(any object possible to match)

      if !(String === o) and o.respond_to?(:match)
        pattern = notwice(o, pattern, 'pattern')
        conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert))
        next
      end

      # anything others

      case o
      when Proc, Method
        block = notwice(o, block, 'block')
      when Array, Hash
        case pattern
        when CompletingHash
        when nil
          pattern = CompletingHash.new
          conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert))
        else
          raise ArgumentError, "argument pattern given twice"
        end
        o.each {|(o, *v)| pattern[o] = v.fetch(0) {o}}
      when Module
        raise ArgumentError, "unsupported argument type: #{o}"
      when *ArgumentStyle.keys
        style = notwice(ArgumentStyle[o], style, 'style')
      when /^--no-([^\[\]=\s]*)(.+)?/
        q, a = $1, $2
        o = notwice(a ? Object : TrueClass, klass, 'type')
        not_pattern, not_conv = search(:atype, o) unless not_style
        not_style = (not_style || default_style).guess(arg = a) if a
        default_style = Switch::NoArgument
        default_pattern, conv = search(:atype, FalseClass) unless default_pattern
        ldesc << "--no-#{q}"
        long << 'no-' + (q = q.downcase)
        nolong << q
      when /^--\[no-\]([^\[\]=\s]*)(.+)?/
        q, a = $1, $2
        o = notwice(a ? Object : TrueClass, klass, 'type')
        if a
          default_style = default_style.guess(arg = a)
          default_pattern, conv = search(:atype, o) unless default_pattern
        end
        ldesc << "--[no-]#{q}"
        long << (o = q.downcase)
        not_pattern, not_conv = search(:atype, FalseClass) unless not_style
        not_style = Switch::NoArgument
        nolong << 'no-' + o
      when /^--([^\[\]=\s]*)(.+)?/
        q, a = $1, $2
        if a
          o = notwice(NilClass, klass, 'type')
          default_style = default_style.guess(arg = a)
          default_pattern, conv = search(:atype, o) unless default_pattern
        end
        ldesc << "--#{q}"
        long << (o = q.downcase)
      when /^-(\[\^^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
        q, a = $1, $2
        o = notwice(Object, klass, 'type')
        if a
          default_style = default_style.guess(arg = a)
          default_pattern, conv = search(:atype, o) unless default_pattern
        end
        sdesc << "-#{q}"
        short << Regexp.new(q)
      when /^-(.)(.+)?/
        q, a = $1, $2
        if a
          o = notwice(NilClass, klass, 'type')
          default_style = default_style.guess(arg = a)
          default_pattern, conv = search(:atype, o) unless default_pattern
        end
        sdesc << "-#{q}"
        short << q
      when /^=/
        style = notwice(default_style.guess(arg = o), style, 'style')
        default_pattern, conv = search(:atype, Object) unless default_pattern
      else
        desc.push(o)
      end
    end

    default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
    s = if short.empty? and long.empty?
          raise ArgumentError, "no switch given" if style or pattern or block
          desc
        else
          (style || default_style).new(pattern || default_pattern,
                                       conv, sdesc, ldesc, arg, desc, block)
        end
    return s, short, long,
      (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
      nolong
  end

[Source]

# File optparse.rb, line 970
  def new
    @stack.push(List.new)
    if block_given?
      yield self
    else
      self
    end
  end

[Source]

# File optparse.rb, line 1217
  def on(*opts, &block)
    define(*opts, &block)
    self
  end

[Source]

# File optparse.rb, line 1227
  def on_head(*opts, &block)
    define_head(*opts, &block)
    self
  end

[Source]

# File optparse.rb, line 1237
  def on_tail(*opts, &block)
    define_tail(*opts, &block)
    self
  end

[Source]

# File optparse.rb, line 1265
  def order(*argv, &block)
    argv = argv[0].dup if argv.size == 1 and Array === argv[0]
    order!(argv, &block)
  end

[Source]

# File optparse.rb, line 1270
  def order!(argv = ARGV, &nonopt)
    opt, arg, sw, val, rest = nil
    nonopt ||= proc {|arg| throw :terminate, arg}
    argv.unshift(arg) if arg = catch(:terminate) {
      while arg = argv.shift
        case arg
        # long option

        when /\A--([^=]*)(?:=(.*))?/
          opt, rest = $1, $2
          begin
            sw, = complete(:long, opt)
          rescue ParseError
            raise $!.set_option(arg, true)
          end
          begin
            opt, sw, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
            sw.call(val) if sw
          rescue ParseError
            raise $!.set_option(arg, rest)
          end

        # short option

        when /\A-(.)((=).*|.+)?/
          opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2
          begin
            unless sw = search(:short, opt)
              begin
                sw, = complete(:short, opt)
                # short option matched.

                val = arg.sub(/\A-/, '')
                has_arg = true
              rescue InvalidOption
                # if no short options match, try completion with long

                # options.

                sw, = complete(:long, opt)
                eq ||= !rest
              end
            end
          rescue ParseError
            raise $!.set_option(arg, true)
          end
          begin
            opt, sw, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
            raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
            argv.unshift(opt) if opt and (opt = opt.sub(/\A-*/, '-')) != '-'
            sw.call(val) if sw
          rescue ParseError
            raise $!.set_option(arg, arg.length > 2)
          end

        # non-option argument

        else
          nonopt.call(arg)
        end
      end

      nil
    }

    argv
  end

[Source]

# File optparse.rb, line 1371
  def parse(*argv)
    argv = argv[0].dup if argv.size == 1 and Array === argv[0]
    parse!(argv)
  end

[Source]

# File optparse.rb, line 1376
  def parse!(argv = ARGV)
    if ENV.include?('POSIXLY_CORRECT')
      order!(argv)
    else
      permute!(argv)
    end
  end

[Source]

# File optparse.rb, line 1345
  def permute(*argv)
    argv = argv[0].dup if argv.size == 1 and Array === argv[0]
    permute!(argv)
  end

[Source]

# File optparse.rb, line 1350
  def permute!(argv = ARGV)
    nonopts = []
    arg = nil
    order!(argv) {|arg| nonopts << arg}
    argv[0, 0] = nonopts
    argv
  end

[Source]

# File optparse.rb, line 903
  def program_name
    @program_name || File.basename($0, '.*')
  end

[Source]

# File optparse.rb, line 873
  def reject(*args, &blk) top.reject(*args, &blk) end

[Source]

# File optparse.rb, line 930
  def release
    @release || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE)
  end

[Source]

# File optparse.rb, line 983
  def remove
    @stack.pop
  end

[Source]

# File optparse.rb, line 1243
  def separator(string)
    top.append(string, nil, nil)
  end

[Source]

# File optparse.rb, line 1003
  def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
    visit(:summarize, {}, {}, width, max, indent, &(blk || proc {|l| to << l + $/}))
    to
  end

[Source]

# File optparse.rb, line 840
  def terminate(arg = nil)
    self.class.terminate(arg)
  end

[Source]

# File optparse.rb, line 1020
  def to_a; summarize(banner.to_a.dup) end
to_s()

Alias for help

[Source]

# File optparse.rb, line 954
  def top
    @stack[-1]
  end

[Source]

# File optparse.rb, line 934
  def ver
    if v = version
      str = "#{program_name} #{[v].join('.')}"
      str << " (#{v})" if v = release
      str
    end
  end

[Source]

# File optparse.rb, line 926
  def version
    @version || (defined?(::Version) && ::Version)
  end

[Source]

# File optparse.rb, line 942
  def warn(mesg = $!)
    super("#{program_name}: #{mesg}")
  end

[Validate]