Ruby Optionparser Empty Switch "-" Behavior

Ruby OptionParser empty switch - behavior

If you are using OptionParser, then yes, you need to explicitly disallow the empty switch and manually check the required parameters.

However, if you used another tool for option parsing, such as defunkt's gem choice, you could mark options as required and the invalid options (such as the empty switch) would cause the help to be printed and application to exit. I understand that in some cases it makes more sense to use OptionParser, but personally I prefer to use the more convenient tools out there.

Even though making options required is pretty easy one way or the other, I would recommend that you think your API decision through. How many command line utilities do you know, that have required options? There's a reason why command line is usually separated into options and arguments, with the former being usually optional and the latter usually required. I would stick to that established convention.

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

Ruby OptionParser empty switch - behavior

If you are using OptionParser, then yes, you need to explicitly disallow the empty switch and manually check the required parameters.

However, if you used another tool for option parsing, such as defunkt's gem choice, you could mark options as required and the invalid options (such as the empty switch) would cause the help to be printed and application to exit. I understand that in some cases it makes more sense to use OptionParser, but personally I prefer to use the more convenient tools out there.

Even though making options required is pretty easy one way or the other, I would recommend that you think your API decision through. How many command line utilities do you know, that have required options? There's a reason why command line is usually separated into options and arguments, with the former being usually optional and the latter usually required. I would stick to that established convention.

How can Ruby OptionParser take care of parameter with spaces?

Use single or double quotes to surround the parameter:

-p 'SENDER=foo,RECIPIENT=bar,BODY=foo bar'

For example:

require 'optparse'

options = {}
OptionParser.new do |opt|
opt.on('-p', '--params OPTS', Array) { |o| options[:p] = o }
end.parse!

require 'pp'
pp options # =>

Running that at the command-line using:

ruby test.rb --params 'SENDER=foo,RECIPIENT=bar,BODY=foo bar'

Outputs:

{:p=>["SENDER=foo", "RECIPIENT=bar", "BODY=foo bar"]}

This isn't an OptionParser issue, it's how the command-line works when parsing the options and passing them to the script. OptionParse only gets involved once it sees the argument 'SENDER=foo,RECIPIENT=bar,BODY=foo bar' and splits it on the commas into an array and passes that to the opt.on block:

'SENDER=foo,RECIPIENT=bar,BODY=foo bar'.split(',')
# => ["SENDER=foo", "RECIPIENT=bar", "BODY=foo bar"]

It looks like you're trying to split the incoming data into an array of arrays because of:

options.params = params.map { |p| p.split("=") }

I'd recommend considering converting it into a hash instead:

opt.on('-p', '--params OPTS', Array) { |o| options[:p] = Hash[o.map{ |s| s.split('=') }] }

Which results in:

{:p=>{"SENDER"=>"foo", "RECIPIENT"=>"bar", "BODY"=>"foo bar"}}

And makes it easy to get at specific entries passed in:

pp options[:p]['BODY'] # => "foo bar"

Can OptionParser skip unknown options, to be processed later in a Ruby program?

Assuming the order in which the parsers will run is well defined, you can just store the extra options in a temporary global variable and run OptionParser#parse! on each set of options.

The easiest way to do this is to use a delimiter like you alluded to. Suppose the second set of arguments is separated from the first by the delimiter --. Then this will do what you want:

opts = OptionParser.new do |opts|
# set up one OptionParser here
end

both_args = $*.join(" ").split(" -- ")
$extra_args = both_args[1].split(/\s+/)
opts.parse!(both_args[0].split(/\s+/))

Then, in the second code/context, you could do:

other_opts = OptionParser.new do |opts|
# set up the other OptionParser here
end

other_opts.parse!($extra_args)

Alternatively, and this is probably the "more proper" way to do this, you could simply use OptionParser#parse, without the exclamation point, which won't remove the command-line switches from the $* array, and make sure that there aren't options defined the same in both sets. I would advise against modifying the $* array by hand, since it makes your code harder to understand if you are only looking at the second part, but you could do that. You would have to ignore invalid options in this case:

begin
opts.parse
rescue OptionParser::InvalidOption
puts "Warning: Invalid option"
end

The second method doesn't actually work, as was pointed out in a comment. However, if you have to modify the $* array anyway, you can do this instead:

tmp = Array.new

while($*.size > 0)
begin
opts.parse!
rescue OptionParser::InvalidOption => e
tmp.push(e.to_s.sub(/invalid option:\s+/,''))
end
end

tmp.each { |a| $*.push(a) }

It's more than a little bit hack-y, but it should do what you want.

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.



Related Topics



Leave a reply



Submit