How can I get info from user with OptionParser in Ruby? - ruby

For example, when I type ruby file.rb -a "water the plants" in command line
I want this line to be added on a hash. Such as a to-do list.
So it will look something like item1: water the plants
Here's what I did so far:
require 'optparse'
option_parser = OptionParser.new do |opts|
opts.on '-a', '--add',
end
Thanks in advance!

Look a bit more closely at the examples in the docs for OptionParser.
To accept a value for an argument, you have to specify it in the second argument to opts.on, something like this:
require 'optparse'
option_parser = OptionParser.new do |opts|
opts.on '-a', '--add val' do |value|
puts value
end
end.parse!
To make it a required argument, just change that val to capitalized VAL (it can be any word, I'm just using "val" as an example).
Calling it, you can see how it works:
ruby file.rb -a "water the plants"
# => "water the plants"
ruby file.rb -a "water the plants" "do the dishes"
# => "water the plants"
ruby file.rb -a "water the plants" -a "do the dishes"
# => water the plants
# => do the dishes
As you can see, to pass multiple values, you need to include the -a flag multiple times. The block is called for each value individually.

Related

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"

Command line option parsing in Ruby

I have a ruby script that parses command line options given to it as follows:
#!/usr/bin/ruby
require 'optparse'
puts 'Hello World!, This is my first ruby program'
options = {}
optparse = OptionParser.new do|opts|
opts.banner = "Featbuild minimal trial script for command line parsing"
options[:cpl] = nil
opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt|
options[:cpl] = cplopt
OPT_CPL=cplopt
puts cplopt
end
opts.on('-h', '--help', 'Display this screen') do
puts opts
exit
end
end
optparse.parse!
output = open("mypipe", "w+")
output.puts OPT_CPL
#output.flush
Now the line opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt| in the above script is where I have a problem.
I believe we can do it in follwoing ways:
1.)opts.on('--cpl SWITCH_STATE', 'compile on or off') do|cplopt|
2.)opts.on('-c', '--cpl SWITCH_STATE', 'compile on or off') do|cplopt|
3.)opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt|
This is what I pass as the arguments that works:
$./try1.rb --cpl on
$./try1.rb -c on
This does not work:
$./try1.rb -cpl on
Ruby, instead of getting 'on' as the option argument, gets 'pl', as if $./try.rb -c pl was specified.
I want to have the string $./try1.rb -cpl on be parsed in such a way that 'on' gets passed to the block of the method opts.on() in 'cplopt'.
I was referring to this tutorial: http://ruby.about.com/od/advancedruby/a/optionparser2.htm
It seems '-cpl on' isn't possible in Ruby? Is this so?
What other alternatve solutions can I apply over here?
Try Trollop, as it makes option parsing life easier.
require 'trollop'
opts = Trollop::options do
version "compile 0.1.0"
banner "Usage: compile <option> - where [options] are:"
opt :cpl, "compile on or off", :type => :string, :default => "off"
end
puts opts.cpl
When run, results in:
$ ruby ./trollop.rb --cpl on
on
$ ruby ./trollop.rb --cpl off
off
$ ruby ./trollop.rb -c on
on
$ ruby ./trollop.rb -c off
off
$ ruby ./trollop.rb
off
Trollop 2.0 supports no- negation of boolean options which you might find easier than dealing with the on/off strings.
opt "cpl", "Compile", :default => true
When run, results in:
$ ruby trollop.rb --cpl
true
$ ruby trollop.rb --no-cpl
false
I think you will need to ensure that only cp1 is in the single quotes instead of
-cpl SWITCH_STATE
do
opts.on('-cpl', 'compile on or off') do|cplopt|
options[:cpl] = cplopt
OPT_CPL=cplopt
puts cplopt
end
Here is an example:
opts.on('-s', '--size 1G or 1024M', '1G or 1024M') do |s|
options[:size] = s;
end

Printing list of only some options using Ruby OptionParser

My program has a lot of command line options. But I don't want to overwhelm the user when he types --help. Instead of printing out all options, I'd like to list only the most important ones, printing the rest of them when one types --help=full.
But OptionParser seems to only support printing of the whole list of defined options. I'd like to write my own code that loops over the defined options and prints them out in my way, but OptionParser doesn't seem to provide any ways to access the options definitions it contains.
Is there a way to access the options in OptionParser that I may have missed? Or is there some good alternative to OptionParser? Or some other approach to this problem?
You could redefine the option --help for your need.
require 'optparse'
#create parsers
opts = OptionParser.new()
opts.banner = "Usage: example.rb [options]"
opts.separator("test optparse with --help[=full]")
opts.on("-v", "--[no-]verbose", "Run verbosely") { |v|
puts "->Verbose ist #{v.inspect}"
}
opts.on("-r", "--repeat REPEAT", "Repeat REPEAT times") { |v|
puts "->Repeat ist #{v.inspect}"
}
#Define your own --help
opts.on("-h", "--help [HELP]", "Help") { |v|
case v
when 'full' #write original help
puts opts.help
when nil, '' #write script specific help
puts opts.banner
opts.summarize([], opts.summary_width ) { |helpline|
#make your own decision on each helpline
#~ puts helpline #puts each line
puts helpline unless helpline =~ /-v/ #ignore -v
}
else
puts opts.banner
puts <<helpmessage
Undefined --help option. Please use 'full' or no option
#{File.basename(__FILE__)} --help
#{File.basename(__FILE__)} --help=full
helpmessage
end
}
opts.parse!
In this version, --help shows all options, but not -v. You may make your own selection - or write a complete different help.

Resources