class OptionGrouper

A basic command line option parser with group support.

Constants

AMBIGOUS

@private Marker for ambigous arguments without group

VERSION

Attributes

result[R]

Hash of parsed results. Hash keys are the group names, values are another hashes, where key is the option name and value is the parsed value. @return [Hash{Symbol => Hash{Symbol => Object}}]

Public Class Methods

new(&blk) click to toggle source

Initialize an OptionGrouper. You can configure it using the yielded block.

# File lib/optiongrouper.rb, line 93
def initialize &blk
  @groups = {}
  @on_version = :exit
  @on_help = :exit
  @on_invalid_parameter = :exit
  @on_ambigous_parameter = :exit
  @on_not_argument = :exit

  run = Proc.new { show_help }
  group do
    header 'General options'
    opt :help, 'Show this message', :short => 'h', :on => run
  end

  invoke &blk if blk
end

Public Instance Methods

group(name = :default, &blk) click to toggle source

Define a new group, if needed, then invoke it. @param name [Symbol] name of the group @yield a configuration block @return [Group] the defined group

# File lib/optiongrouper.rb, line 171
def group name = :default, &blk
  @groups[name] ||= Group.new(name)
  @groups[name].invoke &blk
  @groups[name]
end
header(str) click to toggle source

Sets header printed when help is requested. @param str [#to_s] header message @return [void]

# File lib/optiongrouper.rb, line 163
def header str
  @header = str
end
invoke(&blk) click to toggle source

Yield the configurator block again.

# File lib/optiongrouper.rb, line 111
def invoke &blk
  Blockenspiel.invoke blk, self
end
on_ambigous_parameter(to) click to toggle source

What to do after an ambigous parameter. Takes same options as {#on_invalid_parameter}.

# File lib/optiongrouper.rb, line 139
def on_ambigous_parameter to
  @on_ambigous_parameter = to
end
on_help(res) click to toggle source

What to do after printing help. Takes same options as {#on_version}.

# File lib/optiongrouper.rb, line 127
def on_help res
  @on_help = res
end
on_invalid_parameter(to) click to toggle source

What to do after an invalid parameter. Takes the same options as {#on_version}, except you can also use ‘:raise` to raise an exception.

# File lib/optiongrouper.rb, line 133
def on_invalid_parameter to
  @on_invalid_parameter = to
end
on_not_argument(to) click to toggle source

What to do with a non-option string. Takes same options as {#on_invalid_parameter}.

# File lib/optiongrouper.rb, line 145
def on_not_argument to
  @on_not_argument = to
end
on_version(res) click to toggle source

What to do after printing version.

  • ‘:exit`: call exit to quit the program.

  • ‘:continue`: continue processing

  • ‘:stop`: stop processing

  • a Proc: call it

@param res [Symbol, Proc] do this. @return [void]

# File lib/optiongrouper.rb, line 122
def on_version res
  @on_version = res
end
parse(args = ARGV) click to toggle source

Parse command line arguments @param args [Array<String>] list of command line arguments. @return [Hash] {#result}

# File lib/optiongrouper.rb, line 180
def parse args = ARGV
  init_parse

  catch(:stop) do
    while a = args.shift
      if a == '--'
        break
      elsif a =~ /^--[^-]/
        handle_long args, a
      elsif a =~ /^-[^-]/
        handle_short args, a
      else
        @ignored << a
        on_error @on_not_argument, "`#{a}' is not an argument."
      end
    end
  end
  @result
ensure
  args.unshift *@ignored
end
stop() click to toggle source

Stop argument processing (call it in callbacks)

# File lib/optiongrouper.rb, line 209
def stop
  throw :stop
end
version(version) click to toggle source

Sets the version of the program @param version [#to_s] program name and version. @return [void]

# File lib/optiongrouper.rb, line 152
def version version
  @version = version
  run = Proc.new { show_version }
  group :default do
    opt :version, 'Show version', :on => run
  end
end

Private Instance Methods

ambigous_parameter(par, cand) click to toggle source
# File lib/optiongrouper.rb, line 349
def ambigous_parameter par, cand
  @ignored << par
  msg = "Ambigous parameter `#{par}'.\nCandidates:\n"
  msg << cand.map {|m| "  --#{m}" }.join("\n")
  on_error @on_ambigous_parameter, msg
end
handle_general(args, x, opt, value = nil) click to toggle source
# File lib/optiongrouper.rb, line 327
def handle_general args, x, opt, value = nil
  if opt[:value]
    if opt[:value].is_a? Array
      @result[x[0]][x[1]] = opt[:value].map do |v|
        z = parse_param(value || args.shift, v)
        value = nil
        z
      end
    else
      @result[x[0]][x[1]] = parse_param(value || args.shift, opt[:value])
    end
  else
    @result[x[0]][x[1]] = opt[:set]
  end
  opt[:on].call @result[x[0]][x[1]] if opt[:on]
end
handle_long(args, a) click to toggle source

Handle long parameters (‘–xx`)

# File lib/optiongrouper.rb, line 280
def handle_long args, a
  arg = a[2..-1].split '=', 2

  # Check first if we have group defined
  grparg = arg[0].split ':', 2
  if grparg.size == 2
    unless @group_names[grparg[0]]
      c = @group_names.select {|k,v| k.start_with? grparg[0] }
      return invalid_parameter a if c.size == 0
      return ambigous_parameter a, c.map {|k,v| "#{k}:..." } if c.size > 1
      grparg[0] = c.keys.first
    end
    find = "#{grparg[0]}:#{grparg[1]}"
    long = @long_grp
  else
    find = arg[0]
    long = @long_nogrp
  end

  x = long[find]
  unless x # handle abbreviated options
    c = long.select {|k,v| k.start_with? find }
    return invalid_parameter a if c.size == 0
    return ambigous_parameter a, c.keys if c.size > 1
    x = c.values.first
  end
  return ambigous_parameter a, x[1..-1].map {|m| "#{m}:#{arg[0]}"} if x[0] == AMBIGOUS
  opt = @groups[x[0]].opts[x[1]]

  handle_general args, x, opt, arg[1]
end
handle_short(args, arg, a = arg[1..-1]) click to toggle source

Handle short options

# File lib/optiongrouper.rb, line 313
def handle_short args, arg, a = arg[1..-1]
  x = @shorts[a[0]]
  return invalid_parameter "-#{a}" unless x
  opt = @groups[x[0]].opts[x[1]]

  # If it has a value, we take anything left in the argument as value
  if opt[:value] && a.size > 1
    handle_general args, x, opt, a[1..-1]
  else
    handle_general args, x, opt
    handle_short args, arg, a[1..-1] if a.size > 1
  end
end
init_parse() click to toggle source

Initialize parsing structures

# File lib/optiongrouper.rb, line 219
def init_parse
  # list of long arguments without group
  @long_nogrp = {}
  # long arguments in group:name format
  @long_grp = {}
  # short arguments
  @shorts = {}
  @result = {}
  @group_names = {}
  # list of ignored parameters
  @ignored = []

  @groups.each do |gname, grp|
    @group_names[grp.long] = gname

    @result[gname] = {}
    grp.opts.each do |oname, opt|
      long = opt[:long].to_s
      if @long_nogrp[long] # if this group is already defined
        unless @long_nogrp[long][0] == :default
          # only mark as ambigous if the other item is not in the default group
          ot = @long_nogrp[long][@long_nogrp[0] == AMBIGOUS ? 1..-1 : 0]
          @long_nogrp[long] = [AMBIGOUS, ot, gname].flatten
        end
      else
        @long_nogrp[long] = [gname, oname]
      end
      @long_grp["#{grp.long}:#{long}"] = [gname, oname]
      short_coll gname, oname if @shorts[opt[:short]]
      @shorts[opt[:short]] = [gname, oname] if opt[:short]

      @result[gname][oname] = opt[:default]
    end
  end
  # after gone through the list once, fill in unset short parameters
  # automatically
  @groups.each do |gname, grp|
    grp.opts.each do |oname, opt|
      next if opt[:short] || opt[:no_short]
      opt[:long].each_char do |c|
        next if c == "-"
        if !@shorts[c]
          @shorts[c] = [gname, oname]
          break
        elsif !@shorts[c.capitalize]
          @shorts[c.capitalize] = [gname, oname]
          break
        end
      end
    end
  end
end
invalid_parameter(par) click to toggle source
# File lib/optiongrouper.rb, line 344
def invalid_parameter par
  @ignored << par
  on_error @on_invalid_parameter, "Unknown argument `#{par}'."
end
on_error(on, msg) click to toggle source
# File lib/optiongrouper.rb, line 411
def on_error on, msg
  case on
  when :exit
    $stderr.puts msg
    $stderr.puts "\nRun with `--help' to get help."
    exit
  when :raise
    raise RuntimeError, msg
  when :continue
    # nop
  when :stop
    stop
  else
    on.call msg
  end
end
on_general(on) click to toggle source
# File lib/optiongrouper.rb, line 398
def on_general on
  case on
  when :exit
    exit
  when :continue
    # nop
  when :stop
    stop
  else
    on.call
  end
end
param_display(type) click to toggle source
# File lib/optiongrouper.rb, line 439
def param_display type
  str = if type.is_a? Array
          type[1]
        elsif type.is_a? Proc
          "PARAM"
        else
          type.to_s.upcase
        end
  " <#{str}>"
end
parse_param(param, type) click to toggle source
# File lib/optiongrouper.rb, line 428
def parse_param param, type
  type = type[0] if type.is_a? Array
  if type.is_a? Proc
    type.call param
  elsif Kernel.respond_to? type.to_s.capitalize
    Kernel.send type.to_s.capitalize, param
  else
    raise RuntimeError, "Unknown type #{type.inspect} specified"
  end
end
short_coll(g, o) click to toggle source

Called when two options try to set the same short argument.

# File lib/optiongrouper.rb, line 273
def short_coll g, o
  s = @groups[g].opts[o][:short]
  a = @shorts[s]
  raise RuntimeError, "`--#{a.join ':'}` and `--#{g}:#{o}' both tried to set short option `-#{s}'."
end
show_help() click to toggle source
# File lib/optiongrouper.rb, line 361
def show_help
  puts @version if @version
  puts @header if @header

  prompts = {}
  max = 1
  @groups.each do |gname, grp|
    prompts[gname] = {}
    grp.opts.each do |oname, opt|
      short = @shorts.key [gname, oname]
      msg = short ? " -#{short}, " : "     "
      if @long_nogrp[opt[:long]][0] != gname
        msg << "--#{grp.long}:#{opt[:long]}"
      else
        msg << "--#{opt[:long]}"
      end
      if opt[:value].is_a? Array
        opt[:value].each {|v| msg << param_display(v) }
      elsif opt[:value]
        msg << param_display(opt[:value])
      end
      prompts[gname][oname] = msg
      max = msg.size if msg.size > max
    end
  end

  @groups.each do |gname, grp|
    puts grp.header
    grp.opts.each do |oname, opt|
      printf "%#{max}s: %s\n", prompts[gname][oname], opt[:desc]
    end
    puts
  end

  on_general @on_help
end
show_version() click to toggle source
# File lib/optiongrouper.rb, line 356
def show_version
  puts @version
  on_general @on_version
end