How to use optparse to have a flags argument require another argument - ruby

I have a tool that I'm updating and need to have an argument require another argument, for example:
require 'optparse'
OPTIONS = {}
OptionParser.new do |opts|
opts.on('-t', '--type INPUT', String, 'Verify a type'){ |o| OPTIONS[:type] = o }
end.parse!
def help_page
puts 'ruby test.rb -t dev'
end
def gather_type
case OPTIONS[:type]
when /dev/
unlock(OPTIONS[:type])
else
help_page
end
end
def unlock(type)
if type == 'unlock' #Find out what type by passing argument another argument
puts 'Unlock account'
else
puts 'Reset account'
end
end
def start
case
when OPTIONS[:type]
gather_type
else
help_page
end
end
start
When this is run you get the following:
C:\Users\bin\ruby>ruby test.rb -t dev=unlock
Reset account
C:\Users\bin\ruby>ruby test.rb -t dev=reset
Reset account
Now that's all well and dandy but what I want to do is give the dev part an argument and go from there to decide if it's an unlock or if it's a reset:
ruby test.rb -t dev=unlock OR ruby test.rb -t dev=reset
After that I want the unlock(type) method to determine what argument was given to the flags argument and output the correct information, so
C:\Users\bin\ruby>ruby test.rb -t dev=unlock
Unlock account
C:\Users\bin\ruby>ruby test.rb -t dev=reset
Reset account
How can I go about to determine if an argument was given to the argument of the flag?

I figured out that if you put an options in parenthesis you can get what I'm asking:
require 'optparse'
OPTIONS = {}
OptionParser.new do |opts|
opts.on('-t', '--type INPUT[=INPUT]', String, 'Verify a type'){ |o| OPTIONS[:type] = o }
end.parse!
def help_page
puts 'ruby test.rb -t dev'
end
def gather_type
case OPTIONS[:type]
when /dev/
unlock(OPTIONS[:type])
else
help_page
end
end
def unlock(type)
if type =~ /unlock/ #Find out what type by passing argument another argument
puts 'Unlock account'
elsif type =~ /reset/
puts 'Reset account'
else
puts 'No flag given defaulting to unlock'
end
end
def start
case
when OPTIONS[:type]
gather_type
else
help_page
end
end
start
C:\Users\bin\ruby>ruby test.rb -t dev
No flag given defaulting to unlock
C:\Users\bin\ruby>ruby test.rb -t dev=unlock
Unlock account
C:\Users\bin\ruby>ruby test.rb -t dev=reset
Reset account

Related

Declaring Ruby Methods along with a menu

