Command-line interfaces with Ruby OptionParser
Updated Oct 16, 2020 ย โย 5ย min read
The Ruby standard library ships with the OptionParser class for parsing command-line options. Besides just parsing options it can take care of help messages and usage information too. This makes building command-line interfaces a breeze compared to using the ARGV constant.
To access the OptionParser class you need to require optparse
at the top of you Ruby file. Don't confuse it with a gem. It comes with Ruby, there is no need to install it separately.
require 'optparse'
You get started by creating an instance of the OptionParser class, pass it a block and call parse!
on it.
require 'optparse'
OptionParser.new do
puts 'I found a goldfish'
end.parse!
#=> $ ruby fish_finder.rb
#=> I found a goldfish
Okay, that was pretty useless, a single line of puts
would have had the same result. Lets start with adding an option to this fish finder CLI. We can store the option values in a hash by passing in into the parse method.
require 'optparse'
params = {}
OptionParser.new do |parser|
parser.on('-w', '--water TYPE')
parser.on('-h', '--help')
parser.on('-v', '--verbose')
end.parse!(into: params)
puts params
#=> $ ruby fish_finder.rb -v --water salt
#=> {:verbose=>true, :water=>"salt"}
You can note a couple things from the above example already:
- You can require options to have an argument by adding a argument name to the option specification. See the
--water TYPE
specification. If you want an optional argument you would use--water [TYPE]
in this case. - Options you don't pass to the script are not present in the
params
hash. Note the absence of:help
. - The long option name is used as a symbol in the
params
hash.
Now before we continue lets wrap this in a fancy FishFinder class.
require 'optparse'
class FishFinder
def fish
'I found a goldfish'
end
def parse
OptionParser.new do |parser|
parser.on('--water TYPE')
parser.on('-h', '--help')
parser.on('-v', '--verbose')
end.parse!
end
end
finder = FishFinder.new
finder.parse
puts finder.fish
Print usage information
The OptionParser class provides methods out of the box to help you create usage information messages. We do this by setting a banner for the parser, which is the first line in the usage output. With the seperator method you can add extra lines of text, or a blank line in this case. In the block that is passed for the --help
option you see that puts parser
is enough to print the usage information. We exit here as well so we don't search for a goldfish while we only want to see the usage information.
require 'optparse'
class FishFinder
def fish
'I found a goldfish'
end
def parse
OptionParser.new do |parser|
parser.banner = 'Usage: fish_finder.rb [options]'
parser.separator ''
parser.on('--water TYPE', 'Pick "fresh" or "salt"')
parser.on('-h', '--help', 'Show this message') do
puts parser
exit
end
parser.on('-v', '--verbose', 'Show fancy fish')
end.parse!
end
end
finder = FishFinder.new
finder.parse
puts finder.fish
#=> $ ruby fish_finder.rb --help
#=> Usage: fish_finder.rb [options]
#=> --water TYPE Pick "fresh" or "salt"
#=> -h, --help Show this message
#=> -v, --verbose Show extra fish
Boolean switch options
We can use the --verbose
flag to overwrite a default configuration. This way we can change the behavior of this fish finder by passing an option. If the --verbose
of -v
option is passed to the script we reward you with a fancy fish emoji.
require 'optparse'
class FishFinder
attr_accessor :verbose
def initialize
self.verbose = false
end
def fish
fish = self.verbose ? '๐ ' : 'goldfish'
"I found a #{fish}"
end
def parse
OptionParser.new do |parser|
parser.banner = "Usage: fish_finder.rb [options]"
parser.separator ""
parser.on('--water TYPE', 'Pick "fresh" or "salt"')
parser.on('-h', '--help', 'Show this message') do
puts parser
exit
end
parser.on('-v', '--verbose', 'Show fancy fish') do |verbose|
self.verbose = verbose
end
end.parse!
end
end
finder = FishFinder.new
finder.parse
puts finder.fish
#=> $ ruby fish_finder.rb
#=> I found a goldfish
#=> $ ruby fish_finder.rb --verbose
#=> I found a ๐
Options with required arguments
By adding an all caps name to the option definition you can define a required argument for an option. This means that an OptionParser::MissingArgument
exception is raised when the option is missing. You probably want to catch this and give a nice feedback message.
Now, a second issue might be that you receive an argument that you don't expect. In our case we allow for the water types "fresh" and "salt". We could use the build-in type coercion. So an OptionParser::InvalidArgument
exception in thrown when an invalid argument is passed.
parser.on('--water TYPE', String, 'Pick "fresh" or "salt"')
In the above example you see how that would look. We require the TYPE
argument to be a non-empty string. You can take a look at the docs for more type coercion options.
Since our argument values are rather specific we can check for a pattern too. See the final example for a check on salt or fresh water.
require 'optparse'
class FishFinder
attr_accessor :verbose, :filter
def initialize
self.verbose = false
end
def fish
fish = self.verbose ? '๐ ' : self.find_fish
"I found a #{fish}"
end
def find_fish
if self.filter == 'fresh'
'goldfish'
elsif self.filter == 'salt'
'tuna'
else
'sturgeon'
end
end
def parse
OptionParser.new do |parser|
parser.banner = "Usage: fish_finder.rb [options]"
parser.separator ""
parser.on('--water TYPE', /fresh|salt/, 'Pick "fresh" or "salt"') do |water|
self.filter = water
end
parser.on('-h', '--help', 'Show this message') do
puts parser
exit
end
parser.on('-v', '--verbose', 'Show extra fish') do |v|
self.verbose = v
end
end.parse!
end
end
finder = FishFinder.new
finder.parse
puts finder.fish
#=> $ ruby fish_finder.rb --water salt
#=> I found a tuna
#=> $ ruby fish_finder.rb --water fresh
#=> I found a goldfish
#=> $ ruby fish_finder.rb
#=> I found a sturgeon