--noooooooooooooooooo
Apr 28, 2018
Ruby’s OptionParser surprised me the other day.
Say you want to make a CLI in Ruby. Ruby comes with an OptionParser and so you
think “hey, this might be handy for parsing CLI arguments.”
# pizza.rb
require 'optparse'
class Options
attr_accessor :sauce
def initialize
@sauce = 'marinara'
end
end
options = Options.new
OptionParser.new do |parser|
parser.on '--sauce=SAUCE' do |sauce|
options.sauce = sauce
end
end.parse!
puts "sauce: #{options.sauce}"
Let’s test it out.
$ ruby pizza.rb --sauce=pesto
sauce: pesto
$ ruby pizza.rb
sauce: marinara
OptionParser only calls the block passed to on if the flag is in the ARGV.
It calls the block with the value the user passes to the flag.
OptionParser.new do |parser|
parser.on '--use-marinara' do |use_marinara|
puts "Use marinara sauce? so #{use_marinara}"
end
end.parse!
$ ruby pizza.rb --use-marinara
Use marinara sauce? so true
$ ruby pizza.rb
$
OptionParser makes a boolean switch out of long arguments with no declared
value. It calls the on block with the boolean if the switch is in the ARGV.
I don’t think adding the word “no” to flags is a good idea. But what happens when we do?
OptionParser.new do |parser|
parser.on '--no-onions' do |no_onions|
puts "NO ONIONS???: EXTREMELY #{no_onions.to_s.upcase}--<<<"
end
end.parse!
$ ruby pizza.rb --no-onions
NO ONIONS???: EXTREMELY FALSE--<<<
OptionParser negates the boolean! Didn’t expect that.
OK, what if I just hack it by declaring it as a flag with a value?
OptionParser.new do |parser|
parser.on '--no-sauce=NO_SAUCE' do |no_sauce|
puts "--no-sauce=#{no_sauce}"
end
end.parse!
$ ruby pizza.rb --no-sauce=1
/poop.rb:25:in `<main>': needless argument: --no-sauce=1 (OptionParser::NeedlessArgument)
Hm. Maybe OptionParser still thinks that it’s a switch.
$ ruby pizza.rb --no-sauce
--no-sauce=false
Yep.
Let’s take a look at the OptionParser docs.
It shows a minimal example:
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
Aha! --[no-]verbose. Declaring an optional [no-] allows both positive and
negative versions of the switch.
OptionParser.new do |parser|
parser.on '--[no-]onions' do |onions|
puts "onions? #{onions}"
end
end.parse!
$ ruby pizza.rb --onions
onions? true
$ ruby pizza.rb --no-onions
onions? false
OK, the boolean negation makes sense here. OptionParser eases the burden of
writing two switches if you want to have a negative version of some positive
option. For example, it helps us avoid writing something like:
OptionParser.new do |parser|
parser.on '--onions' do |onions|
puts "onions? #{onions}"
end
parser.on '--no-onions' do |onions|
puts "onions? #{onions}"
end
end.parse!
Why does declaring the non-optional --no-onions behave like the optional
--[no-]onions? I don’t know, but I imagine that the optparse authors wanted
to keep behavior consistent between optional and non-optional no-s.
(This has been the behavior in OptionParser since at least
2002.)