OptionParser (Class)

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

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 846
  def self.accept(*args, &blk) top.accept(*args, &blk) end

[Source]

# File optparse/version.rb, line 47
  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 781
  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 806
  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
    yield self if block_given?
  end

[Source]

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

[Source]

# File optparse/version.rb, line 57
  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
    show = proc do |klass, version|
      version = version.join(".") if Array === version
      str = "#{progname}"
      str << ": #{klass}" unless klass == Object
      str << " version #{version}"
      case
      when klass.const_defined?(:Release)
        str << " (#{klass.const_get(:Release)})"
      when klass.const_defined?(:RELEASE)
        str << " (#{klass.const_get(:Release)})"
      end
      puts str
    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, version)
        end
      end
    else
      pkg.each do |pkg|
        /\A[A-Z]\w*((::|\/)[A-Z]\w*)*\z/ni =~ pkg or next
        begin
          pkg = eval(pkg)
          v = case
              when pkg.const_defined?(:Version)
                pkg.const_get(:Version)
              when pkg.const_defined?(:VERSION)
                pkg.const_get(:VERSION)
              else
                "unknown"
              end
          show.call(pkg, v)
        rescue NameError
          puts "#{progname}: #$!"
        end
      end
    end
    exit
  end

[Source]

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

[Source]

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

[Source]

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

Public Instance methods

[Source]

# File optparse.rb, line 929
  def abort(mesg = $!)
    super(program_name + ': ' + mesg)
  end

[Source]

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

[Source]

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

[Source]

# File optparse.rb, line 945
  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 1196
  def define(*opts, &block)
    top.append(*(sw = make_switch(*opts, &block)))
    sw[0]
  end

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

# File optparse.rb, line 1442
  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 1068
  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 953
  def new
    @stack.push(List.new)
    if block_given?
      yield self
    else
      self
    end
  end

[Source]

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

[Source]

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

[Source]

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

[Source]

# File optparse.rb, line 1248
  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 1253
  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 1354
  def parse(*argv)
    argv = argv[0].dup if argv.size == 1 and Array === argv[0]
    parse!(argv)
  end

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

# File optparse.rb, line 986
  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 823
  def terminate(arg = nil)
    self.class.terminate(arg)
  end

[Source]

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

Alias for help

[Source]

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

[Source]

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

[Source]

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

[Source]

# File optparse.rb, line 925
  def warn(mesg = $!)
    super(program_name + ': ' + mesg)
  end

[Validate]