How to Specify a Required Switch (Not Argument) with Ruby Optionparser

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



Leave a reply



Submit