Optitron

Sensible options parsing for Ruby!

Optitron strives to be simple, minimal and to do the “right thing” most of the time. The structure is easy: you have global options, a list of commands, and each of those commands takes a number of arguments and options.

Usage

To create an Optitron command line interface (CLI), start with a class you want to wire up as a CLI. In our example, we'll use the class Runner, which we want to work as a CLI.

class Runner
  def start
    # ... starts
  end

  def stop
    # ... stops
  end

  def status
    # ... reports status
  end
end

To make this class suitable for use as a CLI, either extend Optitron::CLI or include Optitron::ClassDsl. Then, describe each argument as follows:

class Runner < Optitron::CLI
  desc "Starts the process"
  def start
    # ... starts
  end

  desc "Stops the process"
  def stop
    # ... stops
  end

  desc "Report the status"
  def status
    # ... reports status
  end
end

Help command

Help is added by default, but, if you don't want to use this, include the command dont_use_help in your class. Help is available from either --help or -?.

Arguments

Only methods described will be available to the CLI. If you have a method that needs arguments, simply include them as method arguments. It will respect splats and defaults. For instance

desc "Starts the process"
def start(mode)
  # ... starts
end

Will generate

start [mode]       # Starts the process

In your help file.

desc "Starts the process"
def start(mode = 'production')
  # ... starts
end

Will generate

start <mode="production">       # Starts the process

And

desc "Starts the process"
def start(*modes)
  # ... starts
end

Will generate

start <modes1 modes2 ...>       # Starts the process

As well, you can provide type hints to the args:

desc "Does something awesome"
arg_types :numeric, :string, :boolean
def start(number, string, truth)
  # ... does something
end

Will generate

start [number] [string] [truth]       # Does something awesome

And those arguments have to conform to the types given. For instance, calling this with

start 123 something flase

Will produce the error Truth is invalid. Calling with

start 123 something true

however, will correctly invoke the method.

These same arg type hints can be provided by ending your arguments in _type. The accepted types are: float, string, int, numeric, array, hash. For example

desc "Does something awesome"
def start(number_int, something_string, opt_boolean)
  # ... does something
end

Options

Options are specified with the opt method in your class, as follows:

class Runner < Optitron::CLI
  desc "Starts the process"
  opt "verbose"
  opt "environment", :in => ['development', 'production', 'staging', 'test']
  def start
    # ... starts
  end

  # .. more methods

end

If you need an option available for all methods, use class_opt to specify it.

class Runner < Optitron::CLI
  class_opt "verbose", "Be loud"

  # ... your methods
end

The last line in your runner has to be the class name and dispatch. This will parse ARGV and execute normal dispatching on it.

class Runner < Optitron::CLI
  # ... your methods
end
Runner.dispatch

Options can have defaults, types and inclusion checks. Here are all the options available on an opt:

:default

This allows you to specify a default value. The type of value is used to infer the type required for this option. This can be over-ridden with :type.

:short_name

This allows you to set a short name for the option, though, one will be assigned automatically from the short names available.

:run

This allows you to run an arbitrary block for an option. The proc will be called with the value, and the response object.

:in

This allows you to test for inclusion in a range or array (or anything that responds to #include? and #first). The first item in the object will be used to infer the type. This can be over-ridden with :type.

:required

This allows you to force an option to be required. False by default.

:type

This allows you to specify the type. Acceptable options are :numeric, :array, :hash, :string or :boolean.

With boolean type, you can additionally supply :use_no => true to be able to use –no-option style options.

Stand alone usage

You can create parsers and parse using them.

@parser = Optitron.new {
  help
  opt 'verbose', "Be very loud"
  cmd "install", "This installs things" do
    arg "file", "The file to install"
  end
  cmd "show", "This shows things" do
    arg "first", "The first thing to show"
    arg "second", "The second optional thing to show", :required => false
  end
  cmd "kill", "This kills things" do
    opt "pids", "A list of pids to kill", :type => :array
    opt "pid", "A pid to kill", :type => :numeric
    opt "names", "Some sort of hash", :type => :hash
  end
  cmd "join", "This joins things" do
    arg "thing", "Stuff to join", :type => :greedy
  end
}

To generate help, use the #help method.

@parser.help

Which returns,

Commands

show [first] <second>          # This shows things
install [file]                 # This installs things
kill                           # This kills things
  -p/--pids=[ARRAY]            # A list of pids to kill
  -P/--pid=[NUMERIC]           # A pid to kill
  -n/--names=[HASH]            # Some sort of hash
join [thing1 thing2 ...]       # This joins things

Global options

-v/--verbose                   # Be very loud

The parse method can parse a list of arguments. For example, @parser.parse(%w(-v install file)) gives back:

response = @parser.parse(%w(-v install file))
response.command
=> "install"
response.args
=> ["file"]
response.params
=> {"verbose" => true}

If you try parsing invalid parameters, you can get back friendly error messages using #error_messages.

@parser.parse(%w()).error_messages
=> ["Unknown command"]
@parser.parse(%w(something)).error_messages
=> ["Something is an unknown command"]
@parser.parse(%w(install)).error_messages
=> ["File is required"]
@parser.parse(%w(kill --pid=something)).error_messages
=> ["Pid is invalid"]

The response from #parse can also be used to dispatch. All that's required is the target object be able to respond to params=.

For example

class Runner
  attr_accessor :params
  def install(file)
    puts "I'm installing #{file} with #{params.inspect}"
  end
end

parser = Optitron.new {
  help
  opt 'verbose', "Be very loud"
  cmd "install", "This installs things" do
    arg "file", "The file to install"
  end
}

parser.parse(%w(install this_file -v)).dispatch(Runner.new)

Will output

I'm installing this_file with {"help"=>false, "verbose"=>true}