How do you specify a required switch (not argument) with Ruby OptionParser?
I am assuming you are using optparse here, although the same technique will work for other option parsing libraries.
The simplest method is probably to parse the parameters using your chosen option parsing library and then raise an OptionParser::MissingArgument Exception if the value of host is nil.
The following code illustrates
#!/usr/bin/env ruby
require 'optparse'
options = {}
optparse = OptionParser.new do |opts|
opts.on('-h', '--host HOSTNAME', "Mandatory Host Name") do |f|
options[:host] = f
end
end
optparse.parse!
#Now raise an exception if we have not found a host option
raise OptionParser::MissingArgument if options[:host].nil?
puts "Host = #{options[:host]}"
Running this example with a command line of
./program -h somehost
simple displays "Host = somehost"
Whilst running with a missing -h and no file name produces the following output
./program:15: missing argument: (OptionParser::MissingArgument)
And running with a command line of ./program -h produces
/usr/lib/ruby/1.8/optparse.rb:451:in `parse': missing argument: -h (OptionParser::MissingArgument)
from /usr/lib/ruby/1.8/optparse.rb:1288:in `parse_in_order'
from /usr/lib/ruby/1.8/optparse.rb:1247:in `catch'
from /usr/lib/ruby/1.8/optparse.rb:1247:in `parse_in_order'
from /usr/lib/ruby/1.8/optparse.rb:1241:in `order!'
from /usr/lib/ruby/1.8/optparse.rb:1332:in `permute!'
from /usr/lib/ruby/1.8/optparse.rb:1353:in `parse!'
from ./program:13
How to generate OptionParser require arguments
There's a similar question with an answer that may help you:
"How do you specify a required switch (not argument) with Ruby OptionParser?"
In short: there doesn't seem to be a way to make an option required (they are called options after all).
There is an OptionParser::MissingArgument
exception that you could raise rather than the ArgumentError
you're currently throwing.
OptionParser's make_switch error with '-?'
Perhaps a quick look at the (somewhat confusing) documentation would shed some light on the situation. If you look at the docs, you'll end up at OptionParser#make_switch
where you'll find an explanation of what the opt.on
arguments look like:
Long style switch:
Specifies a long style switch which takes a mandatory, optional or no argument. It’s a string of the following form:"--switch=MANDATORY" or "--switch MANDATORY"
"--switch[=OPTIONAL]"
"--switch"
Short style switch:
Specifies short style switch which takes a mandatory, optional or no argument. It’s a string of the following form:"-xMANDATORY"
"-x[OPTIONAL]"
"-x"
Note the -xMANDATORY
and then look closer at your @opts.on
call:
@opts.on( '-?', '-help','Show this message') do
# ---------------^^^^^
That -help
defines a -h
option with a required elp
argument. Presumably the option parser is interpreting that to mean that -h
is an alias for -?
and since -h
is defined with a required argument, -?
also requires an argument. If you use --help
(i.e. a long style switch) then you'll probably have a better time:
@opts.on('-?', '--help', 'Show this message') do
I working from the Ruby 2.0 version but I doubt much has changed in the option parser since the older version of Ruby that you appear to be using.
How to parse an argument without a name with Ruby's optparse
First parse!
with optparse, then scan the ARGV and raise if ARGV is empty. Like so:
op.parse!
filename = ARGV.pop
raise "Need to specify a file to process" unless filename
The mandatory filename will not be processed by the OptionParser
and will be left for you in ARGV - if it's not there, just raise manually.
How to DRYly subclass (or otherwise share code with) rubys OptionParser to e.g. share options?
I haven't used OptionParser so there may be a better way to do this, but I'll take a stab at it anyway.
The most important thing about OptionParser#initialize
(for our purposes) is that it yields self
to the block given. To make a subclass that works the same, all we have to do is make its initialize
method yield self
, too:
require 'optparse'
require 'ostruct'
module MyGem
class OptionParser < ::OptionParser
attr_reader :options
def initialize(*args)
@options = OpenStruct.new
super *args
default_options!
yield(self, options) if block_given?
end
private
def default_options!
on '--whatever=WHATEVER' do |w|
options.whatever = w
end
end
end
end
This calls super
with all of the passed arguments except the passed block (if given). Then it calls default_options!
to create those default options (this could have done by passing a block to super
, but I find the above much cleaner).
Finally, it yields to the given block just as the superclass did, but it passes a second argument, the options object. The user can then use it like this:
require 'my_gem/option_parser'
opts = MyGem::OptionParser.new do |parser, options|
parser.on '--and-another=ANOTHER' do |a|
options.another = a
end
end
opts.parse!
p opts.options
This would give the user results like the following:
$ ruby script.rb --whatever=www --and-another=aaa
#<OpenStruct whatever="www", another="aaa">
As an alternative to yield(self, options)
, we could use yield self
, but then the user would need to do e.g. parser.options.whatever = ...
inside the block.
Another alternative would be to add a &block
argument to initialize
and then do instance_eval(&block)
instead of yield
. This would evaluate the block in the instance context, so the user could access the options
attribute (and all other instance methods, etc.) directly, e.g.:
parser = MyGem::OptionParser.new do
on '--and-another=ANOTHER' do |a|
options.another = a
end
end
parser.parse!
That has the downside, however, that the user must know that the block will be evaluated in the instance context. Personally I prefer the explicit yield(self, options)
.
Ruby Option Parser. Is there more concision possible for this code?
The trollop gem might be of interest to you. It has long/short options, default values, required flags, etc. Use of it is not unlike what you have right now.
Ruby OptionParser not parsing -- commands properly
When you call parse
manually, you need to pass in the ARGV
, which is not the string of everything after the script name, but the split array:
./example.rb -f s # => ["-f", "s"]
./example.rb --format s # => ["--format", "s"]
./example.rb --format=s # => ["--format=s"]
So, if we pass those formats to parse we get the options correctly parsed:
op.parse(['-f', 'a']) # => {"format"=>"a"}
op.parse(['--format', 'b']) # => {"format"=>"b"}
op.parse(['--format=c']) # => {"format"=>"c"}
Related Topics
How to Do Basic Authentication with Restclient
In-Place Progress Output in the Terminal or Console
What Does :: (Double Colon) Mean in Ruby
Error Installing Nokogiri 1.6.0 on MAC (Libxml2)
Creating a Thread-Safe Temporary File Name
Ruby: What's the Difference Between Stdin.Gets() and Gets.Chomp()
How to Determine the Md5 Digest of a Given Asset in the Rails Asset Pipeline
Why Bundle Install Is Installing Gems in Vendor/Bundle
Are the Date, Time, and Datetime Classes Necessary
Rspec: How to Stub an Instance Method Called by Constructor
Rails Link_To External Site, Url Is Attribute of User Table, Like: @Users.Website
Options for Distribution of an Offline Ruby on Rails Application
What Is the %W "Thing" in Ruby