How to get the specified option flag from within OptionParser - ruby

I'd like to get the exact option flag that was specified on the command line from within Ruby's OptionParser.
For example, suppose I have the following code:
parser = OptionParser.new {
|opts|
opts.on('-f', '--file FILE', 'filename') {
|arg|
$filename = arg
# Here I'd like to know whether '-f' or '--file' was entered
# on the command line.
}
# ... etc. ...
}
I'd like to know whether the user happened to type '-f' or '--file' on the command line. Is this possible without writing two separate opts.on blocks?

I don't think you can get the flags being passed in when inside the OptionParser.new block. At that point it's too late. However, prior to OptionParser parsing the command-line, it's possible to look and see what's being passed in.
ARGV contains the raw command-line. For instance, if this is the command-line invocation for some code:
foo -i 1 -j 2
then ARGV will contain:
["-i", "1", "-j", "2"]
and, then it becomes pretty easy to grab the flags:
ARGV.grep(/^-/) # => ["-i", "-j"]
There are other OptionParser-like tools for Ruby, and those might let you access the flags being used, but I can't think of a reason I'd ever care to. Looking at your code it seems like you're not understanding how to use OptionParser:
parser = OptionParser.new {
|opts|
opts.on('-f', '--file FILE', 'filename') {
|arg|
$filename = arg
# Here I'd like to know whether '-f' or '--file' was entered
# on the command line.
}
# ... etc. ...
}
Instead of doing it that way, I'd write it:
options = {}
OptionParser.new do |opts|
opts.on('-f', '--file FILE', 'filename') { |arg| options[:filename] = arg }
end.parse!
if options[:filename]
puts 'exists' if File.exist?(options[:filename])
end
Then, later in your code you can check in the options hash to see if either of the -f or --file options was given, and what the value was. That it was one or the other of -f or --file shouldn't ever matter.
If it does then you need to differentiate between the two flags, instead of treating them as if they're aliases:
options = {}
OptionParser.new do |opts|
opts.on('-f', 'filename') { |arg| options[:f] = arg }
opts.on('--file FILE', 'filename') { |arg| options[:file] = arg }
end.parse!
if options[:file] || options[:f]
puts 'exists' if File.exist?(options[:file] || options[:f])
end

Related

optionparser pass an array to ruby file

I have a trouble with passing an arguments to my ruby file. IE,
OptionParser.new do |opts|
opts.banner = 'Usage: mp3split.rb [options]'
opts.on('-f', '--filename fName1,fName2,fName3', Array, 'absolute or relative pathes to file') { |f| options[:filenames] = f }
end.parse!
This approach requires me to write this commands:
ruby mp3split.rb --filename fursov_13.mp3,fursov_14.mp3,fursov_14_2.mp3,fursov_15.mp3,fursov_16.mp3,fursov_17.mp3
But I want to get array by this:
ruby mp3split.rb --filename fursov_13.mp3 fursov_14.mp3 fursov_14_2.mp3 fursov_15.mp3 fursov_16.mp3 fursov_17.mp3
How can I implement this functionality? I can't find anything helpful about this in docs.
With OptionParser you can’t. There is a workaround, though, when there is the only list you need to handle that way and you won’t pass other arguments to the script. OptionParser splits the input by spaces before it processes it. The unkeyed arguments remain in ARGV global constant after parsing. Assuming everything described above is met, here we go:
OptionParser.new do |opts|
options[:filenames] = []
opts.banner = 'Usage: mp3split.rb [options]'
opts.on('-f', '--filename fName1 fName2 fName3', Array,
'absolute or relative pathes to file') do |f|
options[:filenames] |= [*f]
end
end.parse!
options[:filenames] |= ARGV
I apparently fixed another glitch within your code: options[:filenames] should be appended not overwritten on every occurence of -f switch, OptionParser supports script -f f1 -f f2 -f f3.

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 can Ruby OptionParser take care of parameter with spaces?

I am trying to define the options for my Ruby script which sending messages from User A to User B for testing purpose. However I couldn't get it work when some of the option have spaces in the value. For example:
OptionParser.new do |opts|
opts.on("-p", "--params a=A,b=B,c=C", Array, "Parameters to compose the message") do |params|
options.params = params.map { |p| p.split("=") }
end
end
But when I try to specify thing like -p SENDER=foo,RECIPIENT=bar,BODY=foo bar it just gave me back ["SENDER" => "foo", "RECIPIENT" => "bar", "BODY" => "foo"].
I have also tried -p SENDER=foo,RECIPIENT=bar,BODY='foo bar' but no luck with it either.
Does OptionParser support this scenario?
Thank you!
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"

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

Resources