How to specify the desired switch (not an argument) with Ruby OptionParser?

I am writing a script and I want to set the --host switch with a value, but if the --host parameter is not specified, I want the parameter parsing to fail.

I can’t figure out how to do this. The docs seem to indicate only how to make the argument value mandatory, and not by the switch itself.

+42
ruby arguments
09 Oct '09 at 0:38
source share
8 answers

I assume that you are using optparse here, although the same method will work for other parameter parsing libraries as well.

The simplest method, probably, is to parse the parameters using the parsing library of your choice and then throw an OptionParser :: MissingArgument Exception if the host value is zero.

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]}" 

Run this example using the command line

 ./program -h somehost 

simple displays "Host = somehost"

When working with the missing -h name and without the file name, the following output is generated

 ./program:15: missing argument: (OptionParser::MissingArgument) 

And working with the command line. / program -h, you create

 /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 
+55
09 Oct '09 at 8:53
source share

An approach using optparse that provides friendly output of missing switches:

 #!/usr/bin/env ruby require 'optparse' options = {} optparse = OptionParser.new do |opts| opts.on('-f', '--from SENDER', 'username of sender') do |sender| options[:from] = sender end opts.on('-t', '--to RECIPIENTS', 'comma separated list of recipients') do |recipients| options[:to] = recipients end options[:number_of_files] = 1 opts.on('-n', '--num_files NUMBER', Integer, "number of files to send (default #{options[:number_of_files]})") do |number_of_files| options[:number_of_files] = number_of_files end opts.on('-h', '--help', 'Display this screen') do puts opts exit end end begin optparse.parse! mandatory = [:from, :to] # Enforce the presence of missing = mandatory.select{ |param| options[param].nil? } # the -t and -f switches unless missing.empty? # raise OptionParser::MissingArgument.new(missing.join(', ')) # end # rescue OptionParser::InvalidOption, OptionParser::MissingArgument # puts $!.to_s # Friendly output when parsing fails puts optparse # exit # end # puts "Performing task with options: #{options.inspect}" 

Running without the -t or -f switches shows the following output:

 Missing options: from, to Usage: test_script [options] -f, --from SENDER username of sender -t, --to RECIPIENTS comma separated list of recipients -n, --num_files NUMBER number of files to send (default 1) -h, --help 

Running the parse method in the begin / rescue clause allows friendly formatting for other failures, such as missing arguments or invalid switch values, for example, try passing a string for the -n switch.

+80
Jan 27 '10 at 18:00
source share

I turned this into a gem that you can download and install from rubygems.org:

 gem install pickled_optparse 

And you can check the updated project source code on github:
http://github.com/PicklePumpers/pickled_optparse

- Old post information -

It was really, really listening to me, so I fixed it and to use super DRY.

To make the required switch, simply add the required character in any of the arrays of such parameters:

 opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option| @options[:foo] = option end 

Then at the end of the OptionParser block, add one of them to print the missing switches and usage instructions:

 if opts.missing_switches? puts opts.missing_switches puts opts exit end 

And finally, to do all this, you need to add the following file β€œoptparse_required_switches.rb” to your project somewhere and require it when you figure out the command line.

I wrote a short article with an example on my blog: http://picklepumpers.com/wordpress/?p=949

And here is a modified OptionParser file with an example of its use:

required_switches_example.rb

 #!/usr/bin/env ruby require 'optparse' require_relative 'optparse_required_switches' # Configure options based on command line options @options = {} OptionParser.new do |opts| opts.banner = "Usage: test [options] in_file[.srt] out_file[.srt]" # Note that :required can be anywhere in the parameters # Also note that OptionParser is bugged and will only check # for required parameters on the last option, not my bug. # required switch, required parameter opts.on("-s Short", String, :required, "a required switch with just a short") do |operation| @options[:operation] = operation end # required switch, optional parameter opts.on(:required, "--long [Long]", String, "a required switch with just a long") do |operation| @options[:operation] = operation end # required switch, required parameter opts.on("-b", "--both ShortAndLong", String, "a required switch with short and long", :required) do |operation| @options[:operation] = operation end # optional switch, optional parameter opts.on("-o", "--optional [Whatever]", String, "an optional switch with short and long") do |operation| @options[:operation] = operation end # Now we can see if there are any missing required # switches so we can alert the user to what they # missed and how to use the program properly. if opts.missing_switches? puts opts.missing_switches puts opts exit end end.parse! 

