How to check if 'ARGV' contains both '-p' and '-c' in optparse - ruby

This code is intended to check whether the user entered an option with the command:
require 'optparse'
ARGV << '-h' if ARGV.empty?
options = {}
OptionParser.new do |parser|
parser.banner = "Usage: myruby.rb [options]"
parser.on("-h", "--help", "Help myruby") do | |
puts parser
exit
end
parser.on("-p", "--people PEOPLE", "PPPPPPPPPP") do |v|
options[:pppp] = v
end
parser.on("-c", "--coordinate COORDINATE", "ccccccccc") do |x|
options[:coordinate] = x
end
end.parse!
# Start my program from this line
unless options[:pppp] && options[:coordinate]
puts "Exit OK because missing both (option and argument) p,c"
exit
end
puts "It work if only run myruby.rb -p argument_P -c argument_c"
I just found an error. If the user enters only one but not both required ARGV (-p -c).
I can check and exit from my application, but I want to filter ARGV by exiting to assign ARGV << 'h'.
What is the best way?
updated 1: Added unless case before run my program problem : Worked as
asked, but error when -p or -c missing argument. example : ruby
thiscode.rb -p bababa -c error : rb:17:in `': missing
argument (OptionParser::MissingArgument)

Just explicitly check the presence of both after options are parsed:
unless options[:pppp] && options[:coordinate]
puts USAGE # or do whatever else
exit
end

Related

Ruby OptionParser: how to handle arguments without a prefix (like a required filename)

I am working with OptionParser for the first time.
What I would like to know, is how I can make OptionParser handle arguments that are not prefixed with a certain flagname. I want to be able to write a statement like:
myscript.rb -d someoption -b someotheroption filename
where filename is the name of the file I want to work on. It is not prefixed by any option flag. How can I parse commands like the one above with OptionParser, and get a reference to filename?
OptionParser specifically handles options - that is, things starting with dashes. After it parses, the remaining arguments are left in ARGV. You can check for your filename there and exit with an error if it's missing.
With a slight modification on their minimal example,
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
p options
p ARGV
p "Where is my hat?!" if ARGV.length == 0
You get this:
$ ruby parse.rb
{}
[]
"Where is my hat?!"
$ ruby parse.rb hat
{}
["hat"]
$ ruby parse.rb -v hat
{:verbose=>true}
["hat"]

How do I call help if path undefined using OptionParser?

I'm new to Ruby and am trying to code an elegant method for setting the working directory for the program. All file accesses with be relative to this path.
The program may or may not be run from within a Git repo. I also want to provide a method of overriding the path.
I'm using OptionParser and am having difficulty getting it to set the option correctly. It seems that work_area always gets set to the Git toplevel, regardless of whether or not I'm using the --work_area flag. I've tried using a || operator within the opts.on and that didn't work either.
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: mysprogram.rb [options]"
options[:work_area] = `git rev-parse --show-toplevel --quiet 2>/dev/null`
opts.on("-d", "--work_area", String, "Override default work area.") do |wa|
options[:work_area] = wa
end
if options[:work_area]
puts "Work area is " + options[:work_area]
else
puts "ERROR: Valid work directory not found or specified."
puts opts
exit
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!
Any suggestions on what I'm doing wrong, or how to make this more Ruby-like, would be appreciated.
The block you pass to opts.on("-d" ...) is called after the block that's passed to OptionParser.new, so your puts statement is being executed before the argument parsing is actually happening. Try initializing it to the default (and testing it) outside of the OptionParser.new block entirely:
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: mysprogram.rb [options]"
opts.on("-d", "--work_area", String, "Override default work area.") do |wa|
options[:work_area] = wa
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end
opts.parse!
# Notice the "||=" here; this means "set options[:work_area] to a new thing
# only if it's not nil or false."
options[:work_area] ||= `git rev-parse --show-toplevel --quiet 2>/dev/null`
if options[:work_area]
puts "Work area is " + options[:work_area]
else
puts "ERROR: Valid work directory not found or specified."
puts opts
exit
end
I've always used something like:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.banner = "Usage: #{ File.basename($0) } [options]"
opt.on('--path PATH') { |o| options[:path] = o }
options[:help] = opt.help
end.parse!
puts options[:help] if !options[:path]
Saving that and running it with a --path foo option returns no output, as it should.
Running it without the --path foo option outputs:
Usage: test.rb [options]
--path PATH
Also, notice that OptionParser automatically supplies a -h or --help parameter for you if you don't define them. Calling the same code with -h results in the output you'd expect and are trying to code into your script.

OptionParser's make_switch error with '-?'

I'm running into an issue with OptionParser's make_switch.
My code parses three arguments and runs a test to see if my MANDATORY argument is here:
#!/usr/bin/env ruby
require 'optparse'
require 'ostruct'
options = OpenStruct.new
#argv = ARGV
optparse = OptionParser.new do |opts|
#opts=opts
usage = "USAGE: ./#{File.basename($0)} [-v] -p xxxxxx"
#opts.banner = usage
#opts.on( '-p', '--pdu [PDU]', 'Specify a PDU to configure') do |res|
options.pdu = true
$pdu_name = res
end
#opts.on( '-v', '--[no-]verbose', 'Run verbosely') do
options.verbose = true
end
#opts.on( '-?', '-help','Show this message') do
puts "Help Me!"
puts #opts
exit 1
end
end
begin
if not #argv.empty?
optparse.order!(#argv)
if !options.pdu
$stderr.puts "Options -p missing."
$stderr.puts "#{#opts}\n\n"
exit 1
end
else
$stderr.puts "ERROR: Arguments Required."
$stderr.puts "#{#opts}\n\n"
exit 1
end
rescue OptionParser::InvalidOption
$stderr.puts "ERROR: Invalid option."
$stderr.puts "#{#opts}\n\n"
exit 1
end
Everything works except -?:
xxx$ ./myscript.rb -?
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/optparse.rb:451:in
`parse': missing argument: -? (OptionParser::MissingArgument)
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/optparse.rb:1295:in `parse_in_order'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/optparse.rb:1254:in `catch'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/optparse.rb:1254:in `parse_in_order'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/optparse.rb:1248:in `order!'
blabla
However -help works perfectly:
xxxx$ ./myscript.rb -help
Help me!
USAGE: ./myscript.rb [-v] -p xxxxxx
-p, --pdu [PDU] Specify a PDU to configure
-v, --[no-]verbose Run verbosely
-?, -help Show this message
More surprisingly, -? -v works too:
xxxx$ ./myscript.rb -? -v
Help Me!
USAGE: ./myscript.rb [-v] -p xxxxxx
-p, --pdu [PDU] Specify a PDU to configure
-v, --[no-]verbose Run verbosely
-?, -help Show this message
What did I do wrong?
The same issue occurs if I replace -? with -h in the code.
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 use getoptlong class in ruby?

I need help using getoptlong class in Ruby. I need to execute command prog_name.ruby -u -i -s filename. So far I can only execute it with prog_name.ruby -u filename -i filename -s filename.
This is my getoptlong code:
require 'getoptlong'
class CommonLog
parser = GetoptLong.new
parser.set_options(["-h", "--help", GetoptLong::NO_ARGUMENT],
["-u", "--url", GetoptLong::NO_ARGUMENT],
["-i", "--ip", GetoptLong::NO_ARGUMENT],
["-s", "--stat", GetoptLong::NO_ARGUMENT])
begin
begin
opt,arg = parser.get_option
break if not opt
case opt
when "-h" || "--help"
puts "Usage: -u filename"
puts "Usage: -i filename"
puts "Usage: -s filename"
exit
when "-u" || "--url"
log = CommonLog.new(ARGV[0])
log.urlReport
when "-i" || "--ip"
log = CommonLog.new(ARGV[0])
log.ipReport
when "-s" || "--stat"
log = CommonLog.new(ARGV[0])
log.statReport
end
rescue => err
puts "#{err.class()}: #{err.message}"
puts "Usage: -h -u -i -s filename"
exit
end
end while 1
if ARGV[0] == nil || ARGV.size != 1
puts "invalid! option and filename required"
puts "usage: -h -u -i -s filename"
end
I'm going to answer by recommending looking at the new-ish "slop" gem. It's a wrapper around getoptlong.
You can use gem install slop if you're using RVM, or sudo gem install slop otherwise.
GetOptLong is very powerful but, though I've used it several times, I still have to go review the docs each time.
If you want a bit more power, with an "easier to use interface than GetOptLong", look into Ruby's OptionParser. You'll need to work out the logic better, but this is a quick pass converting your code. I had to stub out a class for the CommonLog gem because I don't use it. The important stuff follows the line pulling log from ARGV:
require 'optparse'
class CommonLog
def initialize(*args); end
def urlReport(); puts "running urlReport()"; end
def ipReport(); puts "running ipReport()"; end
def statReport(arg); puts "running statReport(#{arg})"; end
end
log = CommonLog.new(ARGV[0])
OptionParser.new { |opts|
opts.banner = "Usage: #{File.basename($0)} -u -i -s filename"
opts.on( '-u', '--[no-]url', 'some short text describing URL') do
log.urlReport()
end
opts.on('-i', '--[no-]ip', 'some short text describing IP') do
log.ipReport()
end
opts.on('-s', '--stat FILENAME', 'some short text describing STAT') do |arg|
log.statReport(arg)
end
}.parse!
Also, as a quick critique, you are not writing idiomatic Ruby code:
when statements can be written: when "-h", "--help"
if ARGV[0] == nil || ARGV.size != 1 is convoluted. Study up on how ARGV and arrays work. Normally, for ARGV[0] to be nil there will be no more arguments, so ARGV.empty? would probably suffice.
you have several errors in the sample program
#each and #get only return the first string in the option and convert the others to it.
You should put that check for arguments before the options processing
You probably don't want this in with your logging class
require 'getoptlong'
# don't pollute CommonLog with this
include CommonLog
# if this is the startup module
if __FILE__ == $0 then
# Check to ensure there are arguments
if ARGV.size < 1
puts "invalid! option and filename required"
puts "usage: -h -u -i -s filename"
end
# set up parser and get the options
parser_opts=GetoptLong.new(
["--help", "-h", GetoptLong::NO_ARGUMENT],
["--url", "-u", GetoptLong::NO_ARGUMENT],
["--ip", "-i", "--ip", GetoptLong::NO_ARGUMENT],
["--stat", "-s", GetoptLong::NO_ARGUMENT]
)
parser_opts.each do |opt,arg|
begin # this is for the exception processing
case opt
when "--help" #only the first option is returned read ruby doc on #each
puts "Usage: -u filename"
puts "Usage: -i filename"
puts "Usage: -s filename"
exit
when "--url" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.urlReport
when "--ip" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.ipReport
when "--stat" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.statReport
else # this should not be used
puts "unexpected option %s"%opt
puts "Usage: -h -u -i -s filename"
end
rescue Exception => err #rescuing an unexpected Exception
puts "#{err.class()}: #{err.message}"
puts "Usage: -h -u -i -s filename"
Kernel.exit
end
end
end

How do I handle a missing mandatory argument in Ruby OptionParser?

In OptionParser I can make an option mandatory, but if I leave out that value it will take the name of any following option as the value, screwing up the rest of the command line parsing.
Here is a test case that echoes the values of the options:
$ ./test_case.rb --input foo --output bar
output bar
input foo
Now leave out the value for the first option:
$ ./test_case.rb --input --output bar
input --output
Is there some way to prevent it taking another option name as a value?
Thanks!
Here is the test case code:
#!/usr/bin/env ruby
require 'optparse'
files = Hash.new
option_parser = OptionParser.new do |opts|
opts.on('-i', '--input FILENAME', 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o', '--output FILENAME', 'Output filename - required') do |filename|
files[:output] = filename
end
end
begin
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
$stderr.print "Error: " + $! + "\n"
exit
end
files.keys.each do |key|
print "#{key} #{files[key]}\n"
end
What you want to do is not a good idea. What if you really have a file named "--output"? This is a perfectly valid filename on Unix. Every Unix program's option parsing works the way the ruby one is doing, so you shouldn't change it, because then your program will be arbitrarily different from everything else, which is confusing and violates the "principle of least surprise."
The real question is: why are you having this problem in the first place? Perhaps you're running your program from another program, and the parent program is providing a blank filename as the parameter to --input, which makes it see --output as the parameter to --input. You can work around this by always quoting the filenames you pass on the command line:
./test_case.rb --input "" --output "bar"
Then --input will be blank, and that's easy to detect.
Also note that if --input is set to --output (and --output is not a real file) you can just try to open the --input file. If it fails, print a message like:
can't open input file: --output: file not found
And that should make it clear to the user what they did wrong.
try this:
opts.on('-i', '--input FILENAME', 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o', '--output FILENAME', 'Output filename - required') do |filename|
files[:output] = filename
end
opts.on("-h", "--help", "Show this message") do
puts opts
exit
end
begin
ARGV << "-h" if ARGV.size != 2
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
$stderr.print "Error: " + $! + "\n"
exit
end
In this case, the mandatory --output option is missing, so do this after calling parse!:
unless files[:input] && files[:output]
$stderr.puts "Error: you must specify both --input and --output options."
exit 1
end
OK - this works - the regular expression in the on() call allows any string as long as it doesn't start with a '-'
If I don't pass an argument to --input and there is another option downstream then it will take that option key as the argument to --input. (e.g. --input --output). The regexp catches that and then I check the error message. If the argument it reports starts with '-' I output the correct error message, namely that there is a missing argument. Not pretty but it seems to work.
Here is my working test case:
#!/usr/bin/env ruby
require 'optparse'
files = Hash.new
option_parser = OptionParser.new do |opts|
opts.on('-i FILENAME', '--input FILENAME', /\A[^\-]+/, 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o FILENAME', '--output FILENAME', /\A[^\-]+/, 'Output filename - required') do |filename|
files[:output] = filename
end
end
begin
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
if $!.to_s =~ /invalid\s+argument\:\s+(\-\-\S+)\s+\-/
$stderr.print "Error: missing argument: #{$1}\n"
else
$stderr.print "Error: " + $! + "\n"
end
exit
end
files.keys.each do |key|
print "#{key} #{files[key]}\n"
end

Resources