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}