How does Ruby's OptionParser work? - ruby

"Minimal example" of OptionParser from http://ruby-doc.org/stdlib-2.1.5/libdoc/optparse/rdoc/OptionParser.html:
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
Main questions:
What exactly is the content of opts there? Is it the new OptionParser instance, or is it all of the /-\w/ or /--\w+/-looking things passed to the script? As a corollary, is that do block a loop or not?
What does parse! do? Why is it called on the whole do block?
Also wondering:
What is the OptionParser#banner method? In what context would you see that text?
In what context would you see the third parameter passed to OptionParser in that example, that little description of the flag's effect?
How can you create a custom error message if the script is run with an unknown option?

opts is just the new instance of OptionParser. The block supplied to .new is run with this line:
yield self if block_given?
parse! is the same thing as parse but it is destructive, meaning that it will remove used switches from ARGV. It is called on the entire do ... end block because the value returned is the new OptionParser instance.
banner gets the heading of the summary, which can be set with opts.banner = "foo"
The description is shown when the help is displayed (-h flag):
Usage: example.rb [options]
-v, --[no-]verbose Run verbosely
You could rescue the OptionParser::InvalidOption exception:
parser = OptionParser.new ...
begin
parser.parse!
rescue OptionParser::InvalidOption
puts 'Invalid args!'
end

Related

rspec exits when program exits

When RSpec comes across exit in my code, it also exits and no further tests are run. Here is a distilled example:
class Parser
def initialize(argv)
#options = {}
optparse(argv)
end
def optparse(argv)
OptionParser.new do |opts|
opts.on_tail('-h', '--help', 'Show this message') do
puts opts
exit
end
end.parse!(argv, into: #options)
end
end
RSpec.describe Parser do
context 'when -h is passed' do
it 'exits cleanly' do
expect(described_class.new(['-h'])).to raise(SystemExit)
end
end
context 'when --help is passed' do
it 'exits cleanly' do
expect(described_class.new(['--help'])).to raise(SystemExit)
end
end
end
I've also tried exit_with_code(0) and multiple forms of writing these two tests to get the 2nd one to run. Any suggestions?
If I replace
expect(described_class.new(['--help']))
with
expect { described_class.new(['--help']) }
it works.
And since I'm also doing other tests on the same command (in addition to the SystemExit check), I've had to explicitly rescue it and continue on.
it "other test" do
begin
parser.new(args)
rescue SystemExit
end
expect(...)
end
This pattern is kinda ugly, but works. The suppressed exception will require a rubocop override if you use rubocop, but I got that pattern from rubocop's own rspec tests, so ¯\_(ツ)_/¯

Spec Testing a Ruby CLI

I am trying to test the first ruby CLI i've written (n00b alert) and need some help. All my code is within 1 file, this includes a Class, OptionParser and some basic class execution methods. Here's an idea of what that looks like
The *rb. file
require 'optparse'
require 'fileutils'
class Foo
attr_accessor :arg, :opt
def initialize(p={})
#opt = p[:opt] || false
end
def do_something(arg)
#arg = arg
end
#more methods...
end
# Options
#options={}
#opt_parser = OptionParser.new do |opt|
opt.banner = "<{ FooBar }>"
opt.separator "------------"
opt.on("-o", "--opt", "An Option" do
#options[:opt] = true
end
end
#opt_parser.parse!
#CLI Execution
#foo = Foo.new(#options)
#foo.do_something(ARGV[0])
So here is the problem, i know would like to run some rspec tests rspec spec/ that i've wrote for the class, however the lines outside the class get executed of course and im left with an ARGV error.
What im looking for
Is there a better way to organize my code so i can test all the pieces, or how could i write a test to accommodate this file, Any and all suggestions would be greatly appreciated.
One posible solution is to wrap your option parsing code with a conditional that checks if the file is being run directly or loaded by some other file.
if __FILE__ == $0
# option parsing code
end
If you do that then all the code inside the if __FILE__ == $0 will not run with your test, but the rest of the code will run normally.

how to access global_options in ruby commander

How do I access the global_options of the ruby gem commander? I've see examples like
global_option('--verbose') { $verbose = true }
...
c.action do |args, options|
say 'foo' if $verbose
...
If I want to check the option outside of the c.action block, how is that done?
As to my knowledge one wouldn't access global_option directly outside the action block.
That's not a problem as there are multiple options to handle global settings, both tied to the block being called by global_option.
Option 1:
Set hash/array inside the block.
my_options = Hash.new
global_option('--verbose') do |f|
my_options['verbose'] = true
end
...
puts "Verbose." if my_options['verbose']
Option 2:
Do stuff inside the block once the option is triggered by the user.
global_option('--debug') do |f|
MyLogger.level = Logger::DEBUG
end

Ruby Error - uninitialized constant OpenStruct (NameError)

I am trying to use optionparse of ruby to parse the arguments to my ruby script. Problem is when I am running the script like this
bundler exec ruby generation.rb --help
I am getting error
"uninitialized constant OpenStruct (NameError)"
I believe since I am running the script using bundle exec I should not be getting this error. What am I doing wrong.
require 'optparse'
def parse(args)
options = OpenStruct.new
options.dir = '../somerepo'
opts = OptionParser.new do |opts|
opts.banner = "Usage: generation.rb [options]"
opts.separator ""
opts.separator "Options:"
opts.on("--temp c_name", "abcddd") { |abc|
options.temp = abc
}
opts.separator ""
opts.on_tail("-h", "--help", "Show this message") {
puts opts
exit
}
opts.parse!(args)
return options
end
end
inputOpts = parse(ARGV)
You should require OpenStruct source manually:
require 'ostruct'

Commands in ruby terminal application

I have just written my first terminal application in ruby. I use OptionParser to parse the options and their arguments. However I want to create commands. For example:
git add .
In the above line, add is the command which cannot occur anywhere else than immediately after the application. How do I create these.
I will appreciate if anyone could point me in the right direction. However, please do not reference any gems such as Commander. I already know about these. I want to understand how it is done.
The OptionParser's parse! takes an array of arguments. By default, it will take ARGV, but you can override this behaviour like so:
Basic Approach
def build_option_parser(command)
# depending on `command`, build your parser
OptionParser.new do |opt|
# ...
end
end
args = ARGV
command = args.shift # pick and remove the first option, do some validation...
#options = build_option_parser(command).parse!(args) # parse the rest of it
Advanced Approach
Instead of a build_option_parser method with a huge case-statement, consider an OO approach:
class AddCommand
attr_reader :options
def initialize(args)
#options = {}
#parser = OptionParser.new #...
#parser.parse(args)
end
end
class MyOptionParser
def initialize(command, args)
#parser = {
'add' => AddCommand,
'...' => DotsCommand
}[command.to_s].new(args)
end
def options
#parser.options
end
end
Alternatives
For sure, there exist tons of Rubygems (well, 20 in that list), which will take care of your problem. I'd like to mention Thor which powers, e.g. the rails command line tool.
You can retrieve the command with Array#shift prior invoking OptionParser.

Resources