I've been fooling around with Ruby lately, so i decided to write an sftp client.
require 'net/sftp'
require 'ostruct'
require 'optparse'
class Sftp
def parse(arguments)
ARGV << "-h" if ARGV.empty?
#options = OpenStruct.new
args = OptionParser.new do |args|
args.banner = "Usage: #{__FILE__} [options]"
args.on("--host HOST", String,
) do |host|
#options.host = host
end
args.on("--username USERNAME", String,
) do |username|
#options.username = username
end
args.on("--password PASSWORD", String,
) do |password|
#options.password = password
end
args.on("--port=PORT", Integer,
) do |port|
#options.port = port
end
args.on("--mkdir=MAKE DIRECTORY", String,
) do |mkdir|
#options.mkdir = mkdir
end
args.on("--rmdir=REMOVE DIRECTORY", String,
) do |rmdir|
#options.rmdir = rmdir
end
args.on("-h", "--help", "Show help and exit") do
puts args
exit
end
end
begin
args.parse!(arguments)
rescue OptionParser::MissingArgument => error
puts "[!] ".red + error.message.bold
exit
rescue OptionParser::InvalidOption => error
puts "[!] ".red + error.message.bold
exit
end
def connect
Net::SFTP.start(#options.host, #options.username, :password => #options.password, :port => #options.port) do |sftp|
sftp.mkdir(#options.mkdir)
puts "Creating Directory: #{#options.mkdir}"
sftp.rmdir(#options.rmdir)
puts "Deleting Directory: #{#options.rmdir}"
end
end
end
def run(arguments)
parse(arguments)
connect
end
end
sftp = Sftp.new
sftp.run(ARGV)
I want these two commands to be separated. For example when i pass
the argument mkdir I just want only this to run and if I want to run rmdir again I just wanna run only this command.
It has to do with methods, but I can't find a proper solution. And I'm really rusty.
Any recommendation?
A very simple approach could be to check if the required value is set before running the command, and skip the command if the value is not set.
def connect
Net::SFTP.start(#options.host, #options.username, password: #options.password, port: #options.port) do |sftp|
if #options.mkdir
sftp.mkdir(#options.mkdir)
puts "Creating Directory: #{#options.mkdir}"
end
if #options.rmdir
sftp.rmdir(#options.rmdir)
puts "Deleting Directory: #{#options.rmdir}"
end
end
end

how to share a rake variable in the code it invokes?

What I need is basically send a target argument and use it in my RSpec tests, e.g.:
$ rake unistall_and_run[test_spec.rb]
Rakefile:
desc 'uninstall app to run tests'
task :uninstall_and_run, [:arg] do |t, arg|
#note this, i will explain later
start_driver(fullReset: true)
oi = arg.to_s.split('"')[1]
file_dir = (project_home + '/spec/' + oi)
exec "rspec #{file_dir}"
end
start_driver is called on that line, but when I run the tests (exec "rspec ..."), it is called again and the args I passed is overwritten by the default (because its on RSpec config).
What I'd like to do is, on my RSpec file check if it was already called and don't run again.
Here is the start_driver method:
def start_driver(options= {})
if options.empty?
capabilities = caps
else
capabilities = caps(options)
end
$appium = Appium::Driver.new(caps: capabilities)
$browser = $appium.start_driver
Appium.promote_appium_methods RSpec::Core::ExampleGroup
end
So, i found a way to do it. Its not beautiful though. I save a file with the args I want when run rake:
desc 'uninstall app to run tests'
task :uninstall_and_run, [:arg] do |t, arg|
send_custom_caps(fullReset: true)
oi = arg.to_s.split('"')[1]
file_dir = (project_home + '/spec/' + oi)
exec "rspec #{file_dir}"
end
and the send_custom_caps method is:
def send_custom_caps(*opts)
file = File.new(custom_caps_file, "w+")
File.open(file, 'w') do |f|
f.write(opts)
end
end
now the ruby code itself (in this case, my spec config) will check if there is custom args before start_driver. Here is my custom start_driver method (which I renamed):
def start_appium_driver (options= {})
if options.empty?
get_caps
if $custom_args
capabilities = caps($custom_args)
else
capabilities = caps
end
else
capabilities = caps(options)
end
$appium = Appium::Driver.new(caps: capabilities)
$browser = $appium.start_driver
Appium.promote_appium_methods RSpec::Core::ExampleGroup
end
and get_caps
def get_caps
if File.exist?(custom_caps_file) #$custom_args
file = File.read(custom_caps_file)
$custom_args = eval(file)
File.delete (custom_caps_file)
end
$custom_args unless $custom_args.defined?
end
probably this is not the best solution, but it is working ok for me :)

How does Ruby's OptionParser work?

"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

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'

How to get exit status with Ruby's Net::SSH library?

I have a snippet of code, simply trying to execute a script on a remote server, in the event that it fails, I'd like to make a follow-up call, imagine this:
require 'rubygems'
require 'net/ssh'
require 'etc'
server = 'localhost'
Net::SSH.start(server, Etc.getlogin) do |ssh|
puts (ssh.exec("true") ? 'Exit Success' : "Exit Failure")
puts (ssh.exec("false") ? 'Exit Success' : "Exit Failure")
end
I would expect (ignoring that stdout and stderr are printed in my contrived example) - but first line should exit with 0 which I would expect Ruby would interperate as false and display "Exit Failure" (sure, so the logic is wrong, the ternary needs to be flipped) - but the second line should exit with the opposite status, and it doesn't.
I can't even find anything in the documentation about how to do this, and I'm a little worried that I might be doing it wrong?!
I find the following way of running processes with Net::SSH much more useful. It provides you with distinct stdout and stderr, exit code and exit signal.
require 'rubygems'
require 'net/ssh'
require 'etc'
server = 'localhost'
def ssh_exec!(ssh, command)
stdout_data = ""
stderr_data = ""
exit_code = nil
exit_signal = nil
ssh.open_channel do |channel|
channel.exec(command) do |ch, success|
unless success
abort "FAILED: couldn't execute command (ssh.channel.exec)"
end
channel.on_data do |ch,data|
stdout_data+=data
end
channel.on_extended_data do |ch,type,data|
stderr_data+=data
end
channel.on_request("exit-status") do |ch,data|
exit_code = data.read_long
end
channel.on_request("exit-signal") do |ch, data|
exit_signal = data.read_long
end
end
end
ssh.loop
[stdout_data, stderr_data, exit_code, exit_signal]
end
Net::SSH.start(server, Etc.getlogin) do |ssh|
puts ssh_exec!(ssh, "true").inspect
# => ["", "", 0, nil]
puts ssh_exec!(ssh, "false").inspect
# => ["", "", 1, nil]
end
Hope this helps.
Building on the answer by flitzwald - I've monkey patched my version of this into Net::SSH (Ruby 1.9+)
class Net::SSH::Connection::Session
class CommandFailed < StandardError
end
class CommandExecutionFailed < StandardError
end
def exec_sc!(command)
stdout_data,stderr_data = "",""
exit_code,exit_signal = nil,nil
self.open_channel do |channel|
channel.exec(command) do |_, success|
raise CommandExecutionFailed, "Command \"#{command}\" was unable to execute" unless success
channel.on_data do |_,data|
stdout_data += data
end
channel.on_extended_data do |_,_,data|
stderr_data += data
end
channel.on_request("exit-status") do |_,data|
exit_code = data.read_long
end
channel.on_request("exit-signal") do |_, data|
exit_signal = data.read_long
end
end
end
self.loop
raise CommandFailed, "Command \"#{command}\" returned exit code #{exit_code}" unless exit_code == 0
{
stdout:stdout_data,
stderr:stderr_data,
exit_code:exit_code,
exit_signal:exit_signal
}
end
end
For newer versions of Net::SSH, you can just pass a status hash to Net::SSH::Connection::Session#exec:
status = {}
Net::SSH.start(hostname, user, options) do |ssh|
channel = ssh.exec(command, status: status)
channel.wait # wait for the command to actually be executed
end
puts status.inspect
# {:exit_code=>0}
By default, exec streams its output to $stdout and $stderr. You can pass a block to exec to do something different, a la:
ssh.exec(command, status: status) do |ch, stream, data|
if stream == :stdout
do_something_with_stdout(data)
else
do_something_with_stderr(data)
end
end
This works on 6.1.0 - not sure about availability for older versions. See http://net-ssh.github.io/net-ssh/Net/SSH/Connection/Session.html#method-i-exec for more details.

Resources