How do you specify a required switch (not argument) with Ruby OptionParser? - ruby
I'm writing a script and I want to require a --host switch with value, but if the --host switch isn't specified, I want the option parsing to fail.
I can't seem to figure out how to do that. The docs seem to only specify how to make the argument value mandatory, not the switch itself.
An approach using optparse that provides friendly output on 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 a begin/rescue clause allows friendly formatting upon other failures such as missing arguments or invalid switch values, for instance, try passing a string for the -n switch.
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
I turned this into a gem you can download and install from rubygems.org:
gem install pickled_optparse
And you can checkout the updated project source code on github:
http://github.com/PicklePumpers/pickled_optparse
-- Older post info --
This was really, really bugging me so I fixed it and kept the usage super DRY.
To make a switch required just add a :required symbol anywhere in the array of options like so:
opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option|
#options[:foo] = option
end
Then at the end of your OptionParser block add one of these to print out the missing switches and the usage instructions:
if opts.missing_switches?
puts opts.missing_switches
puts opts
exit
end
And finally to make it all work you need to add the following "optparse_required_switches.rb" file to your project somewhere and require it when you do your command line parsing.
I wrote up a little article with an example on my blog:
http://picklepumpers.com/wordpress/?p=949
And here's the modified OptionParser file with an example of its usage:
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
I came up with a clear and concise solution that sums up your contributions. It raises an OptionParser::MissingArgument exception with the missing arguments as a message. This exception is catched in the rescue block along with the rest of exceptions coming 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
If host is required, then surely it isn't an option, it's an argument.
With that in mind, here's a way to solve your problem. You can interrogate the ARGV array to see if a host has been specified, and, if it hasn't been, then call abort("You must specify a host!"), or similar, to make your program quit with an error status.
If you do something like this:
opts.on('-h', '--host',
'required host name [STRING]') do |h|
someoptions[:host] = h || nil
end
Then the someoptions[:host] will either be the value from the commandline or nil (if you don't supply --host and/or no value after --host) and you can test for it easily (and conditionally fail) after the parse:
fail "Hostname not provided" unless someoptions[:host]
The answer from unknown (google) is good, but contains a minor error.
rescue OptionParser::InvalidArgument, OptionParser::MissingArgument
should be
OptionParser::InvalidOption, OptionParser::MissingArgument
Otherwise, optparse.parse! will trigger the standard error output for OptionParser::InvalidOption, not the custom message.
The idea is to define an OptionParser, then parse! it, and puts it if some fields are missing. Setting filename to empty string by default is probably not the best way to go, but you got the 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 is 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
Related
Check command line argument in Ruby code is present
I want to execute Ruby code if command line argument is present: ruby sanity_checks.rb --trx-request -e staging_psp -g all Ruby code: def execute command_options = CommandOptionsProcessor.parse_command_options request_type = command_options[:env] tested_env = command_options[:trx-request] // check here if flag is present tested_gateways = gateway_names(env: tested_env, gateway_list: command_options[:gateways]) error_logs = [] if(request_type.nil?) tested_gateways.each do |gateway| ........ end end raise error_logs.join("\n") if error_logs.any? end How I can get the argument --trx-request and check is it present? EDIT: Command parser: class CommandOptionsProcessor def self.parse_command_options command_options = {} opt_parser = OptionParser.new do |opt| opt.banner = 'Usage: ruby sanity_checks.rb [OPTIONS]' opt.separator "Options:" opt.on('-e TEST_ENV', '--env TEST_ENV','Set tested environment (underscored).') do |setting| command_options[:env] = setting end opt.on('-g X,Y,Z', '--gateways X,Y,Z', Array, 'Set tested gateways (comma separated, no spaces).') do |setting| command_options[:gateways] = setting end end opt_parser.parse!(ARGV) command_options end end Can you advice?
You will need to add a boolean switch option to your OptionParser like so opt.on('-t','--[no-]trx-request','Signifies a TRX Request') do |v| # note we used an underscore rather than a hyphen to make this symbol # easier to access command_options[:trx_request] = v end Then in your execute method you can access this as command_options[:trx_request] If you need to have a default value you can add one in the parse_command_options method by setting it outside the OptionParser as command_options[:trx_request] = false
Optparse doesn't seem to return ARGV array. Argument Required Error
This is homework and I do not expect you to give me the complete answer. I'm trying to parse a command line entry such as: ./apacheReport.rb -u testlog.txt When I enter this: ./apacheReport.rb -u testlog.txt I get: Argument required My code is: require_relative 'CommonLog' require 'optparse' # puts ARGV.inspect optparser = OptionParser.new optU = false optI = false optS = false optH = false optparser.banner = "apacheReport.rb [options] filename" optparser.parse! rescue => m puts m.message puts optparser exit end if ARGV.length < 1 puts "Argument required" exit end userInputFile = ARGV[0] userInputFile.to_s file = CommonLog.new(userInputFile) It should parse the leftover portion of the command into ARGV[0] then should store it as userInputFile and then create a CommonLog object using the file as the constructor. At that point I call the methods that were specified in the command. It seems that for some reason my ARGV is not being returned. I'm not sure what the issue is.
Ruby's OptionParser is easy to use, but you have to puzzle through the documentation. Here's a little example that'd be useful for your code: require 'optparse' options = {} OptionParser.new do |opt| opt.on('-u', '--use_this FILE', 'Use this file') { |o| options[:use_this] = o } end.parse! options will contain the flags. In this case, if you pass in -u foo, options[:use_this] will be foo. Save that and try running it without and with a parameter. Also try running it with just a -h flag. You can search StackOverflow for more answers where I was dealing with OptionParser.
It's hard to tell what's wrong since you code doesn't seem to be working at the moment. The problem may be that the parse! method removes found options from ARGV. So when you write: optparser.parse! It removes your two parameters (-u testlog.txt) and this code always fails: if ARGV.length < 1 puts "Argument required" exit end Instead of looking at ARGV, you need to set up optparser correctly. Perhaps something like: optparser = OptionParser.new do |opts| opts.banner = "apacheReport.rb [options] filename" opts.on("-u", "--u-short-for-this", "Whatever u stands for") do |u| optU = u end end Then optU will be true only if the user passed -u and the filename will be in ARGV[0].
Parse command line arguments in a Ruby script
I want to call a Ruby script from the command line, and pass in parameters that are key/value pairs. Command line call: $ ruby my_script.rb --first_name=donald --last_name=knuth my_script.rb: puts args.first_name + args.last_name What is the standard Ruby way to do this? In other languages I usually have to use an option parser. In Ruby I saw we have ARGF.read, but that does not seem to work key/value pairs like in this example. OptionParser looks promising, but I can't tell if it actually supports this case.
Ruby's built-in OptionParser does this nicely. Combine it with OpenStruct and you're home free: require 'optparse' options = {} OptionParser.new do |opt| opt.on('--first_name FIRSTNAME') { |o| options[:first_name] = o } opt.on('--last_name LASTNAME') { |o| options[:last_name] = o } end.parse! puts options options will contain the parameters and values as a hash. Saving and running that at the command line with no parameters results in: $ ruby test.rb {} Running it with parameters: $ ruby test.rb --first_name=foo --last_name=bar {:first_name=>"foo", :last_name=>"bar"} That example is using a Hash to contain the options, but you can use an OpenStruct which will result in usage like your request: require 'optparse' require 'ostruct' options = OpenStruct.new OptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options.first_name = o } opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options.last_name = o } end.parse! puts options.first_name + ' ' + options.last_name $ ruby test.rb --first_name=foo --last_name=bar foo bar It even automatically creates your -h or --help option: $ ruby test.rb -h Usage: test [options] --first_name FIRSTNAME --last_name LASTNAME You can use short flags too: require 'optparse' options = {} OptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME') { |o| options[:first_name] = o } opt.on('-l', '--last_name LASTNAME') { |o| options[:last_name] = o } end.parse! puts options Running that through its paces: $ ruby test.rb -h Usage: test [options] -f, --first_name FIRSTNAME -l, --last_name LASTNAME $ ruby test.rb -f foo --l bar {:first_name=>"foo", :last_name=>"bar"} It's easy to add inline explanations for the options too: OptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options[:first_name] = o } opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options[:last_name] = o } end.parse! and: $ ruby test.rb -h Usage: test [options] -f, --first_name FIRSTNAME The first name -l, --last_name LASTNAME The last name OptionParser also supports converting the parameter to a type, such as an Integer or an Array. Refer to the documentation for more examples and information. You should also look at the related questions list to the right: "Really Cheap Command-Line Option Parsing in Ruby" "Pass variables to Ruby script via command line"
Based on the answer by #MartinCortez here's a short one-off that makes a hash of key/value pairs, where the values must be joined with an = sign. It also supports flag arguments without values: args = Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/) ] …or alternatively… args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] Called with -x=foo -h --jim=jam it returns {"x"=>"foo", "h"=>nil, "jim"=>"jam"} so you can do things like: puts args['jim'] if args.key?('h') #=> jam While there are multiple libraries to handle this—including GetoptLong included with Ruby—I personally prefer to roll my own. Here's the pattern I use, which makes it reasonably generic, not tied to a specific usage format, and flexible enough to allow intermixed flags, options, and required arguments in various orders: USAGE = <<ENDUSAGE Usage: docubot [-h] [-v] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file] ENDUSAGE HELP = <<ENDHELP -h, --help Show this help. -v, --version Show the version number (#{DocuBot::VERSION}). create Create a starter directory filled with example files; also copies the template for easy modification, if desired. -s, --shell The shell to copy from. Available shells: #{DocuBot::SHELLS.join(', ')} -f, --force Force create over an existing directory, deleting any existing files. -w, --writer The output type to create [Defaults to 'chm'] Available writers: #{DocuBot::Writer::INSTALLED_WRITERS.join(', ')} -o, --output The file or folder (depending on the writer) to create. [Default value depends on the writer chosen.] -n, --nopreview Disable automatic preview of .chm. -l, --logfile Specify the filename to log to. ENDHELP ARGS = { :shell=>'default', :writer=>'chm' } # Setting default values UNFLAGGED_ARGS = [ :directory ] # Bare arguments (no flag) next_arg = UNFLAGGED_ARGS.first ARGV.each do |arg| case arg when '-h','--help' then ARGS[:help] = true when 'create' then ARGS[:create] = true when '-f','--force' then ARGS[:force] = true when '-n','--nopreview' then ARGS[:nopreview] = true when '-v','--version' then ARGS[:version] = true when '-s','--shell' then next_arg = :shell when '-w','--writer' then next_arg = :writer when '-o','--output' then next_arg = :output when '-l','--logfile' then next_arg = :logfile else if next_arg ARGS[next_arg] = arg UNFLAGGED_ARGS.delete( next_arg ) end next_arg = UNFLAGGED_ARGS.first end end puts "DocuBot v#{DocuBot::VERSION}" if ARGS[:version] if ARGS[:help] or !ARGS[:directory] puts USAGE unless ARGS[:version] puts HELP if ARGS[:help] exit end if ARGS[:logfile] $stdout.reopen( ARGS[:logfile], "w" ) $stdout.sync = true $stderr.reopen( $stdout ) end # etc.
There is a number of command line arguments parsers in Ruby: GetoptLong - Included in stdlib OptionParser - No longer part of stdlib, since Ruby 3.0.0 converted to a separate optparse gem slop optimist and many more... Personally I'd choose slop or optimist, those are not part of standard Ruby installation. gem install slop But it offers simplicity and code readability. Assuming slightly more complex example with required arguments and default values: require 'slop' begin opts = Slop.parse do |o| o.int '-a', '--age', 'Current age', default: 42 o.string '-f', '--first_name', 'The first name', required: true o.string '-l', '--last_name', 'The last name', required: true o.bool '-v', '--verbose', 'verbose output', default: false o.on '-h','--help', 'print the help' do puts o exit end end p opts.to_hash rescue Slop::Error => e puts e.message end optimist formerly known as trollop, it's very easy to ready, with minimum boilerplate code: gem install optimist require 'optimist' opts = Optimist::options do opt :verbose, "verbose mode" opt :first_name, "The first name", type: :string, required: true opt :last_name, "The last name", type: :string, required: true opt :age, "Current age", default: 42 end p opts Similar example using OptionParser: #!/usr/bin/env ruby require 'optparse' require 'ostruct' begin options = OpenStruct.new OptionParser.new do |opt| opt.on('-a', '--age AGE', 'Current age') { |o| options.age = o } opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options.first_name = o } opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options.last_name = o } opt.on('-v', '--verbose', 'Verbose output') { |o| options.verbose = true } end.parse! options[:age] = 42 if options[:age].nil? raise OptionParser::MissingArgument.new('--first_name') if options[:first_name].nil? raise OptionParser::MissingArgument.new('--last_name') if options[:last_name].nil? options[:verbose] = false if options[:verbose].nil? rescue OptionParser::ParseError => e puts e.message exit end GetoptLong parsing is even more complicated: require 'getoptlong' opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--first_name', '-f', GetoptLong::REQUIRED_ARGUMENT ], [ '--last_name', '-l', GetoptLong::REQUIRED_ARGUMENT ], [ '--age','-a', GetoptLong::OPTIONAL_ARGUMENT ], [ '--verbose','-v', GetoptLong::OPTIONAL_ARGUMENT ] ) begin options = {} options[:verbose] = false options[:age] = 42 opts.each do |opt, arg| case opt when '--help' puts <<-EOF usage: ./getlongopts.rb [options] -a, --age Current age -f, --first_name The first name -l, --last_name The last name -v, --verbose verbose output -h, --help print the help EOF when '--first_name' options[:first_name] = arg when '--last_name' options[:last_name] = arg when '--age' options[:age] = arg.to_i when '--verbose' options[:verbose] = arg else puts "unknown option `#{opt}`" exit 1 end end raise GetoptLong::MissingArgument.new('Missing argument --first_name') if options[:first_name].nil? raise GetoptLong::MissingArgument.new('Missing argument --last_name') if options[:last_name].nil? rescue GetoptLong::Error => e puts e.message exit end puts options Command line arguments was never meant to be a rocket science task, spend your time on reading/writing more useful code :)
I personally use Docopt. This is much more clear, maintainable and easy to read. Have a look at the Ruby implementation's documentation for examples. The usage is really straightforward. gem install docopt Ruby code: doc = <<DOCOPT My program who says hello Usage: #{__FILE__} --first_name=<first_name> --last_name=<last_name> DOCOPT begin args = Docopt::docopt(doc) rescue Docopt::Exit => e puts e.message exit end print "Hello #{args['--first_name']} #{args['--last_name']}" Then calling: $ ./says_hello.rb --first_name=Homer --last_name=Simpsons Hello Homer Simpsons And without arguments: $ ./says_hello.rb Usage: says_hello.rb --first_name=<first_name> --last_name=<last_name>
A bit of standard Ruby Regexp in myscript.rb: args = {} ARGV.each do |arg| match = /--(?<key>.*?)=(?<value>.*)/.match(arg) args[match[:key]] = match[:value] # e.g. args['first_name'] = 'donald' end puts args['first_name'] + ' ' + args['last_name'] And on the command line: $ ruby script.rb --first_name=donald --last_name=knuth Produces: $ donald knuth
Here is a slight modification to #Phrogz excellent answer: this mod will allow you to pass a string with spaces in it. args= Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:="(.*?)"+)?/)] In a command line pass the string like this: ruby my_script.rb '--first="Boo Boo" --last="Bear"' Or from another ruby script like this: system('ruby my_script.rb \'--first="Boo Boo" --last="Bear"\'') Results: {"first"=>"Boo Boo", "last"=>"Bear"}
An improved version that handles arguments that are not options, arguments with a parameter, and -a as well as --a. def parse(args) parsed = {} args.each do |arg| match = /^-?-(?<key>.*?)(=(?<value>.*)|)$/.match(arg) if match parsed[match[:key].to_sym] = match[:value] else parsed[:text] = "#{parsed[:text]} #{arg}".strip end end parsed end
How to generate OptionParser require arguments
The code below works, but I am manually raising argument errors for the required arguments using fetch, when I want to build the required arguments into the native OptionParser sytax for required parameters: # ocra script.rb -- --type=value options = {} OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("--type [TYPE]",String, [:gl, :time], "Select Exception file type (gl, time)") do |t| options["type"] = t end opts.on("--company [TYPE]",String, [:jaxon, :doric], "Select Company (jaxon, doric)") do |t| options["company"] = t end end.parse! opts = {} opts['type'] = options.fetch('type') do raise ArgumentError,"no 'type' option specified as a parameter (gl or time)" end opts['company'] = options.fetch('company') do raise ArgumentError,"no 'company' option specified as a parameter (doric or jaxon)" end
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.
Faced with the same situation, I ended up with an option like this. If not all of my mandatory options are provided, output the user-friendly help text generated by OptionParser based on my defined options. Feels cleaner than throwing an exception and printing a stack trace to the user. options = {} option_parser = OptionParser.new do |opts| opts.banner = "Usage: #{$0} --data-dir DATA_DIR [options]" # A non-mandatory option opts.on('-p', '--port PORT', Integer, 'Override port number') do |v| options[:port] = v end # My mandatory option opts.on('-d', '--data-dir DATA_DIR', '[Mandatory] Specify the path to the data dir.') do |d| options[:data_dir] = d end end option_parser.parse! if options[:data_dir].nil? puts option_parser.help exit 1 end
How can I do readline arguments completion?
I have a Ruby app which uses readline with command completion. After the first string (the command) was typed, I would like to be able to complete its arguments. The arguments list should be based on the chosen command. Does someone have a quick example? These are the commands: COMMANDS = [ 'collect', 'watch' ].sort COLLECT = [ 'stuff', 'otherstuff' ].sort comp = proc do |s| COMMANDS.grep( /^#{Regexp.escape(s)}/ ) end Readline.completion_proc = comp Each time I press TAB, the proc block is executed and a command from the COMMANDS array is matched. After one of the commands was fully matched I would like to start searching for the argument only in the COLLECT array.
Since your question popped up first every time I looked for something like this I want to share my code for any one else. #!/usr/bin/env ruby require 'readline' module Shell PROMPT = "shell> " module InputCompletor CORE_WORDS = %w[ clear help show exit export] SHOW_ARGS = %w[ list user ] EXPORT_ARGS = %w[ file ] COMPLETION_PROC = proc { |input| case input when /^(show|export) (.*)/ command = $1 receiver = $2 DISPATCH_TABLE[$1].call($2) when /^(h|s|c|e.*)/ receiver = $1 CORE_WORDS.grep(/^#{Regexp.quote(receiver)}/) when /^\s*$/ puts CORE_WORDS.map{|d| print "#{d}\t"} puts print PROMPT end } def self.show(receiver) if SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).length > 1 SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/) elsif SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).length == 1 "show #{SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).join}" end end def self.export(receiver) if EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).length > 1 EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/) elsif EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).length == 1 "export #{EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).join}" end end DISPATCH_TABLE = {'show' => lambda {|x| show(x)} , 'export' => lambda {|x| export(x)}} end class CLI Readline.completion_append_character = ' ' Readline.completer_word_break_characters = "\x00" Readline.completion_proc = Shell::InputCompletor::COMPLETION_PROC def initialize while line = Readline.readline("#{PROMPT}",true) Readline::HISTORY.pop if /^\s*$/ =~ line begin if Readline::HISTORY[-2] == line Readline::HISTORY.pop end rescue IndexError end cmd = line.chomp case cmd when /^clear/ system('clear') when /^help/ puts 'no help here' when /show list/ puts 'nothing to show' when /^show\s$/ puts 'missing args' when /export file/ puts 'nothing to export' when /^export\s$/ puts 'missing args' when /^exit/ exit end end end end end Shell::CLI.new
After thinking a while, the solution was very simple: comp = proc do |s| if Readline.line_buffer =~ /^.* / COLLECT.grep( /^#{Regexp.escape(s)}/ ) else COMMANDS.grep( /^#{Regexp.escape(s)}/ ) end end Now I just need to turn it into something more flexible/usable.