optparse_required_switches.rb

 # Add required switches to OptionParser class OptionParser # An array of messages describing the missing required switches attr_reader :missing_switches # Convenience method to test if we're missing any required switches def missing_switches? !@missing_switches.nil? end def make_switch(opts, block = nil) short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] ldesc, sdesc, desc, arg = [], [], [] default_style = Switch::NoArgument default_pattern = nil klass = nil n, q, a = nil # Check for required switches required = opts.delete(:required) opts.each do |o| # argument class next if search(:atype, o) do |pat, c| klass = notwice(o, klass, 'type') if not_style and not_style != Switch::NoArgument not_pattern, not_conv = pat, c else default_pattern, conv = pat, c end end # directly specified pattern(any object possible to match) if (!(String === o || Symbol === o)) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc else conv = SPLAT_PROC end next end # anything others case o when Proc, Method block = notwice(o, block, 'block') when Array, Hash case pattern when CompletingHash when nil pattern = CompletingHash.new conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) else raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') when /^--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style not_style = (not_style || default_style).guess(arg = a) if a default_style = Switch::NoArgument default_pattern, conv = search(:atype, FalseClass) unless default_pattern ldesc << "--no-#{q}" long << 'no-' + (q = q.downcase) nolong << q when /^--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--[no-]#{q}" long << (o = q.downcase) not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << 'no-' + o when /^--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--#{q}" long << (o = q.downcase) when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << Regexp.new(q) when /^-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << q when /^=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else desc.push(o) end end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern if !(short.empty? and long.empty?) s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) end s = desc else short << pattern s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block) end # Make sure required switches are given if required && !(default_argv.include?("-#{short[0]}") || default_argv.include?("--#{long[0]}")) @missing_switches ||= [] # Should be placed in initialize if incorporated into Ruby proper # This is more clear but ugly and long. #missing = "-#{short[0]}" if !short.empty? #missing = "#{missing} or " if !short.empty? && !long.empty? #missing = "#{missing}--#{long[0]}" if !long.empty? # This is less clear and uglier but shorter. missing = "#{"-#{short[0]}" if !short.empty?}#{" or " if !short.empty? && !long.empty?}#{"--#{long[0]}" if !long.empty?}" @missing_switches << "Missing switch: #{missing}" end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), nolong end end 
+9
Oct 28 2018-10-10T00:
source share

I came up with a clear and concise solution that summarizes your contributions. It throws an OptionParser::MissingArgument with missing arguments as a message. This exception falls into the rescue block along with the other exceptions emanating from OptionParser .

 #!/usr/bin/env ruby require 'optparse' options = {} optparse = OptionParser.new do |opts| opts.on('-h', '--host hostname', "Host name") do |host| options[:host] = host end end begin optparse.parse! mandatory = [:host] missing = mandatory.select{ |param| options[param].nil? } raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty? rescue OptionParser::ParseError => e puts e puts optparse exit end 

Running this example:

 ο£Ώ ./program missing argument: host Usage: program [options] -h, --host hostname Host name 
+4
Jan 02 '15 at 5:51 on
source share

If a host is required, then of course this is not an option, this is an argument.

With that in mind, here is a way to solve your problem. You can poll the ARGV array to see if a host was specified, and if it wasn’t, call abort("You must specify a host!") Or similar to have your program terminate with an error status.

+3
Dec 23 2018-11-11T00:
source share

If you do something like this:

 opts.on('-h', '--host', 'required host name [STRING]') do |h| someoptions[:host] = h || nil end 

Then someoptions[:host] will either be a value from the command line or nil (unless you specify --host and / or no value after --host), and you can easily test it (and conditionally crash) after parsing:

 fail "Hostname not provided" unless someoptions[:host] 
+2
Apr 08 '13 at 15:10
source share

The answer from the unknown (google) is good, but contains a minor error.

 rescue OptionParser::InvalidArgument, OptionParser::MissingArgument 

it should be

 OptionParser::InvalidOption, OptionParser::MissingArgument 

Otherwise optparse.parse! will cause standard error output for OptionParser::InvalidOption , not a custom message.

+1
Feb 04 '10 at 8:19
source share

The idea is to define OptionParser , then parse! it and puts if some fields are missing. Setting filename to an empty string by default is probably not the best way, but you got this idea.

 require 'optparse' filename = '' options = OptionParser.new do |opts| opts.banner = "Usage: swift-code-style.rb [options]" opts.on("-iNAME", "--input-filename=NAME", "Input filename") do |name| filename = name end opts.on("-h", "--help", "Prints this help") do puts opts exit end end options.parse! if filename == '' puts "Missing filename.\n---\n" puts options exit end puts "Processing '#{filename}'..." 

If -i filename missing, it displays:

 ~/prj/gem/swift-code-kit ./swift-code-style.rb Missing filename. --- Usage: swift-code-style.rb [options] -i, --input-filename=NAME Input filename -h, --help Prints this help 
0
Jul 14 '16 at 13:21
source share



All Articles